Skip to content

Commit c6c0a76

Browse files
Fix deletion not found handling in the delete operation and adding related tests (#2592)
* Fix deletion not found handling and add related tests * Adding changelog entry * Adding license header * Adding refernece to manifest resource / fixing return statement outside of else block
1 parent 9d6074c commit c6c0a76

File tree

5 files changed

+132
-8
lines changed

5 files changed

+132
-8
lines changed

.changelog/2592.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
`kubernetes_manifest` - handling "404 Not Found" errors during the deletion of Kubernetes resources, particularly in cases where the resource may have already been deleted by an operator managing the CRD before Terraform attempts to delete it.
3+
```

manifest/provider/apply.go

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -540,16 +540,25 @@ func (s *RawProviderServer) ApplyResourceChange(ctx context.Context, req *tfprot
540540

541541
err = rs.Delete(ctxDeadline, rname, metav1.DeleteOptions{})
542542
if err != nil {
543-
rn := types.NamespacedName{Namespace: rnamespace, Name: rname}.String()
544-
resp.Diagnostics = append(resp.Diagnostics,
545-
&tfprotov5.Diagnostic{
546-
Severity: tfprotov5.DiagnosticSeverityError,
547-
Summary: fmt.Sprintf("Error deleting resource %s: %s", rn, err),
548-
Detail: err.Error(),
549-
})
543+
if apierrors.IsNotFound(err) {
544+
s.logger.Trace("[ApplyResourceChange][Delete]", "Resource is already deleted")
545+
546+
resp.Diagnostics = append(resp.Diagnostics,
547+
&tfprotov5.Diagnostic{
548+
Severity: tfprotov5.DiagnosticSeverityWarning,
549+
Summary: fmt.Sprintf("Resource %q was already deleted", rname),
550+
Detail: fmt.Sprintf("The resource %q was not found in the Kubernetes API. This may be due to the resource being already deleted.", rname),
551+
})
552+
} else {
553+
resp.Diagnostics = append(resp.Diagnostics,
554+
&tfprotov5.Diagnostic{
555+
Severity: tfprotov5.DiagnosticSeverityError,
556+
Summary: fmt.Sprintf("Error deleting resource %s: %s", rname, err),
557+
Detail: err.Error(),
558+
})
559+
}
550560
return resp, nil
551561
}
552-
553562
// wait for delete
554563
for {
555564
if time.Now().After(deadline) {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
//go:build acceptance
5+
// +build acceptance
6+
7+
package acceptance
8+
9+
import (
10+
"context"
11+
"testing"
12+
"time"
13+
14+
"github.com/hashicorp/go-hclog"
15+
"github.com/hashicorp/terraform-provider-kubernetes/manifest/provider"
16+
"github.com/hashicorp/terraform-provider-kubernetes/manifest/test/helper/kubernetes"
17+
)
18+
19+
func TestKubernetesManifest_DeletionNotFound(t *testing.T) {
20+
ctx := context.Background()
21+
22+
reattachInfo, err := provider.ServeTest(ctx, hclog.Default(), t)
23+
if err != nil {
24+
t.Fatalf("Failed to create provider instance: %v", err)
25+
}
26+
27+
name := randName()
28+
namespace := randName()
29+
30+
tf := tfhelper.RequireNewWorkingDir(ctx, t)
31+
tf.SetReattachInfo(ctx, reattachInfo)
32+
33+
k8shelper.CreateNamespace(t, namespace)
34+
t.Logf("Verifying if namespace %s exists", namespace)
35+
k8shelper.AssertResourceExists(t, "v1", "namespaces", namespace)
36+
37+
defer func() {
38+
tf.Destroy(ctx)
39+
tf.Close()
40+
k8shelper.DeleteResource(t, namespace, kubernetes.NewGroupVersionResource("v1", "namespaces"))
41+
k8shelper.AssertResourceDoesNotExist(t, "v1", "namespaces", namespace)
42+
}()
43+
44+
tfvars := TFVARS{
45+
"namespace": namespace,
46+
"name": name,
47+
}
48+
49+
// Load the Terraform config that will create the ConfigMap
50+
tfconfig := loadTerraformConfig(t, "DeleteNotFoundTest/resource.tf", tfvars)
51+
tf.SetConfig(ctx, tfconfig)
52+
53+
t.Log("Applying Terraform configuration to create ConfigMap")
54+
if err := tf.Apply(ctx); err != nil {
55+
t.Fatalf("Terraform apply failed: %v", err)
56+
}
57+
58+
state, err := tf.State(ctx)
59+
if err != nil {
60+
t.Fatalf("Failed to retrieve Terraform state: %v", err)
61+
}
62+
t.Logf("Terraform state: %v", state)
63+
64+
time.Sleep(2 * time.Second)
65+
66+
t.Logf("Checking if ConfigMap %s in namespace %s was created", name, namespace)
67+
k8shelper.AssertNamespacedResourceExists(t, "v1", "configmaps", namespace, name)
68+
69+
// Simulating the deletion of the resource outside of Terraform
70+
k8shelper.DeleteNamespacedResource(t, name, namespace, kubernetes.NewGroupVersionResource("v1", "configmaps"))
71+
72+
// Running tf destroy in order to check if we are handling "404 Not Found" gracefully
73+
tf.Destroy(ctx)
74+
75+
// Ensuring that the ConfigMap no longer exists
76+
k8shelper.AssertNamespacedResourceDoesNotExist(t, "v1", "configmaps", namespace, name)
77+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright (c) HashiCorp, Inc.
2+
# SPDX-License-Identifier: MPL-2.0
3+
resource "kubernetes_manifest" "test" {
4+
manifest = {
5+
"apiVersion" = "v1"
6+
"kind" = "ConfigMap"
7+
"metadata" = {
8+
"name" = var.name
9+
"namespace" = var.namespace
10+
}
11+
"data" = {
12+
"foo" = "bar"
13+
}
14+
}
15+
}
16+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Copyright (c) HashiCorp, Inc.
2+
# SPDX-License-Identifier: MPL-2.0
3+
4+
# These variable declarations are only used for interactive testing.
5+
# The test code will template in different variable declarations with a default value when running the test.
6+
#
7+
# To set values for interactive runs, create a var-file and set values in it.
8+
# If the name of the var-file ends in '.auto.tfvars' (e.g. myvalues.auto.tfvars)
9+
# it will be automatically picked up and used by Terraform.
10+
#
11+
# DO NOT check in any files named *.auto.tfvars when making changes to tests.
12+
13+
variable "name" {
14+
type = string
15+
}
16+
17+
variable "namespace" {
18+
type = string
19+
}

0 commit comments

Comments
 (0)