Skip to content

Commit

Permalink
Component option propagation (NodeJS SDK) (#2713)
Browse files Browse the repository at this point in the history
<!--Thanks for your contribution. See [CONTRIBUTING](CONTRIBUTING.md)
    for Pulumi's contribution guidelines.

    Help us merge your changes more quickly by adding more details such
    as labels, milestones, and reviewers.-->

### Proposed changes
Epic: #2254
Fixes: #1938
Fixes: #2049
Fixes: #812

This PR standardizes the option propagation logic for the component
resources in the pulumi-kubernetes NodeJS SDK. The general approach is:
1. In the component resource constructor, compute the child options to
be propagated to any children. The child options consist of the
component as parent, and with `version` and `pluginDownloadURL` if
specified.
2. Compute the invoke options by copying the child options.

### Specification
The component resource is responsible for computing sub-options for
invokes and for child resource declarations. This table outlines the
expected behavior for each [resource
option](https://www.pulumi.com/docs/concepts/options/) when presented to
a component resource.

|  | Propagated | Remarks |
|---|---|---|
| `additionalSecretOutputs` | no | "does not apply to component
resources" |
| `aliases` | no | Inherited via parent-child relationship. |
| `customTimeouts` | no | "does not apply to component resources" |
| `deleteBeforeReplace` | no |  |
| `deletedWith` | no |  |
| `dependsOn` | no | The children implicitly wait for the dependency. |
| `ignoreChanges` | no | Nonsensical to apply directly to children (see
[discussion](pulumi/pulumi#8969)). |
| `import` | no |  |
| `parent` | **yes** | The component becomes the parent. |
| `protect` | no | Inherited. |
| `provider` | no | Combined into providers map, then inherited via
parent-child relationship. |
| `providers` | no | Inherited. |
| `replaceOnChanges` | no | "does not apply to component resources" |
| `retainOnDelete` | no | "does not apply to component resources" |
| `transformations` | no | Inherited. |
| `version` | **yes** | Influences default provider selection logic
during invokes.<br/>Should propagate when child resource is from the
same provider type. |
| `pluginDownloadURL` | **yes** | Influences default provider selection
logic during invokes.<br/>Should propagate when child resource is from
the same provider type. |

### Testing
A new test case is provided ([test
case](https://github.com/pulumi/pulumi-kubernetes/blob/e1ad541a9c86e8eb207bd1d8a276822862425109/tests/sdk/nodejs/nodejs_test.go#L2107),
[test
program](https://github.com/pulumi/pulumi-kubernetes/blob/e1ad541a9c86e8eb207bd1d8a276822862425109/tests/sdk/nodejs/options/index.ts))
that exercises option propagation across the component resources:
-
[kubernetes.helm.sh.v3.Chart](https://www.pulumi.com/registry/packages/kubernetes/api-docs/helm/v3/chart/)
-
[kubernetes.kustomize.Directory](https://www.pulumi.com/registry/packages/kubernetes/api-docs/kustomize/directory/)
-
[kubernetes.yaml.ConfigGroup](https://www.pulumi.com/registry/packages/kubernetes/api-docs/yaml/configgroup/)
-
[kubernetes.yaml.ConfigFile](https://www.pulumi.com/registry/packages/kubernetes/api-docs/yaml/configfile/)

Upgrade testing must be done manually, with an emphasis on avoiding
replacement due to reparenting.

### Related issues (optional)

The p/p NodeJS SDK doesn't propagate the `pluginDownloadURL` option to
the Invoke RPC.
pulumi/pulumi#14839
  • Loading branch information
EronWright committed Jan 3, 2024
1 parent c9fa74e commit afe1f1b
Show file tree
Hide file tree
Showing 12 changed files with 747 additions and 44 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## Unreleased
- Fix option propagation in component resources (NodeJS SDK) (https://github.com/pulumi/pulumi-kubernetes/pull/2713)
- Fix option propagation in component resources (Go SDK) (https://github.com/pulumi/pulumi-kubernetes/pull/2709)

## 4.6.1 (December 14, 2023)
Expand Down
14 changes: 4 additions & 10 deletions provider/pkg/gen/nodejs-templates/helm/v3/helm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export class Chart extends yaml.CollectionComponentResource {
if (config.resourcePrefix !== undefined) {
releaseName = `${config.resourcePrefix}-${releaseName}`
}
const aliasOpts: pulumi.ComponentResourceOptions = {...opts, aliases: [{type: "kubernetes:helm.sh/v2:Chart"}]}
const aliasOpts: pulumi.ComponentResourceOptions = {...opts, aliases: [{type: "kubernetes:helm.sh/v2:Chart"}, ...(opts?.aliases ?? [])]}
super(Chart.__pulumiType, releaseName, config, aliasOpts);

const allConfig = pulumi.output(config);
Expand Down Expand Up @@ -242,14 +242,8 @@ export class Chart extends yaml.CollectionComponentResource {
transformations.push(yaml.skipAwait);
}

// Rather than using the default provider for the following invoke call, use the version specified
// in package.json.
let invokeOpts: pulumi.InvokeOptions = {
async: true,
version: opts?.version ?? getVersion(),
provider: opts?.provider,
parent: opts?.parent
};
const childOpts = yaml.getChildOpts(this, opts);
const invokeOpts = yaml.getInvokeOpts(childOpts);

const promise = pulumi.runtime.invoke("kubernetes:helm:template", {jsonOpts}, invokeOpts);
return pulumi.output(promise).apply<{ [key: string]: pulumi.CustomResource }>(p => yaml.parse(
Expand All @@ -258,7 +252,7 @@ export class Chart extends yaml.CollectionComponentResource {
objs: p.result,
transformations,
},
{parent: this}
childOpts
));
}
}
Expand Down
11 changes: 5 additions & 6 deletions provider/pkg/gen/nodejs-templates/kustomize/kustomize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,10 @@ export class Directory extends yaml.CollectionComponentResource {
}
super("kubernetes:kustomize:Directory", name, config, opts);

const directory = config.directory

// Rather than using the default provider for the following invoke call, use the version specified
// in package.json.
let invokeOpts: pulumi.InvokeOptions = { async: true, version: getVersion(), provider: opts?.provider };
const directory = config.directory;

const childOpts = yaml.getChildOpts(this, opts);
const invokeOpts = yaml.getInvokeOpts(childOpts);

const promise = pulumi.runtime.invoke("kubernetes:kustomize:directory", {directory}, invokeOpts);
this.resources = pulumi.output(promise).apply<{[key: string]: pulumi.CustomResource}>(p => yaml.parse(
Expand All @@ -107,7 +106,7 @@ export class Directory extends yaml.CollectionComponentResource {
objs: p.result,
transformations: config.transformations || [],
},
{ parent: this, dependsOn: opts?.dependsOn }
childOpts
));
}
}
Expand Down
31 changes: 25 additions & 6 deletions provider/pkg/gen/nodejs-templates/yaml/yaml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ export class ConfigGroup extends CollectionComponentResource {
*/
constructor(name: string, config: ConfigGroupOpts, opts?: pulumi.ComponentResourceOptions) {
super("kubernetes:yaml:ConfigGroup", name, config, opts);
this.resources = parse(config, {...opts, parent: this});
const childOpts = getChildOpts(this, opts);
this.resources = parse(config, childOpts);
}
}

Expand Down Expand Up @@ -291,13 +292,14 @@ export class ConfigFile extends CollectionComponentResource {
transformations.push(skipAwait);
}

this.resources = pulumi.output(text.then(t => {
const childOpts = getChildOpts(this, opts);
this.resources = pulumi.output(text.then(t => {
try {
const parsed = parseYamlDocument({
objs: yamlLoadAll(t, opts),
objs: yamlLoadAll(t, childOpts),
transformations,
resourcePrefix: config && config.resourcePrefix || undefined
}, {...opts, parent: this});
}, childOpts);
// If the provider is not fully initialized, the engine skips invoking on the provider and returns an
// empty result. This may change based on how https://github.com/pulumi/pulumi/issues/10209 is addressed.
parsed.apply(p => {
Expand Down Expand Up @@ -383,9 +385,26 @@ export interface ConfigOpts {
resourcePrefix?: string;
}

/** @ignore */ function yamlLoadAll(text: string, opts?: pulumi.ComponentResourceOptions): Promise<any[]> {
let invokeOpts: pulumi.InvokeOptions = { async: true, version: getVersion(), provider: opts?.provider };
/** @ignore */ export function getChildOpts(parent: pulumi.Resource, opts?: pulumi.ComponentResourceOptions): pulumi.CustomResourceOptions {
return {
parent: parent,
...opts?.version && {version: opts.version},
...opts?.pluginDownloadURL && {pluginDownloadURL: opts.pluginDownloadURL}
};
}

/** @ignore */ export function getInvokeOpts(opts?: pulumi.CustomResourceOptions): pulumi.InvokeOptions {
return {
parent: opts?.parent,
provider: opts?.provider,
version: opts?.version ?? getVersion(),
pluginDownloadURL: opts?.pluginDownloadURL,
async: true
};
}

/** @ignore */ function yamlLoadAll(text: string, opts?: pulumi.CustomResourceOptions): Promise<any[]> {
const invokeOpts = getInvokeOpts(opts);
return pulumi.runtime.invoke("kubernetes:yaml:decode", {text}, invokeOpts)
.then(p => p.result);
}
Expand Down
14 changes: 4 additions & 10 deletions sdk/nodejs/helm/v3/helm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export class Chart extends yaml.CollectionComponentResource {
if (config.resourcePrefix !== undefined) {
releaseName = `${config.resourcePrefix}-${releaseName}`
}
const aliasOpts: pulumi.ComponentResourceOptions = {...opts, aliases: [{type: "kubernetes:helm.sh/v2:Chart"}]}
const aliasOpts: pulumi.ComponentResourceOptions = {...opts, aliases: [{type: "kubernetes:helm.sh/v2:Chart"}, ...(opts?.aliases ?? [])]}
super(Chart.__pulumiType, releaseName, config, aliasOpts);

const allConfig = pulumi.output(config);
Expand Down Expand Up @@ -242,14 +242,8 @@ export class Chart extends yaml.CollectionComponentResource {
transformations.push(yaml.skipAwait);
}

// Rather than using the default provider for the following invoke call, use the version specified
// in package.json.
let invokeOpts: pulumi.InvokeOptions = {
async: true,
version: opts?.version ?? getVersion(),
provider: opts?.provider,
parent: opts?.parent
};
const childOpts = yaml.getChildOpts(this, opts);
const invokeOpts = yaml.getInvokeOpts(childOpts);

const promise = pulumi.runtime.invoke("kubernetes:helm:template", {jsonOpts}, invokeOpts);
return pulumi.output(promise).apply<{ [key: string]: pulumi.CustomResource }>(p => yaml.parse(
Expand All @@ -258,7 +252,7 @@ export class Chart extends yaml.CollectionComponentResource {
objs: p.result,
transformations,
},
{parent: this}
childOpts
));
}
}
Expand Down
11 changes: 5 additions & 6 deletions sdk/nodejs/kustomize/kustomize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,10 @@ export class Directory extends yaml.CollectionComponentResource {
}
super("kubernetes:kustomize:Directory", name, config, opts);

const directory = config.directory

// Rather than using the default provider for the following invoke call, use the version specified
// in package.json.
let invokeOpts: pulumi.InvokeOptions = { async: true, version: getVersion(), provider: opts?.provider };
const directory = config.directory;

const childOpts = yaml.getChildOpts(this, opts);
const invokeOpts = yaml.getInvokeOpts(childOpts);

const promise = pulumi.runtime.invoke("kubernetes:kustomize:directory", {directory}, invokeOpts);
this.resources = pulumi.output(promise).apply<{[key: string]: pulumi.CustomResource}>(p => yaml.parse(
Expand All @@ -107,7 +106,7 @@ export class Directory extends yaml.CollectionComponentResource {
objs: p.result,
transformations: config.transformations || [],
},
{ parent: this, dependsOn: opts?.dependsOn }
childOpts
));
}
}
Expand Down
31 changes: 25 additions & 6 deletions sdk/nodejs/yaml/yaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3037,7 +3037,8 @@ export class ConfigGroup extends CollectionComponentResource {
*/
constructor(name: string, config: ConfigGroupOpts, opts?: pulumi.ComponentResourceOptions) {
super("kubernetes:yaml:ConfigGroup", name, config, opts);
this.resources = parse(config, {...opts, parent: this});
const childOpts = getChildOpts(this, opts);
this.resources = parse(config, childOpts);
}
}

Expand Down Expand Up @@ -3126,13 +3127,14 @@ export class ConfigFile extends CollectionComponentResource {
transformations.push(skipAwait);
}

this.resources = pulumi.output(text.then(t => {
const childOpts = getChildOpts(this, opts);
this.resources = pulumi.output(text.then(t => {
try {
const parsed = parseYamlDocument({
objs: yamlLoadAll(t, opts),
objs: yamlLoadAll(t, childOpts),
transformations,
resourcePrefix: config && config.resourcePrefix || undefined
}, {...opts, parent: this});
}, childOpts);
// If the provider is not fully initialized, the engine skips invoking on the provider and returns an
// empty result. This may change based on how https://github.com/pulumi/pulumi/issues/10209 is addressed.
parsed.apply(p => {
Expand Down Expand Up @@ -3218,9 +3220,26 @@ export interface ConfigOpts {
resourcePrefix?: string;
}

/** @ignore */ function yamlLoadAll(text: string, opts?: pulumi.ComponentResourceOptions): Promise<any[]> {
let invokeOpts: pulumi.InvokeOptions = { async: true, version: getVersion(), provider: opts?.provider };
/** @ignore */ export function getChildOpts(parent: pulumi.Resource, opts?: pulumi.ComponentResourceOptions): pulumi.CustomResourceOptions {
return {
parent: parent,
...opts?.version && {version: opts.version},
...opts?.pluginDownloadURL && {pluginDownloadURL: opts.pluginDownloadURL}
};
}

/** @ignore */ export function getInvokeOpts(opts?: pulumi.CustomResourceOptions): pulumi.InvokeOptions {
return {
parent: opts?.parent,
provider: opts?.provider,
version: opts?.version ?? getVersion(),
pluginDownloadURL: opts?.pluginDownloadURL,
async: true
};
}

/** @ignore */ function yamlLoadAll(text: string, opts?: pulumi.CustomResourceOptions): Promise<any[]> {
const invokeOpts = getInvokeOpts(opts);
return pulumi.runtime.invoke("kubernetes:yaml:decode", {text}, invokeOpts)
.then(p => p.result);
}
Expand Down
Loading

0 comments on commit afe1f1b

Please sign in to comment.