diff --git a/manifest/provider/apply.go b/manifest/provider/apply.go index 22451dc5ed..f372531fb5 100644 --- a/manifest/provider/apply.go +++ b/manifest/provider/apply.go @@ -540,16 +540,25 @@ func (s *RawProviderServer) ApplyResourceChange(ctx context.Context, req *tfprot err = rs.Delete(ctxDeadline, rname, metav1.DeleteOptions{}) if err != nil { - rn := types.NamespacedName{Namespace: rnamespace, Name: rname}.String() - resp.Diagnostics = append(resp.Diagnostics, - &tfprotov5.Diagnostic{ - Severity: tfprotov5.DiagnosticSeverityError, - Summary: fmt.Sprintf("Error deleting resource %s: %s", rn, err), - Detail: err.Error(), - }) - return resp, nil - } + if apierrors.IsNotFound(err) { + s.logger.Trace("[ApplyResourceChange][Delete]", "Resource is already deleted") + resp.Diagnostics = append(resp.Diagnostics, + &tfprotov5.Diagnostic{ + Severity: tfprotov5.DiagnosticSeverityWarning, + Summary: fmt.Sprintf("Resource %q was already deleted", rname), + Detail: fmt.Sprintf("The resource %q was not found in the Kubernetes API. This may be due to the resource being already deleted.", rname), + }) + } else { + resp.Diagnostics = append(resp.Diagnostics, + &tfprotov5.Diagnostic{ + Severity: tfprotov5.DiagnosticSeverityError, + Summary: fmt.Sprintf("Error deleting resource %s: %s", rname, err), + Detail: err.Error(), + }) + return resp, nil + } + } // wait for delete for { if time.Now().After(deadline) { diff --git a/manifest/test/acceptance/delete_not_found_test.go b/manifest/test/acceptance/delete_not_found_test.go new file mode 100644 index 0000000000..90f242d097 --- /dev/null +++ b/manifest/test/acceptance/delete_not_found_test.go @@ -0,0 +1,77 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build acceptance +// +build acceptance + +package acceptance + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/terraform-provider-kubernetes/manifest/provider" + "github.com/hashicorp/terraform-provider-kubernetes/manifest/test/helper/kubernetes" +) + +func TestKubernetesManifest_DeletionNotFound(t *testing.T) { + ctx := context.Background() + + reattachInfo, err := provider.ServeTest(ctx, hclog.Default(), t) + if err != nil { + t.Fatalf("Failed to create provider instance: %v", err) + } + + name := randName() + namespace := randName() + + tf := tfhelper.RequireNewWorkingDir(ctx, t) + tf.SetReattachInfo(ctx, reattachInfo) + + k8shelper.CreateNamespace(t, namespace) + t.Logf("Verifying if namespace %s exists", namespace) + k8shelper.AssertResourceExists(t, "v1", "namespaces", namespace) + + defer func() { + tf.Destroy(ctx) + tf.Close() + k8shelper.DeleteResource(t, namespace, kubernetes.NewGroupVersionResource("v1", "namespaces")) + k8shelper.AssertResourceDoesNotExist(t, "v1", "namespaces", namespace) + }() + + tfvars := TFVARS{ + "namespace": namespace, + "name": name, + } + + // Load the Terraform config that will create the ConfigMap + tfconfig := loadTerraformConfig(t, "DeleteNotFoundTest/resource.tf", tfvars) + tf.SetConfig(ctx, tfconfig) + + t.Log("Applying Terraform configuration to create ConfigMap") + if err := tf.Apply(ctx); err != nil { + t.Fatalf("Terraform apply failed: %v", err) + } + + state, err := tf.State(ctx) + if err != nil { + t.Fatalf("Failed to retrieve Terraform state: %v", err) + } + t.Logf("Terraform state: %v", state) + + time.Sleep(2 * time.Second) + + t.Logf("Checking if ConfigMap %s in namespace %s was created", name, namespace) + k8shelper.AssertNamespacedResourceExists(t, "v1", "configmaps", namespace, name) + + // Simulating the deletion of the resource outside of Terraform + k8shelper.DeleteNamespacedResource(t, name, namespace, kubernetes.NewGroupVersionResource("v1", "configmaps")) + + // Running tf destroy in order to check if we are handling "404 Not Found" gracefully + tf.Destroy(ctx) + + // Ensuring that the ConfigMap no longer exists + k8shelper.AssertNamespacedResourceDoesNotExist(t, "v1", "configmaps", namespace, name) +} diff --git a/manifest/test/acceptance/testdata/DeleteNotFoundTest/resource.tf b/manifest/test/acceptance/testdata/DeleteNotFoundTest/resource.tf new file mode 100644 index 0000000000..76316832dc --- /dev/null +++ b/manifest/test/acceptance/testdata/DeleteNotFoundTest/resource.tf @@ -0,0 +1,14 @@ +resource "kubernetes_manifest" "test" { + manifest = { + "apiVersion" = "v1" + "kind" = "ConfigMap" + "metadata" = { + "name" = var.name + "namespace" = var.namespace + } + "data" = { + "foo" = "bar" + } + } +} + diff --git a/manifest/test/acceptance/testdata/DeleteNotFoundTest/variables.tf b/manifest/test/acceptance/testdata/DeleteNotFoundTest/variables.tf new file mode 100644 index 0000000000..f526a6a4bf --- /dev/null +++ b/manifest/test/acceptance/testdata/DeleteNotFoundTest/variables.tf @@ -0,0 +1,19 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# These variable declarations are only used for interactive testing. +# The test code will template in different variable declarations with a default value when running the test. +# +# To set values for interactive runs, create a var-file and set values in it. +# If the name of the var-file ends in '.auto.tfvars' (e.g. myvalues.auto.tfvars) +# it will be automatically picked up and used by Terraform. +# +# DO NOT check in any files named *.auto.tfvars when making changes to tests. + +variable "name" { + type = string +} + +variable "namespace" { + type = string +} \ No newline at end of file