Skip to content

Commit

Permalink
Fix deletion not found handling in the delete operation and adding re…
Browse files Browse the repository at this point in the history
…lated tests (hashicorp#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
  • Loading branch information
JaylonmcShan03 authored Oct 1, 2024
1 parent 9d6074c commit c6c0a76
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 8 deletions.
3 changes: 3 additions & 0 deletions .changelog/2592.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
`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.
```
25 changes: 17 additions & 8 deletions manifest/provider/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
})
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) {
Expand Down
77 changes: 77 additions & 0 deletions manifest/test/acceptance/delete_not_found_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
16 changes: 16 additions & 0 deletions manifest/test/acceptance/testdata/DeleteNotFoundTest/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
resource "kubernetes_manifest" "test" {
manifest = {
"apiVersion" = "v1"
"kind" = "ConfigMap"
"metadata" = {
"name" = var.name
"namespace" = var.namespace
}
"data" = {
"foo" = "bar"
}
}
}

19 changes: 19 additions & 0 deletions manifest/test/acceptance/testdata/DeleteNotFoundTest/variables.tf
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit c6c0a76

Please sign in to comment.