Skip to content

Commit

Permalink
Allow customizing ResourceOptions in transformations (#575)
Browse files Browse the repository at this point in the history
* Allow customizing ResourceOptions in transformations

Adds the ability for transformations to see the current ResourceOptions and modify it if desired on an per-resource basis.

Fixes #574.

* Add CHANGELOG

* Add Python support

* Update CHANGELOG
  • Loading branch information
lukehoban committed May 30, 2019
1 parent eb593f2 commit 0e3dcef
Show file tree
Hide file tree
Showing 10 changed files with 88 additions and 23 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
15 changes: 9 additions & 6 deletions pkg/gen/nodejs-templates/yaml.ts.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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(
Expand Down Expand Up @@ -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)) {
Expand Down
14 changes: 12 additions & 2 deletions pkg/gen/python-templates/yaml.py.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)))
Expand Down
4 changes: 2 additions & 2 deletions sdk/nodejs/helm/v2/helm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
15 changes: 9 additions & 6 deletions sdk/nodejs/yaml/yaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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(
Expand Down Expand Up @@ -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)) {
Expand Down
14 changes: 12 additions & 2 deletions sdk/python/pulumi_kubernetes/yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)))
Expand Down
15 changes: 15 additions & 0 deletions tests/examples/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
5 changes: 5 additions & 0 deletions tests/examples/helm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"];
}
}
]
});
Expand Down
14 changes: 14 additions & 0 deletions tests/examples/python/python_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 8 additions & 3 deletions tests/examples/python/yaml-test/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 0e3dcef

Please sign in to comment.