From 04d8201ada18b33c34b4dd14b82733794adffd6b Mon Sep 17 00:00:00 2001 From: Kyle Pitzen Date: Mon, 31 Oct 2022 14:25:24 -0400 Subject: [PATCH] feat(sdk/nodejs): delegates alias computation to the engine --- ...as-computation-to-engine-for-node-sdk.yaml | 4 + pkg/resource/deploy/source_eval.go | 3 + sdk/nodejs/resource.ts | 6 +- sdk/nodejs/runtime/resource.ts | 78 +++++++++++++++++-- sdk/nodejs/runtime/settings.ts | 9 +++ .../072.large_alias_lineage_chains/index.js | 31 +++++++- sdk/nodejs/tests/runtime/langhost/run.spec.ts | 17 ++-- .../adopt_into_component/step2/index.ts | 2 +- 8 files changed, 126 insertions(+), 24 deletions(-) create mode 100644 changelog/pending/20221031--sdk-nodejs--delegates-alias-computation-to-engine-for-node-sdk.yaml diff --git a/changelog/pending/20221031--sdk-nodejs--delegates-alias-computation-to-engine-for-node-sdk.yaml b/changelog/pending/20221031--sdk-nodejs--delegates-alias-computation-to-engine-for-node-sdk.yaml new file mode 100644 index 000000000000..05d37f8cfae9 --- /dev/null +++ b/changelog/pending/20221031--sdk-nodejs--delegates-alias-computation-to-engine-for-node-sdk.yaml @@ -0,0 +1,4 @@ +changes: +- type: feat + scope: sdk/nodejs + description: Delegates alias computation to engine for Node SDK diff --git a/pkg/resource/deploy/source_eval.go b/pkg/resource/deploy/source_eval.go index 8dd54d17dff3..568427d37fc2 100644 --- a/pkg/resource/deploy/source_eval.go +++ b/pkg/resource/deploy/source_eval.go @@ -1062,6 +1062,8 @@ func (rm *resmon) RegisterResource(ctx context.Context, for _, aliasObject := range req.GetAliases() { aliasSpec := aliasObject.GetSpec() + fmt.Println("aliasSpec", aliasSpec) + fmt.Println("aliasSpec.Parent", aliasSpec.GetParentUrn()) var alias resource.Alias if aliasSpec != nil { alias = resource.Alias{ @@ -1078,6 +1080,7 @@ func (rm *resmon) RegisterResource(ctx context.Context, } aliases = append(aliases, alias) } + fmt.Println("aliases", aliases) dependencies := []resource.URN{} for _, dependingURN := range req.GetDependencies() { diff --git a/sdk/nodejs/resource.ts b/sdk/nodejs/resource.ts index 26a3447e7839..2957883c5022 100644 --- a/sdk/nodejs/resource.ts +++ b/sdk/nodejs/resource.ts @@ -17,7 +17,6 @@ import { Input, Inputs, interpolate, Output, output } from "./output"; import { getResource, readResource, registerResource, registerResourceOutputs } from "./runtime/resource"; import { getProject, getStack } from "./runtime/settings"; import { getStackResource } from "./runtime/state"; -import * as stacklib from "./runtime/stack"; import { unknownValue } from "./runtime/rpc"; import * as utils from "./utils"; import * as log from "./log"; @@ -215,7 +214,7 @@ export abstract class Resource { * @internal */ // eslint-disable-next-line @typescript-eslint/naming-convention,no-underscore-dangle,id-blacklist,id-match - private readonly __name?: string; + readonly __name?: string; /** * The set of providers to use for child resources. Keyed by package name (e.g. "aws"). @@ -333,9 +332,6 @@ export abstract class Resource { opts.protect = parent.__protect; } - // Update aliases to include the full set of aliases implied by the child and parent aliases. - opts.aliases = allAliases(opts.aliases || [], name, t, parent, parent.__name!); - this.__providers = parent.__providers; } diff --git a/sdk/nodejs/runtime/resource.ts b/sdk/nodejs/runtime/resource.ts index a4b12e831b9e..d6160c51d821 100644 --- a/sdk/nodejs/runtime/resource.ts +++ b/sdk/nodejs/runtime/resource.ts @@ -17,9 +17,11 @@ import * as query from "@pulumi/query"; import * as log from "../log"; import * as utils from "../utils"; -import { getAllResources, Input, Inputs, Output, output } from "../output"; +import { all, getAllResources, Input, Inputs, Output, output } from "../output"; import { ResolvedResource } from "../queryable"; import { + Alias, + allAliases, ComponentResource, ComponentResourceOptions, createUrn, @@ -53,6 +55,7 @@ import { getStack, isDryRun, isLegacyApplyEnabled, + monitorSupportsAliasSpecs, rpcKeepAlive, serialize, terminateRpcs, @@ -60,6 +63,7 @@ import { const gstruct = require("google-protobuf/google/protobuf/struct_pb.js"); const resproto = require("../proto/resource_pb.js"); +const aliasproto = require("../proto/alias_pb.js"); interface ResourceResolverOperation { // A resolver for a resource's URN. @@ -84,9 +88,11 @@ interface ResourceResolverOperation { // all be URNs of custom resources, not component resources. propertyToDirectDependencyURNs: Map>; // A list of aliases applied to this resource. - aliases: URN[]; + aliases: (Alias | URN)[]; // An ID to import, if any. import: ID | undefined; + // Any important feature support from the monitor. + monitorSupportsStructuredAliases: boolean; } /** @@ -274,6 +280,44 @@ export function readResource(res: Resource, parent: Resource | undefined, t: str }), label); } +function getParentURN(parent?: Resource | Input) { + if (Resource.isInstance(parent)) { + return parent.urn; + } + return output(parent); +} + +function mapAliasesForRequest(aliases: (URN | Alias)[] | undefined, parentURN?: URN) { + if (aliases === undefined) { + return []; + } + + return Promise.all(aliases.map(async a => { + const newAlias = new aliasproto.Alias(); + if (typeof a === "string") { + newAlias.setUrn(a); + } else { + const newAliasSpec = new aliasproto.Alias.Spec(); + const noParent = !a.hasOwnProperty("parent") && !parentURN; + newAliasSpec.setName(a.name); + newAliasSpec.setType(a.type); + newAliasSpec.setStack(a.stack); + newAliasSpec.setProject(a.project); + if (noParent) { + newAliasSpec.setNoparent(noParent); + } else { + const aliasParentUrn = a.hasOwnProperty("parent") ? getParentURN(a.parent) : output(parentURN); + const setUrn = aliasParentUrn.apply(urn => { + newAliasSpec.setParenturn(urn); + }); + all([setUrn]); + } + newAlias.setSpec(newAliasSpec); + } + return newAlias; + })); +} + /** * registerResource registers a new resource object with a given type t and name. It returns the auto-generated * URN and the ID that will resolve after the deployment has completed. All properties will be initialized to property @@ -285,7 +329,7 @@ export function registerResource(res: Resource, parent: Resource | undefined, t: log.debug(`Registering resource: t=${t}, name=${name}, custom=${custom}, remote=${remote}`); const monitor = getMonitor(); - const resopAsync = prepareResource(label, res, parent, custom, remote, props, opts); + const resopAsync = prepareResource(label, res, parent, custom, remote, props, opts, t, name); // In order to present a useful stack trace if an error does occur, we preallocate potential // errors here. V8 captures a stack trace at the moment an Error is created and this stack @@ -312,7 +356,12 @@ export function registerResource(res: Resource, parent: Resource | undefined, t: req.setAcceptsecrets(true); req.setAcceptresources(!utils.disableResourceReferences); req.setAdditionalsecretoutputsList((opts).additionalSecretOutputs || []); - req.setAliasurnsList(resop.aliases); + if (resop.monitorSupportsStructuredAliases) { + const aliasesList = await mapAliasesForRequest(resop.aliases, resop.parentURN); + req.setAliasesList(aliasesList); + } else { + req.setAliasurnsList(resop.aliases); + } req.setImportid(resop.import || ""); req.setSupportspartialvalues(true); req.setRemote(remote); @@ -425,8 +474,11 @@ export function registerResource(res: Resource, parent: Resource | undefined, t: * properties. */ async function prepareResource(label: string, res: Resource, parent: Resource | undefined, custom: boolean, remote: boolean, - props: Inputs, opts: ResourceOptions): Promise { - + props: Inputs, opts: ResourceOptions, type: string, name: string): Promise; +async function prepareResource(label: string, res: Resource, parent: Resource | undefined, custom: boolean, remote: boolean, + props: Inputs, opts: ResourceOptions): Promise; +async function prepareResource(label: string, res: Resource, parent: Resource | undefined, custom: boolean, remote: boolean, + props: Inputs, opts: ResourceOptions, type?: string, name?: string): Promise { // add an entry to the rpc queue while we prepare the request. // automation api inline programs that don't have stack exports can exit quickly. If we don't do this, // sometimes they will exit right after `prepareResource` is called as a part of register resource, but before the @@ -582,12 +634,21 @@ async function prepareResource(label: string, res: Resource, parent: Resource | propertyToDirectDependencyURNs.set(propertyName, urns); } + const monitorSupportsStructuredAliases = await monitorSupportsAliasSpecs(); + let computedAliases; + console.warn("computedAliases", computedAliases); + if (!monitorSupportsStructuredAliases && parent) { + computedAliases = allAliases(opts.aliases || [], name!, type!, parent, parent.__name!); + } else { + computedAliases = opts.aliases || []; + } + // Wait for all aliases. Note that we use `res.__aliases` instead of `opts.aliases` as the former has been processed // in the Resource constructor prior to calling `registerResource` - both adding new inherited aliases and // simplifying aliases down to URNs. const aliases = []; - const uniqueAliases = new Set(); - for (const alias of (res.__aliases || [])) { + const uniqueAliases = new Set(); + for (const alias of (computedAliases || [])) { const aliasVal = await output(alias).promise(); if (!uniqueAliases.has(aliasVal)) { uniqueAliases.add(aliasVal); @@ -607,6 +668,7 @@ async function prepareResource(label: string, res: Resource, parent: Resource | propertyToDirectDependencyURNs: propertyToDirectDependencyURNs, aliases: aliases, import: importID, + monitorSupportsStructuredAliases, }; } finally { diff --git a/sdk/nodejs/runtime/settings.ts b/sdk/nodejs/runtime/settings.ts index 520fdd6b3e43..224ad92c9dc1 100644 --- a/sdk/nodejs/runtime/settings.ts +++ b/sdk/nodejs/runtime/settings.ts @@ -518,6 +518,15 @@ export async function monitorSupportsDeletedWith(): Promise { return monitorSupportsFeature("deletedWith"); } +/** + * monitorSupportsAliasSpecs returns a promise that when resolved tells you if the resource monitor we are + * connected to is able to support alias specs across its RPC interface. When it does, we marshal aliases + * in a special way. + */ +export async function monitorSupportsAliasSpecs(): Promise { + return monitorSupportsFeature("aliasSpecs"); +} + // sxsRandomIdentifier is a module level global that is transfered to process.env. // the goal is to detect side by side (sxs) pulumi/pulumi situations for inline programs // and fail fast. See https://github.com/pulumi/pulumi/issues/7333 for details. diff --git a/sdk/nodejs/tests/runtime/langhost/cases/072.large_alias_lineage_chains/index.js b/sdk/nodejs/tests/runtime/langhost/cases/072.large_alias_lineage_chains/index.js index 23cdb785836a..34e0c55a6c5a 100644 --- a/sdk/nodejs/tests/runtime/langhost/cases/072.large_alias_lineage_chains/index.js +++ b/sdk/nodejs/tests/runtime/langhost/cases/072.large_alias_lineage_chains/index.js @@ -11,7 +11,21 @@ class MyResource extends pulumi.CustomResource { name, {}, { - aliases: aliases, + aliases, + parent + } + ); + } +} + +class MyOtherResource extends pulumi.CustomResource { + constructor(name, aliases, parent) { + super( + "test:index:MyOtherResource", + name, + {}, + { + aliases, parent } ); @@ -21,7 +35,22 @@ class MyResource extends pulumi.CustomResource { const resource1Aliases = Array.from(new Array(1000).keys()).map(key => `my-alias-name-${key}`); const resource1 = new MyResource("testResource1", resource1Aliases); resource1.__aliases.map(alias => alias.apply(aliasName => assert(resource1Aliases.includes(aliasName)))); +assert.equal(resource1.__aliases.length, 1000); const resource2Aliases = Array.from(new Array(1000).keys()).map(key => `my-other-alias-name-${key}`); const resource2 = new MyResource("testResource2", resource2Aliases, resource1) resource2.__aliases.map(alias => alias.apply(aliasName => assert(resource2Aliases.includes(aliasName)))); +assert.equal(resource2.__aliases.length, 1000); + +const resource3Aliases = Array.from(new Array(1000).keys()).map(key => { + return { + name: `my-alias-${key}`, + stack: "my-stack", + project: "my-project", + type: "test:index:MyOtherResource", + } +}); +const resource3 = new MyOtherResource("testResource2", resource3Aliases, resource2) +assert.equal(resource3.__aliases.length, 1000); +// We want to ensure that the parent's type is included in computed aliases from the engine +resource3.__aliases[0].apply(aliasName => assert(aliasName.includes("test:index:MyResource"))); diff --git a/sdk/nodejs/tests/runtime/langhost/run.spec.ts b/sdk/nodejs/tests/runtime/langhost/run.spec.ts index 53df7166b0d9..47ac10ca3221 100644 --- a/sdk/nodejs/tests/runtime/langhost/run.spec.ts +++ b/sdk/nodejs/tests/runtime/langhost/run.spec.ts @@ -1249,14 +1249,13 @@ describe("rpc", () => { return { urn: makeUrn(t, name), id: undefined, props: undefined }; }, }, - /** Skipping this test case as it requires limiting the alias multiplication which occurs */ - // "large_alias_lineage_chains": { - // program: path.join(base, "072.large_alias_lineage_chains"), - // expectResourceCount: 1, - // registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any, ...args: any) => { - // return { urn: makeUrn(t, name), id: undefined, props: undefined }; - // }, - // } + "large_alias_lineage_chains": { + program: path.join(base, "072.large_alias_lineage_chains"), + expectResourceCount: 3, + registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any, ...args: any) => { + return { urn: makeUrn(t, name), id: undefined, props: undefined }; + }, + }, }; for (const casename of Object.keys(cases)) { @@ -1418,7 +1417,7 @@ describe("rpc", () => { // SupportsFeature callback (call: any, callback: any) => { const resp = new resproto.SupportsFeatureResponse(); - resp.setHassupport(false); + resp.setHassupport(true); callback(undefined, resp); }, ); diff --git a/tests/integration/aliases/nodejs/adopt_into_component/step2/index.ts b/tests/integration/aliases/nodejs/adopt_into_component/step2/index.ts index 754803369622..254a1bce74d0 100644 --- a/tests/integration/aliases/nodejs/adopt_into_component/step2/index.ts +++ b/tests/integration/aliases/nodejs/adopt_into_component/step2/index.ts @@ -49,7 +49,7 @@ new Component2("unparented", { class Component3 extends pulumi.ComponentResource { constructor(name: string, opts: pulumi.ComponentResourceOptions = {}) { super("my:module:Component3", name, {}, opts); - new Component2(name + "-child", { aliases: [{ parent: opts.parent}], parent: this }); + new Component2(name + "-child", { aliases: [{ parent: opts.parent }], parent: this }); } }