diff --git a/docs/resources/kubectl_manifest.md b/docs/resources/kubectl_manifest.md index a180509f..35c55c34 100644 --- a/docs/resources/kubectl_manifest.md +++ b/docs/resources/kubectl_manifest.md @@ -37,6 +37,7 @@ YAML > Note: When the kind is a Deployment, this provider will wait for the deployment to be rolled out automatically for you! ### With explicit `wait_for` + If `wait_for` is specified, upon applying the resource, provider will wait for **all** conditions to become true before proceeding further. ```hcl @@ -88,23 +89,21 @@ YAML * `validate_schema` - Optional. Setting to `false` will mimic `kubectl apply --validate=false` mode. Default `true`. * `wait` - Optional. Set this flag to wait or not for finalized to complete for deleted objects. Default `false`. * `wait_for_rollout` - Optional. Set this flag to wait or not for Deployments and APIService to complete rollout. Default `true`. -* `wait_for`- Optional. If set, will wait until either all conditions are satisfied, or until timeout is reached (see [below for nested schema](#nestedblock--wait_for)). Under the hood [gojsonq](https://github.com/thedevsaddam/gojsonq) is used for querying, see the related syntax and examples +* `wait_for` - Optional. If set, will wait until either all conditions are satisfied, or until timeout is reached (see [below for nested schema](#wait_for)). Under the hood [gojsonq](https://github.com/thedevsaddam/gojsonq) is used for querying, see the related syntax and examples. +* `delete_cascade` - Optional; `Background` or `Foreground` are valid options. If set this overrides the default provider behaviour which is to use `Background` unless `wait` is `true` when `Foreground` will be used. To duplicate the default behaviour of `kubectl` this should be explicitly set to `Background`. -### Nested schemas - -### Nested Schema for `wait_for` +### `wait_for` Required: -- `field` (Block List, Min: 1) Condition criteria for a field (see [below for nested schema](#nestedblock--wait_for--field)) +* `field` (Block List, Min: 1) Condition criteria for a field (see [below for nested schema](#wait_forfield)) - -### Nested Schema for `wait_for.field` +### `wait_for.field` Required: -- `key` (String) Key which should be matched from resulting object -- `value` (String) Value to wait for +* `key` (String) Key which should be matched from resulting object +* `value` (String) Value to wait for Optional: @@ -152,7 +151,6 @@ YAML > Note: Only Map values are supported to be made sensitive. If you need to make a value from a list (or sub-list) sensitive, you can set the high-level key as sensitive to suppress the entire tree output. - ## Ignore Manifest Fields You can configure a list of yaml keys to ignore changes to via the `ignore_fields` field. @@ -219,7 +217,7 @@ You can disable this behavior by setting the `wait_for_rollout` field to `false` This provider supports importing existing resources. The ID format expected uses a double `//` as a deliminator (as apiVersion can have a forward-slash): -``` +```shell # Import the my-namespace Namespace terraform import kubectl_manifest.my-namespace v1//Namespace//my-namespace diff --git a/kubernetes/resource_kubectl_manifest.go b/kubernetes/resource_kubectl_manifest.go index d816f7b0..6af9d2d8 100644 --- a/kubernetes/resource_kubectl_manifest.go +++ b/kubernetes/resource_kubectl_manifest.go @@ -5,9 +5,6 @@ import ( "crypto/sha256" "encoding/base64" "fmt" - "github.com/sergi/go-diff/diffmatchpatch" - "k8s.io/cli-runtime/pkg/genericiooptions" - k8sdelete "k8s.io/kubectl/pkg/cmd/delete" "log" "os" "regexp" @@ -15,6 +12,10 @@ import ( "strings" "time" + "github.com/sergi/go-diff/diffmatchpatch" + "k8s.io/cli-runtime/pkg/genericiooptions" + k8sdelete "k8s.io/kubectl/pkg/cmd/delete" + "github.com/alekc/terraform-provider-kubectl/flatten" "github.com/alekc/terraform-provider-kubectl/internal/types" @@ -24,7 +25,9 @@ import ( validate2 "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/mitchellh/mapstructure" "github.com/thedevsaddam/gojsonq/v2" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/watch" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/printers" "k8s.io/kubectl/pkg/scheme" @@ -411,7 +414,7 @@ var ( }, "field_manager": { Type: schema.TypeString, - Description: "Override the default field manager name. This is only relevent when using server-side apply.", + Description: "Override the default field manager name. This is only relevant when using server-side apply.", Optional: true, Default: "kubectl", }, @@ -487,6 +490,12 @@ var ( }, }, }, + "delete_cascade": { + Type: schema.TypeString, + Description: "Cascade mode for delete operations, explicitly setting this to Background to match kubectl is recommended. Default is Background unless wait has been set when it will be Foreground.", + Optional: true, + ValidateDiagFunc: validate2.ToDiagFunc(validate2.StringInSlice([]string{string(meta_v1.DeletePropagationBackground), string(meta_v1.DeletePropagationForeground)}, false)), + }, } ) @@ -719,29 +728,47 @@ func resourceKubectlManifestDelete(ctx context.Context, d *schema.ResourceData, log.Printf("[INFO] %s perform delete of manifest", manifest) - propagationPolicy := meta_v1.DeletePropagationBackground waitForDelete := d.Get("wait").(bool) - if waitForDelete { + + var propagationPolicy meta_v1.DeletionPropagation + cascadeInput := d.Get("delete_cascade").(string) + if len(cascadeInput) > 0 { + propagationPolicy = meta_v1.DeletionPropagation(cascadeInput) + } else if waitForDelete { propagationPolicy = meta_v1.DeletePropagationForeground + } else { + propagationPolicy = meta_v1.DeletePropagationBackground } + err = restClient.ResourceInterface.Delete(ctx, manifest.GetName(), meta_v1.DeleteOptions{PropagationPolicy: &propagationPolicy}) resourceGone := errors.IsGone(err) || errors.IsNotFound(err) if err != nil && !resourceGone { return fmt.Errorf("%v failed to delete kubernetes resource: %+v", manifest, err) } - // at the moment the foreground propagation policy does not behave as expected (it won't block waiting for deletion - // and it's up to us to check that the object has been successfully deleted. - for waitForDelete { - _, err := restClient.ResourceInterface.Get(ctx, manifest.GetName(), meta_v1.GetOptions{}) - resourceGone = errors.IsGone(err) || errors.IsNotFound(err) + + // The rest client doesn't wait for the delete so we need custom logic + if waitForDelete { + log.Printf("[INFO] %s waiting for delete of manifest to complete", manifest) + + watcher, err := restClient.ResourceInterface.Watch(ctx, meta_v1.ListOptions{FieldSelector: fields.OneTermEqualSelector("metadata.name", manifest.GetName()).String()}) if err != nil { - if resourceGone { - break + return err + } + + defer watcher.Stop() + + deleted := false + for !deleted { + select { + case event := <-watcher.ResultChan(): + if event.Type == watch.Deleted { + deleted = true + } + + case <-ctx.Done(): + return fmt.Errorf("%v failed to delete kubernetes resource: %+v", manifest, err) } - return fmt.Errorf("%v failed to delete kubernetes resource: %+v", manifest, err) } - log.Printf("[DEBUG] %v waiting for deletion of the resource:\n%s", manifest, yamlBody) - time.Sleep(time.Second * 10) } // Success remove it from state diff --git a/kubernetes/resource_kubectl_manifest_test.go b/kubernetes/resource_kubectl_manifest_test.go index faa2e602..d3d4e4b8 100644 --- a/kubernetes/resource_kubectl_manifest_test.go +++ b/kubernetes/resource_kubectl_manifest_test.go @@ -3,15 +3,16 @@ package kubernetes import ( "bytes" "fmt" + "log" + "os" + "regexp" + "testing" + "github.com/alekc/terraform-provider-kubectl/yaml" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "log" - "os" - "regexp" - "testing" ) func TestKubectlManifest_RetryOnFailure(t *testing.T) { @@ -38,6 +39,191 @@ YAML }) } +func TestAccKubectl(t *testing.T) { + //language=hcl + config := ` +resource "kubectl_manifest" "test" { + wait = true + yaml_body = <