Skip to content

Commit

Permalink
feat(sdk/nodejs): delegates alias computation to the engine
Browse files Browse the repository at this point in the history
  • Loading branch information
kpitzen committed Nov 4, 2022
1 parent 72f007f commit 86e0bdd
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 21 deletions.
@@ -0,0 +1,4 @@
changes:
- type: feat
scope: sdk/nodejs
description: Delegates alias computation to engine for Node SDK
6 changes: 1 addition & 5 deletions sdk/nodejs/resource.ts
Expand Up @@ -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";
Expand Down Expand Up @@ -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").
Expand Down Expand Up @@ -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;
}

Expand Down
58 changes: 51 additions & 7 deletions sdk/nodejs/runtime/resource.ts
Expand Up @@ -20,6 +20,8 @@ import * as utils from "../utils";
import { getAllResources, Input, Inputs, Output, output } from "../output";
import { ResolvedResource } from "../queryable";
import {
Alias,
allAliases,
ComponentResource,
ComponentResourceOptions,
createUrn,
Expand Down Expand Up @@ -53,13 +55,15 @@ import {
getStack,
isDryRun,
isLegacyApplyEnabled,
monitorSupportsAliasSpecs,
rpcKeepAlive,
serialize,
terminateRpcs,
} from "./settings";

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.
Expand All @@ -84,9 +88,11 @@ interface ResourceResolverOperation {
// all be URNs of custom resources, not component resources.
propertyToDirectDependencyURNs: Map<string, Set<URN>>;
// 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.
monitorSupports: Map<string, boolean>;
}

/**
Expand Down Expand Up @@ -273,6 +279,28 @@ export function readResource(res: Resource, parent: Resource | undefined, t: str
}), label);
}

function mapAliasesForRequest(aliases: (string | Alias)[] | undefined) {
if (aliases === undefined) {
return [];
}

return aliases.map(a => {
const newAlias = new aliasproto.Alias();
if (typeof a === "string") {
newAlias.setUrn(a);
} else {
const newAliasSpec = new aliasproto.Alias.Spec();
newAliasSpec.setName(a.name);
newAliasSpec.setType(a.type);
newAliasSpec.setStack(a.stack);
newAliasSpec.setProject(a.project);
newAliasSpec.setParenturn(a.parent);
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
Expand All @@ -284,7 +312,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
Expand All @@ -311,7 +339,11 @@ export function registerResource(res: Resource, parent: Resource | undefined, t:
req.setAcceptsecrets(true);
req.setAcceptresources(!utils.disableResourceReferences);
req.setAdditionalsecretoutputsList((<any>opts).additionalSecretOutputs || []);
req.setAliasurnsList(resop.aliases);
if (resop.monitorSupports.get("aliasSpecs")) {
req.setAliasesList(mapAliasesForRequest(resop.aliases));
} else {
req.setAliasurnsList(resop.aliases);
}
req.setImportid(resop.import || "");
req.setSupportspartialvalues(true);
req.setRemote(remote);
Expand Down Expand Up @@ -424,8 +456,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<ResourceResolverOperation> {

props: Inputs, opts: ResourceOptions, type: string, name: string): Promise<ResourceResolverOperation>;
async function prepareResource(label: string, res: Resource, parent: Resource | undefined, custom: boolean, remote: boolean,
props: Inputs, opts: ResourceOptions): Promise<ResourceResolverOperation>;
async function prepareResource(label: string, res: Resource, parent: Resource | undefined, custom: boolean, remote: boolean,
props: Inputs, opts: ResourceOptions, type?: string, name?: string): Promise<ResourceResolverOperation> {
// 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
Expand Down Expand Up @@ -581,12 +616,20 @@ async function prepareResource(label: string, res: Resource, parent: Resource |
propertyToDirectDependencyURNs.set(propertyName, urns);
}

const supportsAliasSpecs = await monitorSupportsAliasSpecs();
const monitorSupports = new Map();
monitorSupports.set("aliasSpecs", supportsAliasSpecs);
let computedAliases = opts.aliases;
if (!supportsAliasSpecs && parent) {
computedAliases = await allAliases(opts.aliases || [], name!, type!, parent, parent.__name!);
}

// 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<string>();
for (const alias of (res.__aliases || [])) {
const uniqueAliases = new Set<Alias | URN>();
for (const alias of (computedAliases || [])) {
const aliasVal = await output(alias).promise();
if (!uniqueAliases.has(aliasVal)) {
uniqueAliases.add(aliasVal);
Expand All @@ -606,6 +649,7 @@ async function prepareResource(label: string, res: Resource, parent: Resource |
propertyToDirectDependencyURNs: propertyToDirectDependencyURNs,
aliases: aliases,
import: importID,
monitorSupports,
};

} finally {
Expand Down
9 changes: 9 additions & 0 deletions sdk/nodejs/runtime/settings.ts
Expand Up @@ -518,6 +518,15 @@ export async function monitorSupportsDeletedWith(): Promise<boolean> {
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<boolean> {
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.
Expand Down
Expand Up @@ -18,10 +18,39 @@ class MyResource extends pulumi.CustomResource {
}
}

class MyOtherResource extends pulumi.CustomResource {
constructor(name, aliases, parent) {
super(
"test:index:MyOtherResource",
name,
{},
{
aliases: aliases,
parent
}
);
}
}

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")));
17 changes: 8 additions & 9 deletions sdk/nodejs/tests/runtime/langhost/run.spec.ts
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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);
},
);
Expand Down

0 comments on commit 86e0bdd

Please sign in to comment.