From f241e9a70cf426489149c5ec124faf79390a627e Mon Sep 17 00:00:00 2001 From: Gavin Williams Date: Wed, 15 Oct 2025 16:38:09 +0100 Subject: [PATCH 1/3] fix: Resolve panic with `ElementKeyInt(0): can't use tftypes` This commit updates the `manifest/morph/scaffold.go` logic in order to resolve a panic encountered when working with `Tuple` types. Also adds a new unit test to cover this case. Disclaimer: This fix and the coresponding test case was generated by Gemini, and has been reviewed and tested by myself. Fixes: #2778 #2754 plus probably others... --- manifest/morph/scaffold.go | 4 ++- manifest/morph/scaffold_test.go | 55 +++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/manifest/morph/scaffold.go b/manifest/morph/scaffold.go index 853a3bc4c8..c4e10ac190 100644 --- a/manifest/morph/scaffold.go +++ b/manifest/morph/scaffold.go @@ -75,6 +75,7 @@ func DeepUnknown(t tftypes.Type, v tftypes.Value, p *tftypes.AttributePath) (tft if err != nil { return tftypes.Value{}, p.NewError(err) } + ntypes := make([]tftypes.Type, len(atts)) for i, et := range atts { np := p.WithElementKeyInt(i) nv, err := DeepUnknown(et, vals[i], np) @@ -82,8 +83,9 @@ func DeepUnknown(t tftypes.Type, v tftypes.Value, p *tftypes.AttributePath) (tft return tftypes.Value{}, np.NewError(err) } vals[i] = nv + ntypes[i] = nv.Type() } - return tftypes.NewValue(tftypes.Tuple{ElementTypes: atts}, vals), nil + return tftypes.NewValue(tftypes.Tuple{ElementTypes: ntypes}, vals), nil case t.Is(tftypes.List{}) || t.Is(tftypes.Set{}): if v.IsNull() { return tftypes.NewValue(t, tftypes.UnknownValue), nil diff --git a/manifest/morph/scaffold_test.go b/manifest/morph/scaffold_test.go index 63636fb952..151fde026a 100644 --- a/manifest/morph/scaffold_test.go +++ b/manifest/morph/scaffold_test.go @@ -299,6 +299,61 @@ func TestDeepUnknown(t *testing.T) { }, ), }, + "tuple-with-dynamic": { + In: deepUnknownTestSampleInput{ + T: tftypes.Tuple{ + ElementTypes: []tftypes.Type{ + tftypes.DynamicPseudoType, + }, + }, + V: tftypes.NewValue( + tftypes.Tuple{ + ElementTypes: []tftypes.Type{ + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "name": tftypes.String, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "name": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "name": tftypes.NewValue(tftypes.String, "test"), + }, + ), + }, + ), + }, + Out: tftypes.NewValue( + tftypes.Tuple{ + ElementTypes: []tftypes.Type{ + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "name": tftypes.String, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "name": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "name": tftypes.NewValue(tftypes.String, "test"), + }, + ), + }, + ), + }, } for n, s := range samples { t.Run(n, func(t *testing.T) { From 786e02bd676f1d3f31cd269d07362868e370420b Mon Sep 17 00:00:00 2001 From: Gavin Williams Date: Wed, 15 Oct 2025 17:06:53 +0100 Subject: [PATCH 2/3] Add an acceptance test to cover the fix --- .../test/acceptance/strategic_patch_test.go | 81 +++++++++++++++++++ .../acceptance/testdata/StrategicPatch/crd.tf | 58 +++++++++++++ .../StrategicPatch/strategic_patch.tf | 20 +++++ 3 files changed, 159 insertions(+) create mode 100644 manifest/test/acceptance/strategic_patch_test.go create mode 100644 manifest/test/acceptance/testdata/StrategicPatch/crd.tf create mode 100644 manifest/test/acceptance/testdata/StrategicPatch/strategic_patch.tf diff --git a/manifest/test/acceptance/strategic_patch_test.go b/manifest/test/acceptance/strategic_patch_test.go new file mode 100644 index 0000000000..9b6c18dc90 --- /dev/null +++ b/manifest/test/acceptance/strategic_patch_test.go @@ -0,0 +1,81 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build acceptance +// +build acceptance + +package acceptance + +import ( + "context" + "fmt" + "strings" + "testing" + "time" + + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/terraform-provider-kubernetes/manifest/provider" +) + +func TestKubernetesManifest_StrategicPatch(t *testing.T) { + ctx := context.Background() + + reattachInfo, err := provider.ServeTest(ctx, hclog.Default(), t) + if err != nil { + t.Errorf("Failed to create provider instance: %q", err) + } + + kind := "Strategic" + plural := "strategics" + group := "terraform.io" + version := "v1" + groupVersion := group + "/" + version + crd := fmt.Sprintf("%s.%s", plural, group) + + name := strings.ToLower(randName()) + namespace := "default" + + tfvars := TFVARS{ + "name": name, + "namespace": namespace, + "kind": kind, + "plural": plural, + "group": group, + "group_version": groupVersion, + "cr_version": version, + } + + step1 := tfhelper.RequireNewWorkingDir(ctx, t) + step1.SetReattachInfo(ctx, reattachInfo) + defer func() { + step1.Destroy(ctx) + step1.Close() + k8shelper.AssertResourceDoesNotExist(t, "apiextensions.k8s.io/v1", "customresourcedefinitions", crd) + }() + + crdTfConfig := loadTerraformConfig(t, "StrategicPatch/crd.tf", tfvars) + step1.SetConfig(ctx, string(crdTfConfig)) + step1.Init(ctx) + step1.Apply(ctx) + k8shelper.AssertResourceExists(t, "apiextensions.k8s.io/v1", "customresourcedefinitions", crd) + + // wait for API to finish ingesting the CRD + time.Sleep(5 * time.Second) //lintignore:R018 + + reattachInfo2, err := provider.ServeTest(ctx, hclog.Default(), t) + if err != nil { + t.Errorf("Failed to create additional provider instance: %q", err) + } + step2 := tfhelper.RequireNewWorkingDir(ctx, t) + step2.SetReattachInfo(ctx, reattachInfo2) + defer func() { + step2.Destroy(ctx) + step2.Close() + k8shelper.AssertResourceDoesNotExist(t, groupVersion, kind, name) + }() + + tfconfig := loadTerraformConfig(t, "StrategicPatch/strategic_patch.tf", tfvars) + step2.SetConfig(ctx, string(tfconfig)) + step2.Init(ctx) + step2.Apply(ctx) +} diff --git a/manifest/test/acceptance/testdata/StrategicPatch/crd.tf b/manifest/test/acceptance/testdata/StrategicPatch/crd.tf new file mode 100644 index 0000000000..356f382190 --- /dev/null +++ b/manifest/test/acceptance/testdata/StrategicPatch/crd.tf @@ -0,0 +1,58 @@ +resource "kubernetes_manifest" "crd" { + manifest = { + apiVersion = "apiextensions.k8s.io/v1" + kind = "CustomResourceDefinition" + metadata = { + name = "${var.plural}.${var.group}" + } + spec = { + group = "${var.group}" + names = { + kind = "${var.kind}" + plural = "${var.plural}" + } + scope = "Namespaced" + versions = [ + { + name = "${var.cr_version}" + served = true + storage = true + schema = { + openAPIV3Schema = { + type = "object" + properties = { + spec = { + type = "object" + properties = { + patchStrategicMerge = { + type = "object" + properties = { + containers = { + type = "array" + "x-kubernetes-list-type" = "map" + "x-kubernetes-list-map-keys" = ["name"] + items = { + type = "object" + required = ["name"] + properties = { + name = { + type = "string" + } + image = { + type = "string" + } + } + } + } + } + } + } + } + } + } + } + }, + ] + } + } +} diff --git a/manifest/test/acceptance/testdata/StrategicPatch/strategic_patch.tf b/manifest/test/acceptance/testdata/StrategicPatch/strategic_patch.tf new file mode 100644 index 0000000000..8dd8e48a50 --- /dev/null +++ b/manifest/test/acceptance/testdata/StrategicPatch/strategic_patch.tf @@ -0,0 +1,20 @@ +resource "kubernetes_manifest" "test" { + manifest = { + apiVersion = "${var.group_version}" + kind = "${var.kind}" + metadata = { + name = "${var.name}" + namespace = "${var.namespace}" + } + spec = { + patchStrategicMerge = { + containers = [ + { + name = "nginx" + image = "nginx:latest" + }, + ] + } + } + } +} \ No newline at end of file From 78dd4c97b169f8d64c3826b9277f080c893ac4bc Mon Sep 17 00:00:00 2001 From: Gavin Williams Date: Wed, 15 Oct 2025 17:11:40 +0100 Subject: [PATCH 3/3] Add changelog file --- .changelog/2800.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/2800.txt diff --git a/.changelog/2800.txt b/.changelog/2800.txt new file mode 100644 index 0000000000..d32da3bb00 --- /dev/null +++ b/.changelog/2800.txt @@ -0,0 +1,3 @@ +```release-note:bug +`resource/kubernetes_manifest`: fix an issue with the provider panic'ing when working with complex manifests +```