From 97cd5be88f575f25202723c1497ea7846eeebdf3 Mon Sep 17 00:00:00 2001 From: Ramon Quitales Date: Wed, 4 Dec 2024 10:55:22 -0800 Subject: [PATCH 1/7] Also normalize `x-kubernetes-*` fields for outputs Make replacement map DRY --- provider/pkg/provider/provider.go | 69 ++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/provider/pkg/provider/provider.go b/provider/pkg/provider/provider.go index 7ff93b3766..95856de3c1 100644 --- a/provider/pkg/provider/provider.go +++ b/provider/pkg/provider/provider.go @@ -2925,29 +2925,42 @@ func mapReplStripComputed(v resource.PropertyValue) (any, bool) { return nil, false } -// mapReplUnderscoreToDash is needed to work around cases where SDKs don't allow dashes in variable names, and so the -// parameter is renamed with an underscore during schema generation. This function normalizes those keys to the format -// expected by the cluster. +// underscoreToDashMap holds the mappings between underscore and dash keys. +var underscoreToDashMap = map[string]string{ + "x_kubernetes_embedded_resource": "x-kubernetes-embedded-resource", + "x_kubernetes_int_or_string": "x-kubernetes-int-or-string", + "x_kubernetes_list_map_keys": "x-kubernetes-list-map-keys", + "x_kubernetes_list_type": "x-kubernetes-list-type", + "x_kubernetes_map_type": "x-kubernetes-map-type", + "x_kubernetes_preserve_unknown_fields": "x-kubernetes-preserve-unknown-fields", + "x_kubernetes_validations": "x-kubernetes-validations", +} + +// dashedToUnderscoreMap holds the reverse mappings between dash and underscore keys. This +// is a precomputed map based on underscoreToDashMap at runtime to avoid duplicating +// code, or extra passes over the map. +var dashToUnderscoreMap map[string]string = func() map[string]string { + dashToUnderscoreMap := make(map[string]string, len(underscoreToDashMap)) + for k, v := range underscoreToDashMap { + dashToUnderscoreMap[v] = k + } + return dashToUnderscoreMap +}() + +// mapReplUnderscoreToDash denormalizes keys by replacing underscores with dashes. func mapReplUnderscoreToDash(v string) (string, bool) { - switch v { - case "x_kubernetes_embedded_resource": - return "x-kubernetes-embedded-resource", true - case "x_kubernetes_int_or_string": - return "x-kubernetes-int-or-string", true - case "x_kubernetes_list_map_keys": - return "x-kubernetes-list-map-keys", true - case "x_kubernetes_list_type": - return "x-kubernetes-list-type", true - case "x_kubernetes_map_type": - return "x-kubernetes-map-type", true - case "x_kubernetes_preserve_unknown_fields": - return "x-kubernetes-preserve-unknown-fields", true - case "x_kubernetes_validations": - return "x-kubernetes-validations", true - } - return "", false + val, ok := underscoreToDashMap[v] + return val, ok +} + +// mapReplDashToUnderscore normalizes keys by replacing dashes with underscores. +func mapReplDashToUnderscore(v string) (resource.PropertyKey, bool) { + val, ok := dashToUnderscoreMap[v] + return resource.PropertyKey(val), ok } +// propMapToUnstructured converts a resource.PropertyMap to an *unstructured.Unstructured; and applies field name denormalization +// and secret stripping. func propMapToUnstructured(pm resource.PropertyMap) *unstructured.Unstructured { return &unstructured.Unstructured{Object: pm.MapRepl(mapReplUnderscoreToDash, mapReplStripSecrets)} } @@ -2962,11 +2975,17 @@ func initialAPIVersion(state resource.PropertyMap, oldInputs *unstructured.Unstr return oldInputs.GetAPIVersion() } +// checkpointObject generates a checkpointed PropertyMap from the live and input Kubernetes objects. +// It normalizes `x-kubernetes-*` fields to their underscored equivalents, handles secret data annotations, +// processes `stringData` for secret kinds by marking corresponding `data` fields as secrets, +// and includes metadata such as the initial API version and field manager. func checkpointObject(inputs, live *unstructured.Unstructured, fromInputs resource.PropertyMap, initialAPIVersion, fieldManager string, ) resource.PropertyMap { - object := resource.NewPropertyMapFromMap(live.Object) - inputsPM := resource.NewPropertyMapFromMap(inputs.Object) + // When checkpointing the live object, we need to ensure we normalize any `x-kubernetes-*` fields to their + // underscored versions so they can be correctly diffed, and deseriazlied to their typed SDK equivalents. + object := resource.NewPropertyMapFromMapRepl(live.Object, mapReplDashToUnderscore, nil) + inputsPM := resource.NewPropertyMapFromMapRepl(inputs.Object, mapReplDashToUnderscore, nil) annotateSecrets(object, fromInputs) annotateSecrets(inputsPM, fromInputs) @@ -2998,10 +3017,14 @@ func checkpointObject(inputs, live *unstructured.Unstructured, fromInputs resour return object } +// parseCheckpointObject parses the given resource.PropertyMap, stripping sensitive information and normalizing field names. +// It returns two unstructured.Unstructured objects: oldInputs containing the input properties and live containing the live state. func parseCheckpointObject(obj resource.PropertyMap) (oldInputs, live *unstructured.Unstructured) { // Since we are converting everything to unstructured's, we need to strip out any secretness that // may nested deep within the object. - pm := obj.MapRepl(nil, mapReplStripSecrets) + // Note: we also handle conversion of underscored `x_kubernetes_*` fields to their respective dashed + // versions here. + pm := obj.MapRepl(mapReplUnderscoreToDash, mapReplStripSecrets) // // NOTE: Inputs are now stored in `__inputs` to allow output properties to work. The inputs and From 07112eb5ecf51d59323869439d889f0e83f95b42 Mon Sep 17 00:00:00 2001 From: Ramon Quitales Date: Wed, 4 Dec 2024 15:23:41 -0800 Subject: [PATCH 2/7] Add unit test to ensure checkpointed object has dashes removed --- provider/pkg/provider/provider_test.go | 62 ++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/provider/pkg/provider/provider_test.go b/provider/pkg/provider/provider_test.go index 21a95cbbea..58896e0128 100644 --- a/provider/pkg/provider/provider_test.go +++ b/provider/pkg/provider/provider_test.go @@ -39,6 +39,7 @@ var ( "qux": map[string]any{ "xuq": "oof", }, + "x_kubernetes_preserve_unknown_fields": true, } objLive = map[string]any{ initialAPIVersionKey: "", @@ -48,6 +49,26 @@ var ( "xuq": map[string]any{ "qux": "foo", }, + "x_kubernetes_preserve_unknown_fields": true, + } + + objInputsWithDash = map[string]any{ + "foo": "bar", + "baz": float64(1234), + "qux": map[string]any{ + "xuq": "oof", + }, + "x-kubernetes-preserve-unknown-fields": true, + } + objLiveWithDash = map[string]any{ + initialAPIVersionKey: "", + fieldManagerKey: "", + "oof": "bar", + "zab": float64(4321), + "xuq": map[string]any{ + "qux": "foo", + }, + "x-kubernetes-preserve-unknown-fields": true, } ) @@ -67,8 +88,8 @@ func TestParseNewCheckpointObject(t *testing.T) { old["__inputs"] = resource.NewObjectProperty(resource.NewPropertyMapFromMap(objInputs)) oldInputs, live := parseCheckpointObject(old) - assert.Equal(t, objInputs, oldInputs.Object) - assert.Equal(t, objLive, live.Object) + assert.Equal(t, objInputsWithDash, oldInputs.Object) + assert.Equal(t, objLiveWithDash, live.Object) } func TestCheckpointObject(t *testing.T) { @@ -118,13 +139,46 @@ func TestCheckpointSecretObject(t *testing.T) { assert.True(t, oldInputsVal["data"].ContainsSecrets()) } +// Ensure that well-known x-kubernetes-* fields are normazlied to x_kubernetes_* +// in the checkpoint object. +func TestCheckpointXKubernetesFields(t *testing.T) { + objInputWithDash := map[string]any{ + "kind": "fakekind", + "spec": map[string]any{ + "x-kubernetes-preserve-unknown-fields": "true", + }, + } + objLiveWithDash := map[string]any{ + initialAPIVersionKey: "", + fieldManagerKey: "", + "kind": "fakekind", + "spec": map[string]any{ + "x-kubernetes-preserve-unknown-fields": "true", + }, + } + + inputs := &unstructured.Unstructured{Object: objInputWithDash} + live := &unstructured.Unstructured{Object: objLiveWithDash} + + obj := checkpointObject(inputs, live, nil, "", "") + assert.NotNil(t, obj) + + // Ensure we do not have the original x-kubernetes-* fields in the checkpoint objects. + assert.NotContains(t, obj.Mappable()["spec"], "x-kubernetes-preserve-unknown-fields") + assert.NotContains(t, obj["__inputs"].Mappable(), "x-kubernetes-preserve-unknown-fields") + + // Ensure we have the normalized x_kubernetes_* fields in the checkpoint objects. + assert.Contains(t, obj.Mappable()["spec"], "x_kubernetes_preserve_unknown_fields") + assert.Contains(t, obj["__inputs"].Mappable().(map[string]any)["spec"], "x_kubernetes_preserve_unknown_fields") +} + func TestRoundtripCheckpointObject(t *testing.T) { old := resource.NewPropertyMapFromMap(objLive) old["__inputs"] = resource.NewObjectProperty(resource.NewPropertyMapFromMap(objInputs)) oldInputs, oldLive := parseCheckpointObject(old) - assert.Equal(t, objInputs, oldInputs.Object) - assert.Equal(t, objLive, oldLive.Object) + assert.Equal(t, objInputsWithDash, oldInputs.Object) + assert.Equal(t, objLiveWithDash, oldLive.Object) obj := checkpointObject(oldInputs, oldLive, nil, "", "") assert.Equal(t, old, obj) From 7e2e6b702704034921d28f320451dfcfe11ec727 Mon Sep 17 00:00:00 2001 From: Ramon Quitales Date: Wed, 4 Dec 2024 15:26:27 -0800 Subject: [PATCH 3/7] Fix naked return linting in parseCheckpointObject --- provider/pkg/provider/provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provider/pkg/provider/provider.go b/provider/pkg/provider/provider.go index 95856de3c1..cf74ef340e 100644 --- a/provider/pkg/provider/provider.go +++ b/provider/pkg/provider/provider.go @@ -3049,7 +3049,7 @@ func parseCheckpointObject(obj resource.PropertyMap) (oldInputs, live *unstructu oldInputs = &unstructured.Unstructured{Object: inputs.(map[string]any)} live = &unstructured.Unstructured{Object: liveMap.(map[string]any)} - return + return oldInputs, live } // partialError creates an error for resources that did not complete an operation in progress. From 18124f32bed96ea3851d26dfead34c2f44a46b56 Mon Sep 17 00:00:00 2001 From: Ramon Quitales Date: Wed, 4 Dec 2024 16:34:25 -0800 Subject: [PATCH 4/7] Expand nodejs e2e tests to ensure proper handling of x-kuebrnetes fields --- tests/sdk/nodejs/crds/step1/index.ts | 5 ++++- tests/sdk/nodejs/crds/step2/index.ts | 4 +++- tests/sdk/nodejs/nodejs_test.go | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/tests/sdk/nodejs/crds/step1/index.ts b/tests/sdk/nodejs/crds/step1/index.ts index 6c47444067..581e87c3db 100644 --- a/tests/sdk/nodejs/crds/step1/index.ts +++ b/tests/sdk/nodejs/crds/step1/index.ts @@ -20,7 +20,7 @@ const namespace = new k8s.core.v1.Namespace("test-namespace"); // Create a CustomResourceDefinition and a CustomResource. // -new k8s.apiextensions.v1.CustomResourceDefinition("foobar", { +const crd = new k8s.apiextensions.v1.CustomResourceDefinition("foobar", { metadata: { name: "foobars.stable.example.com" }, spec: { group: "stable.example.com", @@ -32,6 +32,7 @@ new k8s.apiextensions.v1.CustomResourceDefinition("foobar", { schema: { openAPIV3Schema: { type: "object", + x_kubernetes_preserve_unknown_fields: true, properties: { spec: { type: "object", @@ -59,6 +60,8 @@ new k8s.apiextensions.v1.CustomResourceDefinition("foobar", { } }); +export const preserveUnknownFields = crd.spec.versions[0].schema.openAPIV3Schema.x_kubernetes_preserve_unknown_fields + new k8s.apiextensions.CustomResource( "my-new-foobar-object", { diff --git a/tests/sdk/nodejs/crds/step2/index.ts b/tests/sdk/nodejs/crds/step2/index.ts index 424c6eef44..f5bac49903 100644 --- a/tests/sdk/nodejs/crds/step2/index.ts +++ b/tests/sdk/nodejs/crds/step2/index.ts @@ -21,7 +21,7 @@ const namespace = new k8s.core.v1.Namespace("test-namespace"); // Create a CustomResourceDefinition and a CustomResource. // -new k8s.apiextensions.v1.CustomResourceDefinition("foobar", { +const crd = new k8s.apiextensions.v1.CustomResourceDefinition("foobar", { metadata: { name: "foobars.stable.example.com" }, spec: { group: "stable.example.com", @@ -60,6 +60,8 @@ new k8s.apiextensions.v1.CustomResourceDefinition("foobar", { } }); +export const preserveUnknownFields = crd.spec.versions[0].schema.openAPIV3Schema.x_kubernetes_preserve_unknown_fields + new k8s.apiextensions.CustomResource( "my-new-foobar-object", { diff --git a/tests/sdk/nodejs/nodejs_test.go b/tests/sdk/nodejs/nodejs_test.go index 683f4ac6cb..578bb28d1e 100644 --- a/tests/sdk/nodejs/nodejs_test.go +++ b/tests/sdk/nodejs/nodejs_test.go @@ -414,6 +414,7 @@ func TestCRDs(t *testing.T) { ct1 := stackInfo.Deployment.Resources[2] provRes := stackInfo.Deployment.Resources[3] stackRes := stackInfo.Deployment.Resources[4] + outputs := stackInfo.Outputs assert.Equal(t, resource.RootStackType, stackRes.URN.Type()) assert.True(t, providers.IsProviderType(provRes.URN.Type())) @@ -426,6 +427,15 @@ func TestCRDs(t *testing.T) { assert.Equal(t, "foobar", string(crd.URN.Name())) assert.Equal(t, "my-new-foobar-object", string(ct1.URN.Name())) + + // Assert that we can reference the x_kubernetes_preserve_unknown_fields field as we should correctly normalize the live object. + assert.NotNil(t, outputs["preserveUnknownFields"], "preserveUnknownFields should be present") + assert.True(t, outputs["preserveUnknownFields"].(bool), "preserveUnknownFields should be true") + + // Verify with kubectl that the CRD has `x-kubernetes-*` fields set correctly. + output, err := tests.Kubectl("get crd foobars.stable.example.com -o json") + require.NoError(t, err) + assert.Contains(t, string(output), `"x-kubernetes-preserve-unknown-fields": true`) }, EditDirs: []integration.EditDir{ { @@ -433,12 +443,21 @@ func TestCRDs(t *testing.T) { Additive: true, ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) { assert.NotNil(t, stackInfo.Deployment) + outputs := stackInfo.Outputs // // Assert that the CR was gotten. // ct1ref := tests.SearchResourcesByName(stackInfo, "", "kubernetes:stable.example.com/v1:FooBar", "my-new-foobar-object-ref") assert.NotNil(t, ct1ref) + + // Assert that the x_kubernetes_preserve_unknown_fields field is now nil, as we remove this in step 2. + assert.Nil(t, outputs["preserveUnknownFields"], "preserveUnknownFields should be present") + + // Verify with kubectl that the CRD does not have `x-kubernetes-*` fields. + output, err := tests.Kubectl("get crd foobars.stable.example.com -o json") + require.NoError(t, err) + assert.NotContains(t, string(output), `"x-kubernetes-preserve-unknown-fields": true`) }, }, { From 5d359f842f22d80dfc91a19eb8b594fc019601c2 Mon Sep 17 00:00:00 2001 From: Ramon Quitales Date: Wed, 4 Dec 2024 16:35:11 -0800 Subject: [PATCH 5/7] Add java e2e tests to ensure we can create CRDs --- tests/sdk/java/crd_java_test.go | 65 +++++++++++ .../java/testdata/crd-java/step1/Pulumi.yaml | 3 + .../sdk/java/testdata/crd-java/step1/pom.xml | 110 ++++++++++++++++++ .../step1/src/main/java/myproject/App.java | 60 ++++++++++ .../step2/src/main/java/myproject/App.java | 64 ++++++++++ .../step3/src/main/java/myproject/App.java | 63 ++++++++++ 6 files changed, 365 insertions(+) create mode 100644 tests/sdk/java/crd_java_test.go create mode 100644 tests/sdk/java/testdata/crd-java/step1/Pulumi.yaml create mode 100644 tests/sdk/java/testdata/crd-java/step1/pom.xml create mode 100644 tests/sdk/java/testdata/crd-java/step1/src/main/java/myproject/App.java create mode 100644 tests/sdk/java/testdata/crd-java/step2/src/main/java/myproject/App.java create mode 100644 tests/sdk/java/testdata/crd-java/step3/src/main/java/myproject/App.java diff --git a/tests/sdk/java/crd_java_test.go b/tests/sdk/java/crd_java_test.go new file mode 100644 index 0000000000..a328831185 --- /dev/null +++ b/tests/sdk/java/crd_java_test.go @@ -0,0 +1,65 @@ +// Copyright 2016-2024, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "testing" + + "github.com/pulumi/providertest/pulumitest" + "github.com/pulumi/pulumi-kubernetes/tests/v4" + "github.com/pulumi/pulumi/sdk/v3/go/auto/optup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestJavaCanCreateCRD tests that we can create a CRD using the Java SDK, and that `x-kubernetes-*` fields are +// correctly serialized. +func TestJavaCanCreateCRD(t *testing.T) { + // Step 1 creates a CRD with `x-kubernetes-preserve-unknown-fields` set to true. + test := pulumitest.NewPulumiTest(t, "testdata/crd-java/step1") + t.Logf("into %s", test.Source()) + t.Cleanup(func() { + test.Destroy() + }) + test.Preview() + test.Up() + + // Step 2 adds a pulumi CRD get operation and ensures we can read its URN properly. + test.UpdateSource("testdata/crd-java/step2") + test.Preview() + test.Up() + up := test.Up(optup.ExpectNoChanges()) + + urn, ok := up.Outputs["urn"] + require.True(t, ok) + require.NotNil(t, urn) + require.Equal(t, urn.Value, "urn:pulumi:test::crd_java::kubernetes:apiextensions.k8s.io/v1:CustomResourceDefinition::getCRDUrn") + + // Verify with kubectl that the CRD has `x-kubernetes-*` fields set correctly. + output, err := tests.Kubectl("get crd javacrds.example.com -o json") + require.NoError(t, err) + assert.Contains(t, string(output), `"x-kubernetes-preserve-unknown-fields": true`) + + // Step 3 removes the `x-kubernetes-preserve-unknown-fields` field and ensures that the CRD is updated. + test.UpdateSource("testdata/crd-java/step3") + test.Preview() + test.Up() + up = test.Up(optup.ExpectNoChanges()) + + // Verify with kubectl that the CRD no longer has `x-kubernetes-*` fields set. + output, err = tests.Kubectl("get crd javacrds.example.com -o json") + require.NoError(t, err) + assert.NotContains(t, string(output), `"x-kubernetes-preserve-unknown-fields": true`) +} diff --git a/tests/sdk/java/testdata/crd-java/step1/Pulumi.yaml b/tests/sdk/java/testdata/crd-java/step1/Pulumi.yaml new file mode 100644 index 0000000000..0596bf4681 --- /dev/null +++ b/tests/sdk/java/testdata/crd-java/step1/Pulumi.yaml @@ -0,0 +1,3 @@ +name: crd_java +description: A minimal Kubernetes Java Pulumi program to test the creation of CRDs +runtime: java diff --git a/tests/sdk/java/testdata/crd-java/step1/pom.xml b/tests/sdk/java/testdata/crd-java/step1/pom.xml new file mode 100644 index 0000000000..4d61760a49 --- /dev/null +++ b/tests/sdk/java/testdata/crd-java/step1/pom.xml @@ -0,0 +1,110 @@ + + + 4.0.0 + + com.pulumi + java + 1.0-SNAPSHOT + + + UTF-8 + 11 + 11 + 11 + myproject.App + + + + + + com.pulumi + pulumi + (,1.0] + + + com.pulumi + kubernetes + [,5.0) + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.2 + + + + true + ${mainClass} + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.3.0 + + + attach-javadocs + + jar + + + + + + -Xdoclint:none + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.3.0 + + + + true + ${mainClass} + + + + jar-with-dependencies + + + + + make-my-jar-with-dependencies + package + + single + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.0.0 + + ${mainClass} + ${mainArgs} + + + + org.apache.maven.plugins + maven-wrapper-plugin + 3.1.0 + + 3.8.5 + + + + + \ No newline at end of file diff --git a/tests/sdk/java/testdata/crd-java/step1/src/main/java/myproject/App.java b/tests/sdk/java/testdata/crd-java/step1/src/main/java/myproject/App.java new file mode 100644 index 0000000000..7ee75e1860 --- /dev/null +++ b/tests/sdk/java/testdata/crd-java/step1/src/main/java/myproject/App.java @@ -0,0 +1,60 @@ +package myproject; + +import com.pulumi.Context; +import com.pulumi.Pulumi; + +import com.pulumi.kubernetes.apiextensions.v1.CustomResourceDefinition; +import com.pulumi.kubernetes.apiextensions.v1.CustomResourceDefinitionArgs; +import com.pulumi.kubernetes.apiextensions.v1.inputs.CustomResourceDefinitionNamesArgs; +import com.pulumi.kubernetes.apiextensions.v1.inputs.CustomResourceDefinitionVersionArgs; +import com.pulumi.kubernetes.apiextensions.v1.inputs.CustomResourceDefinitionSpecArgs; +import com.pulumi.kubernetes.apiextensions.v1.inputs.CustomResourceValidationArgs; +import com.pulumi.kubernetes.meta.v1.inputs.ObjectMetaArgs; +import com.pulumi.kubernetes.apiextensions.v1.inputs.JSONSchemaPropsArgs; +import java.util.HashMap; + +public class App { + public static void main(String[] args) { + Pulumi.run(App::stack); + } + + public static void stack(Context ctx) { + var metadata = ObjectMetaArgs.builder() + .name("javacrds.example.com") + .build(); + + var spec = CustomResourceDefinitionSpecArgs.builder() + .group("example.com") + .scope("Namespaced") + .names(CustomResourceDefinitionNamesArgs.builder() + .kind("JavaCRD") + .plural("javacrds") + .singular("javacrd") + .shortNames("jcrd") + .build()) + .versions(CustomResourceDefinitionVersionArgs.builder() + .name("v1") + .served(true) + .storage(true) + .schema(CustomResourceValidationArgs.builder() + .openAPIV3Schema(JSONSchemaPropsArgs.builder() + .type("object") + .properties(new HashMap() { + { + put("key", JSONSchemaPropsArgs.builder() + .type("object") + .x_kubernetes_preserve_unknown_fields(true) + .build()); + } + }).build()) + .build()) + .build()) + .build(); + + new CustomResourceDefinition("crd", + CustomResourceDefinitionArgs.builder() + .metadata(metadata) + .spec(spec) + .build()); + } +} diff --git a/tests/sdk/java/testdata/crd-java/step2/src/main/java/myproject/App.java b/tests/sdk/java/testdata/crd-java/step2/src/main/java/myproject/App.java new file mode 100644 index 0000000000..9fd308285a --- /dev/null +++ b/tests/sdk/java/testdata/crd-java/step2/src/main/java/myproject/App.java @@ -0,0 +1,64 @@ +package myproject; + +import com.pulumi.Context; +import com.pulumi.Pulumi; + +import com.pulumi.kubernetes.apiextensions.v1.CustomResourceDefinition; +import com.pulumi.kubernetes.apiextensions.v1.CustomResourceDefinitionArgs; +import com.pulumi.kubernetes.apiextensions.v1.inputs.CustomResourceDefinitionNamesArgs; +import com.pulumi.kubernetes.apiextensions.v1.inputs.CustomResourceDefinitionVersionArgs; +import com.pulumi.kubernetes.apiextensions.v1.inputs.CustomResourceDefinitionSpecArgs; +import com.pulumi.kubernetes.apiextensions.v1.inputs.CustomResourceValidationArgs; +import com.pulumi.kubernetes.meta.v1.inputs.ObjectMetaArgs; +import com.pulumi.core.Output; +import com.pulumi.kubernetes.apiextensions.v1.inputs.JSONSchemaPropsArgs; +import java.util.HashMap; + +public class App { + public static void main(String[] args) { + Pulumi.run(App::stack); + } + + public static void stack(Context ctx) { + var metadata = ObjectMetaArgs.builder() + .name("javacrds.example.com") + .build(); + + var spec = CustomResourceDefinitionSpecArgs.builder() + .group("example.com") + .scope("Namespaced") + .names(CustomResourceDefinitionNamesArgs.builder() + .kind("JavaCRD") + .plural("javacrds") + .singular("javacrd") + .shortNames("jcrd") + .build()) + .versions(CustomResourceDefinitionVersionArgs.builder() + .name("v1") + .served(true) + .storage(true) + .schema(CustomResourceValidationArgs.builder() + .openAPIV3Schema(JSONSchemaPropsArgs.builder() + .type("object") + .properties(new HashMap() { + { + put("key", JSONSchemaPropsArgs.builder() + .type("object") + .x_kubernetes_preserve_unknown_fields(true) + .build()); + } + }).build()) + .build()) + .build()) + .build(); + + new CustomResourceDefinition("crd", + CustomResourceDefinitionArgs.builder() + .metadata(metadata) + .spec(spec) + .build()); + + var crdGet = CustomResourceDefinition.get("getCRDUrn", Output.of("javacrds.example.com"), null); + ctx.export("urn", crdGet.urn()); + } +} diff --git a/tests/sdk/java/testdata/crd-java/step3/src/main/java/myproject/App.java b/tests/sdk/java/testdata/crd-java/step3/src/main/java/myproject/App.java new file mode 100644 index 0000000000..3233e30367 --- /dev/null +++ b/tests/sdk/java/testdata/crd-java/step3/src/main/java/myproject/App.java @@ -0,0 +1,63 @@ +package myproject; + +import com.pulumi.Context; +import com.pulumi.Pulumi; + +import com.pulumi.kubernetes.apiextensions.v1.CustomResourceDefinition; +import com.pulumi.kubernetes.apiextensions.v1.CustomResourceDefinitionArgs; +import com.pulumi.kubernetes.apiextensions.v1.inputs.CustomResourceDefinitionNamesArgs; +import com.pulumi.kubernetes.apiextensions.v1.inputs.CustomResourceDefinitionVersionArgs; +import com.pulumi.kubernetes.apiextensions.v1.inputs.CustomResourceDefinitionSpecArgs; +import com.pulumi.kubernetes.apiextensions.v1.inputs.CustomResourceValidationArgs; +import com.pulumi.kubernetes.meta.v1.inputs.ObjectMetaArgs; +import com.pulumi.core.Output; +import com.pulumi.kubernetes.apiextensions.v1.inputs.JSONSchemaPropsArgs; +import java.util.HashMap; + +public class App { + public static void main(String[] args) { + Pulumi.run(App::stack); + } + + public static void stack(Context ctx) { + var metadata = ObjectMetaArgs.builder() + .name("javacrds.example.com") + .build(); + + var spec = CustomResourceDefinitionSpecArgs.builder() + .group("example.com") + .scope("Namespaced") + .names(CustomResourceDefinitionNamesArgs.builder() + .kind("JavaCRD") + .plural("javacrds") + .singular("javacrd") + .shortNames("jcrd") + .build()) + .versions(CustomResourceDefinitionVersionArgs.builder() + .name("v1") + .served(true) + .storage(true) + .schema(CustomResourceValidationArgs.builder() + .openAPIV3Schema(JSONSchemaPropsArgs.builder() + .type("object") + .properties(new HashMap() { + { + put("key", JSONSchemaPropsArgs.builder() + .type("object") + .build()); + } + }).build()) + .build()) + .build()) + .build(); + + new CustomResourceDefinition("crd", + CustomResourceDefinitionArgs.builder() + .metadata(metadata) + .spec(spec) + .build()); + + var crdGet = CustomResourceDefinition.get("getCRDUrn", Output.of("javacrds.example.com"), null); + ctx.export("urn", crdGet.urn()); + } +} From 61e96bc8288c7aa06a9b2ec64da479d6f749fc45 Mon Sep 17 00:00:00 2001 From: Ramon Quitales Date: Wed, 4 Dec 2024 16:45:11 -0800 Subject: [PATCH 6/7] Fix assertions in provider tests --- provider/pkg/provider/provider_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/provider/pkg/provider/provider_test.go b/provider/pkg/provider/provider_test.go index 58896e0128..0c114d72b2 100644 --- a/provider/pkg/provider/provider_test.go +++ b/provider/pkg/provider/provider_test.go @@ -79,8 +79,8 @@ func TestParseOldCheckpointObject(t *testing.T) { }) oldInputs, live := parseCheckpointObject(old) - assert.Equal(t, objInputs, oldInputs.Object) - assert.Equal(t, objLive, live.Object) + assert.Equal(t, objInputsWithDash, oldInputs.Object) + assert.Equal(t, objLiveWithDash, live.Object) } func TestParseNewCheckpointObject(t *testing.T) { From 15631b85ae6cf44bd1ca0438fe4d500e2a81364a Mon Sep 17 00:00:00 2001 From: Ramon Quitales Date: Wed, 4 Dec 2024 16:45:35 -0800 Subject: [PATCH 7/7] Add changelog entry --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1dda71857..855456bde6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ - JSONPath expressions used with the `pulumi.com/waitFor` annotation will no longer hang indefinitely if they match non-primitive fields. (https://github.com/pulumi/pulumi-kubernetes/issues/3345) +- [java] CRDs that contain any `x-kubernetes-*` fields can now be succesfully created and managed by Pulumi. + (https://github.com/pulumi/pulumi-kubernetes/issues/3325) + ## 4.18.3 (October 31, 2024) ### Fixed