Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix deletion not found handling in the delete operation and adding related tests #2592

Merged
merged 4 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
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"
}
}
}

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
}
Loading