diff --git a/CHANGELOG.md b/CHANGELOG.md index 46799ca5f7..b75cf90b9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,11 @@ ### Major changes -- None +- BREAKING: Change the recently added `transformations` callback in Python to match JavaScript API (https://github.com/pulumi/pulumi-kubernetes/pull/575) ### Improvements -- None +- Enable configuring `ResourceOptions` via `transformations` (https://github.com/pulumi/pulumi-kubernetes/pull/575). ### Bug fixes diff --git a/pkg/gen/nodejs-templates/yaml.ts.mustache b/pkg/gen/nodejs-templates/yaml.ts.mustache index c4c2e07e21..a39681d297 100644 --- a/pkg/gen/nodejs-templates/yaml.ts.mustache +++ b/pkg/gen/nodejs-templates/yaml.ts.mustache @@ -23,7 +23,7 @@ import * as outputApi from "../types/output"; * A set of transformations to apply to Kubernetes resource definitions before registering * with engine. */ - transformations?: ((o: any) => void)[]; + transformations?: ((o: any, opts: pulumi.CustomResourceOptions) => void)[]; } export interface ConfigFileOpts { @@ -34,7 +34,7 @@ import * as outputApi from "../types/output"; * A set of transformations to apply to Kubernetes resource definitions before registering * with engine. */ - transformations?: ((o: any) => void)[]; + transformations?: ((o: any, opts: pulumi.CustomResourceOptions) => void)[]; } export interface ConfigOpts { @@ -45,7 +45,7 @@ import * as outputApi from "../types/output"; * A set of transformations to apply to Kubernetes resource definitions before registering * with engine. */ - transformations?: ((o: any) => void)[]; + transformations?: ((o: any, opts: pulumi.CustomResourceOptions) => void)[]; } /** @ignore */ export function parse( @@ -233,15 +233,18 @@ import * as outputApi from "../types/output"; } /** @ignore */ function parseYamlObject( - obj: any, transformations?: ((o: any) => void)[], opts?: pulumi.CustomResourceOptions, + obj: any, transformations?: ((o: any, opts: pulumi.CustomResourceOptions) => void)[], opts?: pulumi.CustomResourceOptions, ): pulumi.Output<{name: string, resource: pulumi.CustomResource}>[] { if (obj == null || Object.keys(obj).length == 0) { return []; } - // Allow users to change API objects before any validation. + // Create a copy of opts to pass into potentially mutating transforms that will be applied to this resource. + opts = Object.assign({}, opts); + + // Allow users to change API objects before any validation. for (const t of transformations || []) { - t(obj); + t(obj, opts); } if (!("kind" in obj && "apiVersion" in obj)) { diff --git a/pkg/gen/python-templates/yaml.py.mustache b/pkg/gen/python-templates/yaml.py.mustache index 1f10604a12..392fa7e6ad 100644 --- a/pkg/gen/python-templates/yaml.py.mustache +++ b/pkg/gen/python-templates/yaml.py.mustache @@ -2,7 +2,8 @@ # *** Do not edit by hand unless you're certain you know what you are doing! *** import json -from copy import deepcopy +from copy import copy, deepcopy +from inspect import getargspec from typing import Callable, Dict, List, Optional import pulumi.runtime @@ -109,10 +110,19 @@ def _parse_yaml_object(obj, opts: Optional[pulumi.ResourceOptions] = None, if not obj: return [] + # Create a copy of opts to pass into potentially mutating transforms that will be applied to this resource. + if opts is not None: + opts = copy(opts) + else: + opts = {} + # Allow users to change API objects before any validation. if transformations is not None: for t in transformations: - obj = t(obj) + if len(getargspec(t)[0]) == 2: + t(obj, opts) + else: + t(obj) if "kind" not in obj or "apiVersion" not in obj: raise Exception("Kubernetes resources require a kind and apiVersion: {}".format(json.dumps(obj))) diff --git a/sdk/nodejs/helm/v2/helm.ts b/sdk/nodejs/helm/v2/helm.ts index 0e88719bae..5522084eac 100644 --- a/sdk/nodejs/helm/v2/helm.ts +++ b/sdk/nodejs/helm/v2/helm.ts @@ -35,7 +35,7 @@ interface BaseChartOpts { * Optional array of transformations to apply to resources that will be created by this chart prior to * creation. Allows customization of the chart behaviour without directly modifying the chart itself. */ - transformations?: ((o: any) => void)[]; + transformations?: ((o: any, opts: pulumi.CustomResourceOptions) => void)[]; } export interface ChartOpts extends BaseChartOpts { @@ -179,7 +179,7 @@ export class Chart extends yaml.CollectionComponentResource { parseTemplate( yamlStream: string, - transformations: ((o: any) => void)[] | undefined, + transformations: ((o: any, opts: pulumi.CustomResourceOptions) => void)[] | undefined, dependsOn: pulumi.Resource[] ): pulumi.Output<{ [key: string]: pulumi.CustomResource }> { // NOTE: We must manually split the YAML stream because of js-yaml#456. Perusing the diff --git a/sdk/nodejs/yaml/yaml.ts b/sdk/nodejs/yaml/yaml.ts index 9b23326e54..09cf7e91e9 100644 --- a/sdk/nodejs/yaml/yaml.ts +++ b/sdk/nodejs/yaml/yaml.ts @@ -23,7 +23,7 @@ import * as outputApi from "../types/output"; * A set of transformations to apply to Kubernetes resource definitions before registering * with engine. */ - transformations?: ((o: any) => void)[]; + transformations?: ((o: any, opts: pulumi.CustomResourceOptions) => void)[]; } export interface ConfigFileOpts { @@ -34,7 +34,7 @@ import * as outputApi from "../types/output"; * A set of transformations to apply to Kubernetes resource definitions before registering * with engine. */ - transformations?: ((o: any) => void)[]; + transformations?: ((o: any, opts: pulumi.CustomResourceOptions) => void)[]; } export interface ConfigOpts { @@ -45,7 +45,7 @@ import * as outputApi from "../types/output"; * A set of transformations to apply to Kubernetes resource definitions before registering * with engine. */ - transformations?: ((o: any) => void)[]; + transformations?: ((o: any, opts: pulumi.CustomResourceOptions) => void)[]; } /** @ignore */ export function parse( @@ -2203,15 +2203,18 @@ import * as outputApi from "../types/output"; } /** @ignore */ function parseYamlObject( - obj: any, transformations?: ((o: any) => void)[], opts?: pulumi.CustomResourceOptions, + obj: any, transformations?: ((o: any, opts: pulumi.CustomResourceOptions) => void)[], opts?: pulumi.CustomResourceOptions, ): pulumi.Output<{name: string, resource: pulumi.CustomResource}>[] { if (obj == null || Object.keys(obj).length == 0) { return []; } - // Allow users to change API objects before any validation. + // Create a copy of opts to pass into potentially mutating transforms that will be applied to this resource. + opts = Object.assign({}, opts); + + // Allow users to change API objects before any validation. for (const t of transformations || []) { - t(obj); + t(obj, opts); } if (!("kind" in obj && "apiVersion" in obj)) { diff --git a/sdk/python/pulumi_kubernetes/yaml.py b/sdk/python/pulumi_kubernetes/yaml.py index 93f46b5313..2958713d02 100644 --- a/sdk/python/pulumi_kubernetes/yaml.py +++ b/sdk/python/pulumi_kubernetes/yaml.py @@ -2,7 +2,8 @@ # *** Do not edit by hand unless you're certain you know what you are doing! *** import json -from copy import deepcopy +from copy import copy, deepcopy +from inspect import getargspec from typing import Callable, Dict, List, Optional import pulumi.runtime @@ -144,10 +145,19 @@ def _parse_yaml_object(obj, opts: Optional[pulumi.ResourceOptions] = None, if not obj: return [] + # Create a copy of opts to pass into potentially mutating transforms that will be applied to this resource. + if opts is not None: + opts = copy(opts) + else: + opts = {} + # Allow users to change API objects before any validation. if transformations is not None: for t in transformations: - obj = t(obj) + if len(getargspec(t)[0]) == 2: + t(obj, opts) + else: + t(obj) if "kind" not in obj or "apiVersion" not in obj: raise Exception("Kubernetes resources require a kind and apiVersion: {}".format(json.dumps(obj))) diff --git a/tests/examples/examples_test.go b/tests/examples/examples_test.go index c9a2167ea2..b8dc97fd82 100644 --- a/tests/examples/examples_test.go +++ b/tests/examples/examples_test.go @@ -133,6 +133,21 @@ func TestExamples(t *testing.T) { base.With(integration.ProgramTestOptions{ SkipRefresh: true, // Deployment controller changes object out-of-band. Dir: path.Join(cwd, "helm"), + Verbose: true, + ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) { + // Ensure that all `Services` have `status` marked as a `Secret` + for _, res := range stackInfo.Deployment.Resources { + if res.Type == tokens.Type("kubernetes:core/v1:Service") { + spec, has := res.Outputs["status"] + assert.True(t, has) + specMap, is := spec.(map[string]interface{}) + assert.True(t, is) + sigKey, has := specMap[resource.SigKey] + assert.True(t, has) + assert.Equal(t, resource.SecretSig, sigKey) + } + } + }, }), base.With(integration.ProgramTestOptions{ diff --git a/tests/examples/helm/index.ts b/tests/examples/helm/index.ts index ccf5db32c9..032621bbb3 100644 --- a/tests/examples/helm/index.ts +++ b/tests/examples/helm/index.ts @@ -48,6 +48,11 @@ const nginx = new k8s.helm.v2.Chart("simple-nginx", { } else { obj.metadata = {namespace: namespaceName} } + }, + (obj: any, opts: pulumi.CustomResourceOptions) => { + if (obj.kind == "Service" && obj.apiVersion == "v1") { + opts.additionalSecretOutputs = ["status"]; + } } ] }); diff --git a/tests/examples/python/python_test.go b/tests/examples/python/python_test.go index feaa209efd..7831f233ce 100644 --- a/tests/examples/python/python_test.go +++ b/tests/examples/python/python_test.go @@ -138,6 +138,20 @@ func TestYaml(t *testing.T) { // Verify root resource. stackRes := stackInfo.Deployment.Resources[15] assert.Equal(t, resource.RootStackType, stackRes.URN.Type()) + + // TODO[pulumi/pulumi#2782] Testing of secrets blocked on a bug in Python support for secrets. + // // Ensure that all `Pod` have `status` marked as a `Secret` + // for _, res := range stackInfo.Deployment.Resources { + // if res.Type == tokens.Type("kubernetes:core/v1:Pod") { + // spec, has := res.Outputs["apiVersion"] + // assert.True(t, has) + // specMap, is := spec.(map[string]interface{}) + // assert.True(t, is) + // sigKey, has := specMap[resource.SigKey] + // assert.True(t, has) + // assert.Equal(t, resource.SecretSig, sigKey) + // } + // } }, }) integration.ProgramTest(t, &options) diff --git a/tests/examples/python/yaml-test/__main__.py b/tests/examples/python/yaml-test/__main__.py index 15d44e83bc..3e85cfdcf6 100644 --- a/tests/examples/python/yaml-test/__main__.py +++ b/tests/examples/python/yaml-test/__main__.py @@ -23,13 +23,18 @@ def add_namespace(obj): else: obj["metadata"] = {"namespace": ns.metadata["name"]} - return obj - +def secret_status(obj, opts): + if obj["kind"] == "Pod" and obj["apiVersion"] == "v1": + opts.additional_secret_outputs = ["apiVersion"] cf_local = ConfigFile( "yaml-test", "manifest.yaml", - transformations=[add_namespace], + transformations=[ + add_namespace, + # TODO[pulumi/pulumi#2782] Testing of secrets blocked on a bug in Python support for secrets. + # secret_status, + ], ) # Create resources from standard Kubernetes guestbook YAML example in the test namespace.