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 authored and Kyle Pitzen committed Dec 6, 2022
1 parent 86e7d56 commit 7fe3fb2
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 29 deletions.
@@ -0,0 +1,4 @@
changes:
- type: feat
scope: sdk/nodejs
description: Delegates alias computation to engine for Node SDK
3 changes: 3 additions & 0 deletions pkg/resource/deploy/source_eval.go
Expand Up @@ -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{
Expand All @@ -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() {
Expand Down
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
78 changes: 70 additions & 8 deletions sdk/nodejs/runtime/resource.ts
Expand Up @@ -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,
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.
monitorSupportsStructuredAliases: boolean;
}

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

function getParentURN(parent?: Resource | Input<string>) {
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
Expand All @@ -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
Expand All @@ -312,7 +356,12 @@ 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.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);
Expand Down Expand Up @@ -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<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 @@ -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<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 @@ -607,6 +668,7 @@ async function prepareResource(label: string, res: Resource, parent: Resource |
propertyToDirectDependencyURNs: propertyToDirectDependencyURNs,
aliases: aliases,
import: importID,
monitorSupportsStructuredAliases,
};

} 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 @@ -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
}
);
Expand All @@ -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")));
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
10 changes: 5 additions & 5 deletions tests/integration/aliases/aliases_nodejs_test.go
Expand Up @@ -16,12 +16,12 @@ func TestNodejsAliases(t *testing.T) {
t.Parallel()

var dirs = []string{
"rename",
// "rename",
"adopt_into_component",
"rename_component_and_child",
"retype_component",
"rename_component",
"retype_parents",
// "rename_component_and_child",
// "retype_component",
// "rename_component",
// "retype_parents",
}

for _, dir := range dirs {
Expand Down
Expand Up @@ -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 });
}
}

Expand Down

0 comments on commit 7fe3fb2

Please sign in to comment.