mirror of https://github.com/pulumi/pulumi.git
2091 lines
86 KiB
TypeScript
2091 lines
86 KiB
TypeScript
// Copyright 2016-2018, Pulumi Corporation.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import assert from "assert";
|
|
import * as childProcess from "child_process";
|
|
import * as path from "path";
|
|
import { ID, runtime, URN } from "../../../index";
|
|
import { platformIndependentEOL } from "../../constants";
|
|
|
|
import * as grpc from "@grpc/grpc-js";
|
|
|
|
import * as gempty from "google-protobuf/google/protobuf/empty_pb";
|
|
import * as gstruct from "google-protobuf/google/protobuf/struct_pb";
|
|
|
|
const enginerpc = require("../../../proto/engine_grpc_pb.js");
|
|
const engineproto = require("../../../proto/engine_pb.js");
|
|
const langrpc = require("../../../proto/language_grpc_pb.js");
|
|
const langproto = require("../../../proto/language_pb.js");
|
|
const resrpc = require("../../../proto/resource_grpc_pb.js");
|
|
const resproto = require("../../../proto/resource_pb.js");
|
|
const providerproto = require("../../../proto/provider_pb.js");
|
|
|
|
interface RunCase {
|
|
only?: boolean;
|
|
project?: string;
|
|
stack?: string;
|
|
pwd: string;
|
|
main?: string;
|
|
args?: string[];
|
|
config?: { [key: string]: any };
|
|
expectError?: string;
|
|
expectBail?: boolean;
|
|
expectResourceCount?: number;
|
|
expectedLogs?: {
|
|
count?: number;
|
|
ignoreDebug?: boolean;
|
|
};
|
|
skipRootResourceEndpoints?: boolean;
|
|
showRootResourceRegistration?: boolean;
|
|
invoke?: (ctx: any, tok: string, args: any, version: string, provider: string) => { failures: any; ret: any };
|
|
readResource?: (
|
|
ctx: any,
|
|
t: string,
|
|
name: string,
|
|
id: string,
|
|
par: string,
|
|
state: any,
|
|
version: string,
|
|
sourcePosition?: runtime.SourcePosition,
|
|
) => {
|
|
urn: URN | undefined;
|
|
props: any | undefined;
|
|
};
|
|
registerResource?: (
|
|
ctx: any,
|
|
dryrun: boolean,
|
|
t: string,
|
|
name: string,
|
|
res: any,
|
|
dependencies?: string[],
|
|
custom?: boolean,
|
|
protect?: boolean,
|
|
parent?: string,
|
|
provider?: string,
|
|
propertyDeps?: any,
|
|
ignoreChanges?: string[],
|
|
version?: string,
|
|
importID?: string,
|
|
replaceOnChanges?: string[],
|
|
providers?: any,
|
|
sourcePosition?: runtime.SourcePosition,
|
|
) => {
|
|
urn: URN | undefined;
|
|
id: ID | undefined;
|
|
props: any | undefined;
|
|
};
|
|
registerResourceOutputs?: (
|
|
ctx: any,
|
|
dryrun: boolean,
|
|
urn: URN,
|
|
t: string,
|
|
name: string,
|
|
res: any,
|
|
outputs: any | undefined,
|
|
) => void;
|
|
log?: (ctx: any, severity: any, message: string, urn: URN, streamId: number) => void;
|
|
getRootResource?: (ctx: any) => { urn: string };
|
|
setRootResource?: (ctx: any, urn: string) => void;
|
|
}
|
|
|
|
let cleanupFns: (() => Promise<void>)[] = [];
|
|
const cleanup = (callback: () => Promise<void>): void => {
|
|
cleanupFns.push(callback);
|
|
};
|
|
const runCleanup = () => {
|
|
for (const d of cleanupFns) {
|
|
// Keep running the test regardless of failure.
|
|
d().catch((err) => console.log("???? Error thrown in defer, ignoring."));
|
|
}
|
|
cleanupFns = [];
|
|
};
|
|
afterEach(async () => {
|
|
runCleanup();
|
|
});
|
|
|
|
function makeUrn(t: string, name: string): URN {
|
|
return `${t}::${name}`;
|
|
}
|
|
|
|
describe("rpc", () => {
|
|
beforeEach(() => {
|
|
runtime._reset();
|
|
});
|
|
const base: string = path.join(path.dirname(__filename), "cases");
|
|
const cases: { [key: string]: RunCase } = {
|
|
// An empty program.
|
|
empty: {
|
|
pwd: path.join(base, "000.empty"),
|
|
expectResourceCount: 0,
|
|
},
|
|
// The same thing, just using pwd rather than an absolute program path.
|
|
"empty.pwd": {
|
|
pwd: path.join(base, "000.empty"),
|
|
main: "./",
|
|
expectResourceCount: 0,
|
|
},
|
|
// The same thing, just using pwd and the filename rather than an absolute program path.
|
|
"empty.pwd.index.js": {
|
|
pwd: path.join(base, "000.empty"),
|
|
main: "./index.js",
|
|
expectResourceCount: 0,
|
|
},
|
|
// A program that allocates a single resource.
|
|
one_resource: {
|
|
pwd: path.join(base, "001.one_resource"),
|
|
expectResourceCount: 1,
|
|
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any) => {
|
|
assert.strictEqual(t, "test:index:MyResource");
|
|
assert.strictEqual(name, "testResource1");
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
// A program that allocates ten simple resources.
|
|
ten_resources: {
|
|
pwd: path.join(base, "002.ten_resources"),
|
|
expectResourceCount: 10,
|
|
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any) => {
|
|
assert.strictEqual(t, "test:index:MyResource");
|
|
if (ctx.seen) {
|
|
assert(!ctx.seen[name], `Got multiple resources with same name ${name}`);
|
|
} else {
|
|
ctx.seen = {};
|
|
}
|
|
const prefix = "testResource";
|
|
assert.strictEqual(
|
|
name.substring(0, prefix.length),
|
|
prefix,
|
|
`Expected ${name} to be of the form ${prefix}N; missing prefix`,
|
|
);
|
|
const seqnum = parseInt(name.substring(prefix.length), 10);
|
|
assert(!isNaN(seqnum), `Expected ${name} to be of the form ${prefix}N; missing N seqnum`);
|
|
ctx.seen[name] = true;
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
// A program that allocates a complex resource with lots of input and output properties.
|
|
one_complex_resource: {
|
|
pwd: path.join(base, "003.one_complex_resource"),
|
|
expectResourceCount: 1,
|
|
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any) => {
|
|
assert.strictEqual(t, "test:index:MyResource");
|
|
assert.strictEqual(name, "testResource1");
|
|
assert.deepStrictEqual(res, {
|
|
inpropB1: false,
|
|
inpropB2: true,
|
|
inpropN: 42,
|
|
inpropS: "a string",
|
|
inpropA: [true, 99, "what a great property"],
|
|
inpropO: {
|
|
b1: false,
|
|
b2: true,
|
|
n: 42,
|
|
s: "another string",
|
|
a: [66, false, "strings galore"],
|
|
o: { z: "x" },
|
|
},
|
|
});
|
|
return {
|
|
urn: makeUrn(t, name),
|
|
id: name,
|
|
props: {
|
|
outprop1: "output properties ftw",
|
|
outprop2: 998.6,
|
|
},
|
|
};
|
|
},
|
|
},
|
|
// A program that allocates 10 complex resources with lots of input and output properties.
|
|
ten_complex_resources: {
|
|
pwd: path.join(base, "004.ten_complex_resources"),
|
|
expectResourceCount: 10,
|
|
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any) => {
|
|
assert.strictEqual(t, "test:index:MyResource");
|
|
if (ctx.seen) {
|
|
assert(!ctx.seen[name], `Got multiple resources with same name ${name}`);
|
|
} else {
|
|
ctx.seen = {};
|
|
}
|
|
const prefix = "testResource";
|
|
assert.strictEqual(
|
|
name.substring(0, prefix.length),
|
|
prefix,
|
|
`Expected ${name} to be of the form ${prefix}N; missing prefix`,
|
|
);
|
|
const seqnum = parseInt(name.substring(prefix.length), 10);
|
|
assert(!isNaN(seqnum), `Expected ${name} to be of the form ${prefix}N; missing N seqnum`);
|
|
ctx.seen[name] = true;
|
|
assert.deepStrictEqual(res, {
|
|
inseq: seqnum,
|
|
inpropB1: false,
|
|
inpropB2: true,
|
|
inpropN: 42,
|
|
inpropS: "a string",
|
|
inpropA: [true, 99, "what a great property"],
|
|
inpropO: {
|
|
b1: false,
|
|
b2: true,
|
|
n: 42,
|
|
s: "another string",
|
|
a: [66, false, "strings galore"],
|
|
o: { z: "x" },
|
|
},
|
|
});
|
|
return {
|
|
urn: makeUrn(t, name),
|
|
id: name,
|
|
props: {
|
|
outseq: seqnum,
|
|
outprop1: "output properties ftw",
|
|
outprop2: 998.6,
|
|
},
|
|
};
|
|
},
|
|
},
|
|
// A program that allocates a single resource.
|
|
resource_thens: {
|
|
pwd: path.join(base, "005.resource_thens"),
|
|
expectResourceCount: 2,
|
|
registerResource: (ctx, dryrun, t, name, res, dependencies) => {
|
|
let id: ID | undefined;
|
|
let props: any | undefined;
|
|
switch (t) {
|
|
case "test:index:ResourceA": {
|
|
assert.strictEqual(name, "resourceA");
|
|
assert.deepStrictEqual(res, { inprop: 777 });
|
|
if (!dryrun) {
|
|
id = name;
|
|
props = { outprop: "output yeah" };
|
|
}
|
|
break;
|
|
}
|
|
case "test:index:ResourceB": {
|
|
assert.strictEqual(name, "resourceB");
|
|
assert.deepStrictEqual(dependencies, ["test:index:ResourceA::resourceA"]);
|
|
|
|
if (dryrun) {
|
|
// If this is a dry-run, we will have no known values.
|
|
assert.deepStrictEqual(res, {
|
|
otherIn: runtime.unknownValue,
|
|
otherOut: runtime.unknownValue,
|
|
});
|
|
} else {
|
|
// Otherwise, we will:
|
|
assert.deepStrictEqual(res, {
|
|
otherIn: 777,
|
|
otherOut: "output yeah",
|
|
});
|
|
}
|
|
|
|
if (!dryrun) {
|
|
id = name;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
assert.fail(`Unrecognized resource type ${t}`);
|
|
}
|
|
return {
|
|
urn: makeUrn(t, name),
|
|
id: id,
|
|
props: props,
|
|
};
|
|
},
|
|
},
|
|
input_output: {
|
|
pwd: path.join(base, "006.asset"),
|
|
expectResourceCount: 1,
|
|
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any) => {
|
|
assert.strictEqual(t, "test:index:FileResource");
|
|
assert.strictEqual(name, "file1");
|
|
assert.deepStrictEqual(res, {
|
|
data: {
|
|
[runtime.specialSigKey]: runtime.specialAssetSig,
|
|
__pulumiAsset: true,
|
|
path: "./testdata.txt",
|
|
},
|
|
});
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
promises_io: {
|
|
pwd: path.join(base, "007.promises_io"),
|
|
expectResourceCount: 1,
|
|
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any) => {
|
|
assert.strictEqual(t, "test:index:FileResource");
|
|
assert.strictEqual(name, "file1");
|
|
const actualData = res.data.replace(platformIndependentEOL, "\n"); // EOL normalization
|
|
assert.deepStrictEqual(actualData, "The test worked!\n\nIf you can see some data!\n\n");
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
// A program that allocates ten simple resources that use dependsOn to depend on one another, 10 different ways.
|
|
ten_depends_on_resources: {
|
|
pwd: path.join(base, "008.ten_depends_on_resources"),
|
|
expectResourceCount: 100,
|
|
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any) => {
|
|
assert.strictEqual(t, "test:index:MyResource");
|
|
if (ctx.seen) {
|
|
assert(!ctx.seen[name], `Got multiple resources with same name ${name}`);
|
|
} else {
|
|
ctx.seen = {};
|
|
}
|
|
const prefix = "testResource";
|
|
assert.strictEqual(
|
|
name.substring(0, prefix.length),
|
|
prefix,
|
|
`Expected ${name} to be of the form ${prefix}N; missing prefix`,
|
|
);
|
|
const seqnum = parseInt(name.substring(prefix.length), 10);
|
|
assert(!isNaN(seqnum), `Expected ${name} to be of the form ${prefix}N; missing N seqnum`);
|
|
ctx.seen[name] = true;
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
// A simple test of the invocation RPC pathways.
|
|
invoke: {
|
|
pwd: path.join(base, "009.invoke"),
|
|
expectResourceCount: 0,
|
|
invoke: (ctx: any, tok: string, args: any, version: string, provider: string) => {
|
|
assert.strictEqual(provider, "");
|
|
assert.strictEqual(tok, "invoke:index:echo");
|
|
assert.deepStrictEqual(args, {
|
|
a: "hello",
|
|
b: true,
|
|
c: [0.99, 42, { z: "x" }],
|
|
id: "some-id",
|
|
urn: "some-urn",
|
|
});
|
|
return { failures: undefined, ret: args };
|
|
},
|
|
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any) => {
|
|
assert.strictEqual(t, "test:index:MyResource");
|
|
assert.strictEqual(name, "testResource1");
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
// Simply test that certain runtime properties are available.
|
|
runtimeSettings: {
|
|
project: "runtimeSettingsProject",
|
|
stack: "runtimeSettingsStack",
|
|
config: {
|
|
"myBag:A": "42",
|
|
"myBag:bbbb": "a string o' b's",
|
|
},
|
|
pwd: path.join(base, "010.runtime_settings"),
|
|
expectResourceCount: 0,
|
|
},
|
|
// A program that throws an ordinary unhandled error.
|
|
unhandled_error: {
|
|
pwd: path.join(base, "011.unhandled_error"),
|
|
expectResourceCount: 0,
|
|
expectError: "",
|
|
expectBail: true,
|
|
expectedLogs: {
|
|
count: 1,
|
|
ignoreDebug: true,
|
|
},
|
|
log: (ctx: any, severity: any, message: string) => {
|
|
if (severity === engineproto.LogSeverity.ERROR) {
|
|
if (
|
|
message.indexOf("failed with an unhandled exception") < 0 &&
|
|
message.indexOf("es the dynamite") < 0
|
|
) {
|
|
throw new Error("Unexpected error: " + message);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
// A program that creates one resource that contains an assets archive.
|
|
assets_archive: {
|
|
pwd: path.join(base, "012.assets_archive"),
|
|
expectResourceCount: 1,
|
|
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any) => {
|
|
assert.deepStrictEqual(res, {
|
|
archive: {
|
|
"4dabf18193072939515e22adb298388d": "0def7320c3a5731c473e5ecbe6d01bc7",
|
|
__pulumiArchive: true,
|
|
assets: {
|
|
archive: {
|
|
"4dabf18193072939515e22adb298388d": "0def7320c3a5731c473e5ecbe6d01bc7",
|
|
__pulumiArchive: true,
|
|
assets: {},
|
|
},
|
|
asset: {
|
|
"4dabf18193072939515e22adb298388d": "c44067f5952c0a294b673a41bacd8c17",
|
|
__pulumiAsset: true,
|
|
text: "foo",
|
|
},
|
|
},
|
|
},
|
|
archiveP: {
|
|
"4dabf18193072939515e22adb298388d": "0def7320c3a5731c473e5ecbe6d01bc7",
|
|
__pulumiArchive: true,
|
|
assets: {
|
|
foo: {
|
|
"4dabf18193072939515e22adb298388d": "c44067f5952c0a294b673a41bacd8c17",
|
|
__pulumiAsset: true,
|
|
text: "bar",
|
|
},
|
|
},
|
|
},
|
|
assetP: {
|
|
"4dabf18193072939515e22adb298388d": "c44067f5952c0a294b673a41bacd8c17",
|
|
__pulumiAsset: true,
|
|
text: "baz",
|
|
},
|
|
});
|
|
return { urn: makeUrn(t, name), id: undefined, props: res };
|
|
},
|
|
},
|
|
// A program that contains an unhandled promise rejection.
|
|
unhandled_promise_rejection: {
|
|
pwd: path.join(base, "013.unhandled_promise_rejection"),
|
|
expectResourceCount: 0,
|
|
expectError: "",
|
|
expectBail: true,
|
|
expectedLogs: {
|
|
count: 1,
|
|
ignoreDebug: true,
|
|
},
|
|
log: (ctx: any, severity: any, message: string) => {
|
|
if (severity === engineproto.LogSeverity.ERROR) {
|
|
if (
|
|
message.indexOf("failed with an unhandled exception") < 0 &&
|
|
message.indexOf("es the dynamite") < 0
|
|
) {
|
|
throw new Error("Unexpected error: " + message);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
// A simple test of the read resource behavior.
|
|
read_resource: {
|
|
pwd: path.join(base, "014.read_resource"),
|
|
expectResourceCount: 0,
|
|
readResource: (ctx: any, t: string, name: string, id: string, par: string, state: any) => {
|
|
assert.strictEqual(t, "test:read:resource");
|
|
assert.strictEqual(name, "foo");
|
|
assert.strictEqual(id, "abc123");
|
|
assert.deepStrictEqual(state, {
|
|
a: "fizzz",
|
|
b: false,
|
|
c: [0.73, "x", { zed: 923 }],
|
|
});
|
|
return {
|
|
urn: makeUrn(t, name),
|
|
props: {
|
|
b: true,
|
|
d: "and then, out of nowhere ...",
|
|
},
|
|
};
|
|
},
|
|
},
|
|
// Test that the runtime can be loaded twice.
|
|
runtime_sxs: {
|
|
pwd: path.join(base, "015.runtime_sxs"),
|
|
config: {
|
|
"sxs:message": "SxS config works!",
|
|
},
|
|
expectResourceCount: 2,
|
|
expectedLogs: {
|
|
count: 2,
|
|
ignoreDebug: true,
|
|
},
|
|
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any) => {
|
|
return { urn: makeUrn(t, name), id: name, props: res };
|
|
},
|
|
log: (ctx: any, severity: number, message: string, urn: URN, streamId: number) => {
|
|
assert.strictEqual(severity, engineproto.LogSeverity.INFO);
|
|
assert.strictEqual(/logging via (.*) works/.test(message), true);
|
|
},
|
|
},
|
|
// Test that leaked debuggable promises fail the deployment.
|
|
promise_leak: {
|
|
pwd: path.join(base, "016.promise_leak"),
|
|
expectError: "Program exited with non-zero exit code: 1",
|
|
},
|
|
// A test of parent default behaviors.
|
|
parent_defaults: {
|
|
pwd: path.join(base, "017.parent_defaults"),
|
|
expectResourceCount: 240,
|
|
registerResource: parentDefaultsRegisterResource,
|
|
},
|
|
logging: {
|
|
pwd: path.join(base, "018.logging"),
|
|
expectResourceCount: 1,
|
|
expectedLogs: {
|
|
count: 5,
|
|
ignoreDebug: true,
|
|
},
|
|
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any) => {
|
|
// "test" is the one resource this test creates - save the URN so we can assert
|
|
// it gets passed to log later on.
|
|
if (name === "test") {
|
|
ctx.testUrn = makeUrn(t, name);
|
|
}
|
|
|
|
return { urn: makeUrn(t, name), id: name, props: res };
|
|
},
|
|
log: (ctx: any, severity: number, message: string, urn: URN, streamId: number) => {
|
|
switch (message) {
|
|
case "info message":
|
|
assert.strictEqual(severity, engineproto.LogSeverity.INFO);
|
|
return;
|
|
case "warning message":
|
|
assert.strictEqual(severity, engineproto.LogSeverity.WARNING);
|
|
return;
|
|
case "error message":
|
|
assert.strictEqual(severity, engineproto.LogSeverity.ERROR);
|
|
return;
|
|
case "attached to resource":
|
|
assert.strictEqual(severity, engineproto.LogSeverity.INFO);
|
|
assert.strictEqual(urn, ctx.testUrn);
|
|
return;
|
|
case "with streamid":
|
|
assert.strictEqual(severity, engineproto.LogSeverity.INFO);
|
|
assert.strictEqual(urn, ctx.testUrn);
|
|
assert.strictEqual(streamId, 42);
|
|
return;
|
|
default:
|
|
assert.fail("unexpected message: " + message);
|
|
}
|
|
},
|
|
},
|
|
// Test stack outputs via exports.
|
|
stack_exports: {
|
|
pwd: path.join(base, "019.stack_exports"),
|
|
expectResourceCount: 1,
|
|
showRootResourceRegistration: true,
|
|
registerResource: (ctx, dryrun, t, name, res, deps, custom, protect, parent) => {
|
|
if (t === "pulumi:pulumi:Stack") {
|
|
ctx.stackUrn = makeUrn(t, name);
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
}
|
|
throw new Error();
|
|
},
|
|
registerResourceOutputs: (
|
|
ctx: any,
|
|
dryrun: boolean,
|
|
urn: URN,
|
|
t: string,
|
|
name: string,
|
|
res: any,
|
|
outputs: any | undefined,
|
|
) => {
|
|
assert.strictEqual(t, "pulumi:pulumi:Stack");
|
|
assert.deepStrictEqual(outputs, {
|
|
a: {
|
|
x: 99,
|
|
y: "z",
|
|
},
|
|
b: 42,
|
|
c: {
|
|
d: "a",
|
|
e: false,
|
|
},
|
|
});
|
|
},
|
|
},
|
|
root_resource: {
|
|
pwd: path.join(base, "001.one_resource"),
|
|
expectResourceCount: 2,
|
|
showRootResourceRegistration: true,
|
|
registerResource: (ctx, dryrun, t, name, res, deps, custom, protect, parent) => {
|
|
if (t === "pulumi:pulumi:Stack") {
|
|
ctx.stackUrn = makeUrn(t, name);
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
}
|
|
|
|
assert.strictEqual(t, "test:index:MyResource");
|
|
assert.strictEqual(name, "testResource1");
|
|
assert.strictEqual(parent, ctx.stackUrn);
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
backcompat_root_resource: {
|
|
pwd: path.join(base, "001.one_resource"),
|
|
expectResourceCount: 2,
|
|
skipRootResourceEndpoints: true,
|
|
showRootResourceRegistration: true,
|
|
registerResource: (ctx, dryrun, t, name, res, deps, custom, protect, parent) => {
|
|
if (t === "pulumi:pulumi:Stack") {
|
|
ctx.stackUrn = makeUrn(t, name);
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
}
|
|
|
|
assert.strictEqual(t, "test:index:MyResource");
|
|
assert.strictEqual(name, "testResource1");
|
|
assert.strictEqual(parent, ctx.stackUrn);
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
property_dependencies: {
|
|
pwd: path.join(base, "020.property_dependencies"),
|
|
expectResourceCount: 5,
|
|
registerResource: (ctx, dryrun, t, name, res, deps, custom, protect, parent, provider, propertyDeps) => {
|
|
assert.strictEqual(t, "test:index:MyResource");
|
|
|
|
switch (name) {
|
|
case "resA":
|
|
assert.deepStrictEqual(deps, []);
|
|
assert.deepStrictEqual(propertyDeps, {});
|
|
break;
|
|
case "resB":
|
|
assert.deepStrictEqual(deps, ["resA"]);
|
|
assert.deepStrictEqual(propertyDeps, {});
|
|
break;
|
|
case "resC":
|
|
assert.deepStrictEqual(deps, ["resA", "resB"]);
|
|
assert.deepStrictEqual(propertyDeps, {
|
|
propA: ["resA"],
|
|
propB: ["resB"],
|
|
propC: [],
|
|
});
|
|
break;
|
|
case "resD":
|
|
assert.deepStrictEqual(deps, ["resA", "resB", "resC"]);
|
|
assert.deepStrictEqual(propertyDeps, {
|
|
propA: ["resA", "resB"],
|
|
propB: ["resC"],
|
|
propC: [],
|
|
});
|
|
break;
|
|
case "resE":
|
|
assert.deepStrictEqual(deps, ["resA", "resB", "resC", "resD"]);
|
|
assert.deepStrictEqual(propertyDeps, {
|
|
propA: ["resC"],
|
|
propB: ["resA", "resB"],
|
|
propC: [],
|
|
});
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return { urn: name, id: undefined, props: { outprop: "qux" } };
|
|
},
|
|
},
|
|
parent_child_dependencies: {
|
|
pwd: path.join(base, "021.parent_child_dependencies"),
|
|
main: "./index.js",
|
|
expectResourceCount: 2,
|
|
registerResource: (ctx, dryrun, t, name, res, deps) => {
|
|
switch (name) {
|
|
case "cust1":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
case "cust2":
|
|
assert.deepStrictEqual(deps, ["test:index:MyResource::cust1"]);
|
|
break;
|
|
default:
|
|
throw new Error("Didn't check: " + name);
|
|
}
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
parent_child_dependencies_2: {
|
|
pwd: path.join(base, "022.parent_child_dependencies_2"),
|
|
main: "./index.js",
|
|
expectResourceCount: 3,
|
|
registerResource: (ctx, dryrun, t, name, res, deps) => {
|
|
switch (name) {
|
|
case "cust1":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
case "cust2":
|
|
assert.deepStrictEqual(deps, ["test:index:MyResource::cust1"]);
|
|
break;
|
|
case "cust3":
|
|
assert.deepStrictEqual(deps, ["test:index:MyResource::cust1"]);
|
|
break;
|
|
default:
|
|
throw new Error("Didn't check: " + name);
|
|
}
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
parent_child_dependencies_3: {
|
|
pwd: path.join(base, "023.parent_child_dependencies_3"),
|
|
main: "./index.js",
|
|
expectResourceCount: 1,
|
|
expectError: "Program exited with non-zero exit code: 1",
|
|
},
|
|
parent_child_dependencies_4: {
|
|
pwd: path.join(base, "024.parent_child_dependencies_4"),
|
|
main: "./index.js",
|
|
expectResourceCount: 3,
|
|
registerResource: (ctx, dryrun, t, name, res, deps) => {
|
|
switch (name) {
|
|
case "cust1":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
case "cust2":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
case "comp1":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
default:
|
|
throw new Error("Didn't check: " + name);
|
|
}
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
parent_child_dependencies_5: {
|
|
pwd: path.join(base, "025.parent_child_dependencies_5"),
|
|
main: "./index.js",
|
|
expectResourceCount: 4,
|
|
registerResource: (ctx, dryrun, t, name, res, deps) => {
|
|
switch (name) {
|
|
case "cust1":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
case "cust2":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
case "comp1":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
case "res1":
|
|
assert.deepStrictEqual(deps, [
|
|
"test:index:MyCustomResource::cust1",
|
|
"test:index:MyCustomResource::cust2",
|
|
]);
|
|
break;
|
|
default:
|
|
throw new Error("Didn't check: " + name);
|
|
}
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
parent_child_dependencies_6: {
|
|
pwd: path.join(base, "026.parent_child_dependencies_6"),
|
|
main: "./index.js",
|
|
expectResourceCount: 6,
|
|
registerResource: (ctx, dryrun, t, name, res, deps) => {
|
|
switch (name) {
|
|
case "comp1":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
case "cust1":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
case "comp2":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
case "cust2":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
case "cust3":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
case "res1":
|
|
assert.deepStrictEqual(deps, [
|
|
"test:index:MyCustomResource::cust1",
|
|
"test:index:MyCustomResource::cust2",
|
|
"test:index:MyCustomResource::cust3",
|
|
]);
|
|
break;
|
|
default:
|
|
throw new Error("Didn't check: " + name);
|
|
}
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
parent_child_dependencies_7: {
|
|
pwd: path.join(base, "027.parent_child_dependencies_7"),
|
|
main: "./index.js",
|
|
expectResourceCount: 10,
|
|
registerResource: (ctx, dryrun, t, name, res, deps) => {
|
|
switch (name) {
|
|
case "comp1":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
case "cust1":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
case "comp2":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
case "cust2":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
case "cust3":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
case "cust4":
|
|
assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust2"]);
|
|
break;
|
|
case "res1":
|
|
assert.deepStrictEqual(deps, [
|
|
"test:index:MyCustomResource::cust1",
|
|
"test:index:MyCustomResource::cust2",
|
|
"test:index:MyCustomResource::cust3",
|
|
]);
|
|
break;
|
|
case "res2":
|
|
assert.deepStrictEqual(deps, [
|
|
"test:index:MyCustomResource::cust2",
|
|
"test:index:MyCustomResource::cust3",
|
|
]);
|
|
break;
|
|
case "res3":
|
|
assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust2"]);
|
|
break;
|
|
case "res4":
|
|
assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust4"]);
|
|
break;
|
|
default:
|
|
throw new Error("Didn't check: " + name);
|
|
}
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
parent_child_dependencies_8: {
|
|
pwd: path.join(base, "028.parent_child_dependencies_8"),
|
|
main: "./index.js",
|
|
expectResourceCount: 6,
|
|
registerResource: (ctx, dryrun, t, name, res, deps) => {
|
|
switch (name) {
|
|
case "comp1":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
case "cust1":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
case "cust2":
|
|
assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust1"]);
|
|
break;
|
|
case "res1":
|
|
assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust1"]);
|
|
break;
|
|
case "res2":
|
|
assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust1"]);
|
|
break;
|
|
case "res3":
|
|
assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust2"]);
|
|
break;
|
|
default:
|
|
throw new Error("Didn't check: " + name);
|
|
}
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
parent_child_dependencies_9: {
|
|
pwd: path.join(base, "029.parent_child_dependencies_9"),
|
|
main: "./index.js",
|
|
expectResourceCount: 3,
|
|
registerResource: (ctx, dryrun, t, name, res, deps) => {
|
|
switch (name) {
|
|
case "cust1":
|
|
assert.deepStrictEqual(deps, []);
|
|
break;
|
|
case "cust2":
|
|
assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust1"]);
|
|
break;
|
|
case "res1":
|
|
assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust1"]);
|
|
break;
|
|
default:
|
|
throw new Error("Didn't check: " + name);
|
|
}
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
run_error: {
|
|
pwd: path.join(base, "040.run_error"),
|
|
expectResourceCount: 0,
|
|
// We should get the error message saying that a message was reported and the
|
|
// host should bail.
|
|
expectBail: true,
|
|
},
|
|
component_opt_single_provider: {
|
|
pwd: path.join(base, "041.component_opt_single_provider"),
|
|
expectResourceCount: 240,
|
|
registerResource: parentDefaultsRegisterResource,
|
|
},
|
|
component_opt_providers_array: {
|
|
pwd: path.join(base, "042.component_opt_providers_array"),
|
|
expectResourceCount: 240,
|
|
registerResource: parentDefaultsRegisterResource,
|
|
},
|
|
depends_on_non_resource: {
|
|
pwd: path.join(base, "043.depends_on_non_resource"),
|
|
expectResourceCount: 0,
|
|
// We should get the error message saying that a message was reported and the
|
|
// host should bail.
|
|
expectBail: true,
|
|
expectedLogs: {
|
|
count: 1,
|
|
ignoreDebug: true,
|
|
},
|
|
log: (ctx: any, severity: any, message: string) => {
|
|
if (severity === engineproto.LogSeverity.ERROR) {
|
|
if (message.indexOf("'dependsOn' was passed a value that was not a Resource.") < 0) {
|
|
throw new Error("Unexpected error: " + message);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
ignore_changes: {
|
|
pwd: path.join(base, "045.ignore_changes"),
|
|
expectResourceCount: 1,
|
|
registerResource: (
|
|
ctx: any,
|
|
dryrun: boolean,
|
|
t: string,
|
|
name: string,
|
|
res: any,
|
|
dependencies?: string[],
|
|
custom?: boolean,
|
|
protect?: boolean,
|
|
parent?: string,
|
|
provider?: string,
|
|
propertyDeps?: any,
|
|
ignoreChanges?: string[],
|
|
) => {
|
|
if (name === "testResource") {
|
|
assert.deepStrictEqual(ignoreChanges, ["ignoredProperty"]);
|
|
}
|
|
return {
|
|
urn: makeUrn(t, name),
|
|
id: name,
|
|
props: {},
|
|
};
|
|
},
|
|
},
|
|
versions: {
|
|
pwd: path.join(base, "044.versions"),
|
|
expectResourceCount: 3,
|
|
registerResource: (
|
|
ctx: any,
|
|
dryrun: boolean,
|
|
t: string,
|
|
name: string,
|
|
res: any,
|
|
dependencies?: string[],
|
|
custom?: boolean,
|
|
protect?: boolean,
|
|
parent?: string,
|
|
provider?: string,
|
|
propertyDeps?: any,
|
|
ignoreChanges?: string[],
|
|
version?: string,
|
|
importID?: string,
|
|
replaceOnChanges?: string[],
|
|
) => {
|
|
switch (name) {
|
|
case "testResource":
|
|
assert.strictEqual("0.19.1", version);
|
|
break;
|
|
case "testResource2":
|
|
assert.strictEqual("0.19.2", version);
|
|
break;
|
|
case "testResource3":
|
|
assert.strictEqual("", version);
|
|
break;
|
|
default:
|
|
assert.fail(`unknown resource: ${name}`);
|
|
}
|
|
return {
|
|
urn: makeUrn(t, name),
|
|
id: name,
|
|
props: {},
|
|
};
|
|
},
|
|
invoke: (ctx: any, tok: string, args: any, version: string) => {
|
|
switch (tok) {
|
|
case "invoke:index:doit":
|
|
assert.strictEqual(version, "0.19.1");
|
|
break;
|
|
case "invoke:index:doit_v2":
|
|
assert.strictEqual(version, "0.19.2");
|
|
break;
|
|
case "invoke:index:doit_noversion":
|
|
assert.strictEqual(version, "");
|
|
break;
|
|
default:
|
|
assert.fail(`unknown invoke: ${tok}`);
|
|
}
|
|
|
|
return {
|
|
failures: [],
|
|
ret: args,
|
|
};
|
|
},
|
|
readResource: (ctx: any, t: string, name: string, id: string, par: string, state: any, version: string) => {
|
|
switch (name) {
|
|
case "foo":
|
|
assert.strictEqual(version, "0.20.0");
|
|
break;
|
|
case "foo_noversion":
|
|
assert.strictEqual(version, "");
|
|
break;
|
|
default:
|
|
assert.fail(`unknown read: ${name}`);
|
|
}
|
|
return {
|
|
urn: makeUrn(t, name),
|
|
props: state,
|
|
};
|
|
},
|
|
},
|
|
// A program that imports a single resource.
|
|
import_resource: {
|
|
pwd: path.join(base, "030.import_resource"),
|
|
expectResourceCount: 1,
|
|
registerResource: (
|
|
ctx,
|
|
dryrun,
|
|
t,
|
|
name,
|
|
res,
|
|
deps,
|
|
custom,
|
|
protect,
|
|
parent,
|
|
provider,
|
|
propertyDeps,
|
|
ignoreChanges,
|
|
version,
|
|
importID,
|
|
) => {
|
|
assert.strictEqual(t, "test:index:MyResource");
|
|
assert.strictEqual(name, "testResource1");
|
|
assert.strictEqual(importID, "testID");
|
|
return { urn: makeUrn(t, name), id: importID, props: {} };
|
|
},
|
|
},
|
|
// Test stack outputs via exports.
|
|
recursive_stack_exports: {
|
|
pwd: path.join(base, "046.recursive_stack_exports"),
|
|
expectResourceCount: 1,
|
|
showRootResourceRegistration: true,
|
|
registerResource: (ctx, dryrun, t, name, res, deps, custom, protect, parent) => {
|
|
if (t === "pulumi:pulumi:Stack") {
|
|
ctx.stackUrn = makeUrn(t, name);
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
}
|
|
throw new Error();
|
|
},
|
|
registerResourceOutputs: (
|
|
ctx: any,
|
|
dryrun: boolean,
|
|
urn: URN,
|
|
t: string,
|
|
name: string,
|
|
res: any,
|
|
outputs: any | undefined,
|
|
) => {
|
|
assert.strictEqual(t, "pulumi:pulumi:Stack");
|
|
assert.deepStrictEqual(outputs, {
|
|
m: { a: { b: 1 } },
|
|
n: { a: { b: 1 } },
|
|
o: { b: 1 },
|
|
obj2: { x: { y: 1 } },
|
|
obj2_x: { y: 1 },
|
|
obj2_x_y: 1,
|
|
p: 1,
|
|
});
|
|
},
|
|
},
|
|
exported_function: {
|
|
pwd: path.join(base, "047.exported_function"),
|
|
expectResourceCount: 1,
|
|
showRootResourceRegistration: true,
|
|
registerResource: (ctx, dryrun, t, name, res, deps, custom, protect, parent) => {
|
|
if (t === "pulumi:pulumi:Stack") {
|
|
ctx.stackUrn = makeUrn(t, name);
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
}
|
|
throw new Error();
|
|
},
|
|
registerResourceOutputs: (
|
|
ctx: any,
|
|
dryrun: boolean,
|
|
urn: URN,
|
|
t: string,
|
|
name: string,
|
|
res: any,
|
|
outputs: any | undefined,
|
|
) => {
|
|
assert.strictEqual(t, "pulumi:pulumi:Stack");
|
|
assert.deepStrictEqual(outputs, {
|
|
a: {
|
|
x: 99,
|
|
y: "z",
|
|
},
|
|
b: 42,
|
|
c: {
|
|
d: "a",
|
|
e: false,
|
|
},
|
|
});
|
|
},
|
|
},
|
|
exported_promise_function: {
|
|
pwd: path.join(base, "048.exported_promise_function"),
|
|
expectResourceCount: 1,
|
|
showRootResourceRegistration: true,
|
|
registerResource: (ctx, dryrun, t, name, res, deps, custom, protect, parent) => {
|
|
if (t === "pulumi:pulumi:Stack") {
|
|
ctx.stackUrn = makeUrn(t, name);
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
}
|
|
throw new Error();
|
|
},
|
|
registerResourceOutputs: (
|
|
ctx: any,
|
|
dryrun: boolean,
|
|
urn: URN,
|
|
t: string,
|
|
name: string,
|
|
res: any,
|
|
outputs: any | undefined,
|
|
) => {
|
|
assert.strictEqual(t, "pulumi:pulumi:Stack");
|
|
assert.deepStrictEqual(outputs, {
|
|
a: {
|
|
x: 99,
|
|
y: "z",
|
|
},
|
|
b: 42,
|
|
c: {
|
|
d: "a",
|
|
e: false,
|
|
},
|
|
});
|
|
},
|
|
},
|
|
exported_async_function: {
|
|
pwd: path.join(base, "049.exported_async_function"),
|
|
expectResourceCount: 1,
|
|
showRootResourceRegistration: true,
|
|
registerResource: (ctx, dryrun, t, name, res, deps, custom, protect, parent) => {
|
|
if (t === "pulumi:pulumi:Stack") {
|
|
ctx.stackUrn = makeUrn(t, name);
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
}
|
|
throw new Error();
|
|
},
|
|
registerResourceOutputs: (
|
|
ctx: any,
|
|
dryrun: boolean,
|
|
urn: URN,
|
|
t: string,
|
|
name: string,
|
|
res: any,
|
|
outputs: any | undefined,
|
|
) => {
|
|
assert.strictEqual(t, "pulumi:pulumi:Stack");
|
|
assert.deepStrictEqual(outputs, {
|
|
a: {
|
|
x: 99,
|
|
y: "z",
|
|
},
|
|
b: 42,
|
|
c: {
|
|
d: "a",
|
|
e: false,
|
|
},
|
|
});
|
|
},
|
|
},
|
|
resource_creation_in_function: {
|
|
pwd: path.join(base, "050.resource_creation_in_function"),
|
|
expectResourceCount: 2,
|
|
showRootResourceRegistration: true,
|
|
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any) => {
|
|
if (t === "pulumi:pulumi:Stack") {
|
|
ctx.stackUrn = makeUrn(t, name);
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
}
|
|
assert.strictEqual(t, "test:index:MyResource");
|
|
assert.strictEqual(name, "testResource1");
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
registerResourceOutputs: (
|
|
ctx: any,
|
|
dryrun: boolean,
|
|
urn: URN,
|
|
t: string,
|
|
name: string,
|
|
res: any,
|
|
outputs: any | undefined,
|
|
) => {
|
|
assert.strictEqual(t, "pulumi:pulumi:Stack");
|
|
assert.deepStrictEqual(outputs, {});
|
|
},
|
|
},
|
|
resource_creation_in_function_with_result: {
|
|
pwd: path.join(base, "051.resource_creation_in_function_with_result"),
|
|
expectResourceCount: 2,
|
|
showRootResourceRegistration: true,
|
|
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any) => {
|
|
if (t === "pulumi:pulumi:Stack") {
|
|
ctx.stackUrn = makeUrn(t, name);
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
}
|
|
assert.strictEqual(t, "test:index:MyResource");
|
|
assert.strictEqual(name, "testResource1");
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
registerResourceOutputs: (
|
|
ctx: any,
|
|
dryrun: boolean,
|
|
urn: URN,
|
|
t: string,
|
|
name: string,
|
|
res: any,
|
|
outputs: any | undefined,
|
|
) => {
|
|
assert.strictEqual(t, "pulumi:pulumi:Stack");
|
|
assert.deepStrictEqual(outputs, { a: 1 });
|
|
},
|
|
},
|
|
resource_creation_in_async_function_with_result: {
|
|
pwd: path.join(base, "052.resource_creation_in_async_function_with_result"),
|
|
expectResourceCount: 2,
|
|
showRootResourceRegistration: true,
|
|
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any) => {
|
|
if (t === "pulumi:pulumi:Stack") {
|
|
ctx.stackUrn = makeUrn(t, name);
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
}
|
|
assert.strictEqual(t, "test:index:MyResource");
|
|
assert.strictEqual(name, "testResource1");
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
registerResourceOutputs: (
|
|
ctx: any,
|
|
dryrun: boolean,
|
|
urn: URN,
|
|
t: string,
|
|
name: string,
|
|
res: any,
|
|
outputs: any | undefined,
|
|
) => {
|
|
assert.strictEqual(t, "pulumi:pulumi:Stack");
|
|
assert.deepStrictEqual(outputs, { a: 1 });
|
|
},
|
|
},
|
|
provider_invokes: {
|
|
pwd: path.join(base, "060.provider_invokes"),
|
|
expectResourceCount: 1,
|
|
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any) => {
|
|
return { urn: makeUrn(t, name), id: name === "p" ? "1" : undefined, props: undefined };
|
|
},
|
|
invoke: (ctx: any, tok: string, args: any, version: string, provider: string) => {
|
|
assert.strictEqual(provider, "pulumi:providers:test::p::1");
|
|
assert.strictEqual(tok, "test:index:echo");
|
|
assert.deepStrictEqual(args, {
|
|
a: "hello",
|
|
b: true,
|
|
c: [0.99, 42, { z: "x" }],
|
|
id: "some-id",
|
|
urn: "some-urn",
|
|
});
|
|
return { failures: undefined, ret: args };
|
|
},
|
|
},
|
|
provider_in_parent_invokes: {
|
|
pwd: path.join(base, "061.provider_in_parent_invokes"),
|
|
expectResourceCount: 2,
|
|
registerResource: (
|
|
ctx: any,
|
|
dryrun: boolean,
|
|
t: string,
|
|
name: string,
|
|
res: any,
|
|
dependencies?: string[],
|
|
custom?: boolean,
|
|
protect?: boolean,
|
|
parent?: string,
|
|
provider?: string,
|
|
) => {
|
|
return { urn: makeUrn(t, name), id: name === "p" ? "1" : undefined, props: undefined };
|
|
},
|
|
invoke: (ctx: any, tok: string, args: any, version: string, provider: string) => {
|
|
assert.strictEqual(provider, "pulumi:providers:test::p::1");
|
|
assert.strictEqual(tok, "test:index:echo");
|
|
assert.deepStrictEqual(args, {
|
|
a: "hello",
|
|
b: true,
|
|
c: [0.99, 42, { z: "x" }],
|
|
id: "some-id",
|
|
urn: "some-urn",
|
|
});
|
|
return { failures: undefined, ret: args };
|
|
},
|
|
},
|
|
providerref_invokes: {
|
|
pwd: path.join(base, "062.providerref_invokes"),
|
|
expectResourceCount: 1,
|
|
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any) => {
|
|
return { urn: makeUrn(t, name), id: name === "p" ? "1" : undefined, props: undefined };
|
|
},
|
|
invoke: (ctx: any, tok: string, args: any, version: string, provider: string) => {
|
|
assert.strictEqual(provider, "pulumi:providers:test::p::1");
|
|
assert.strictEqual(tok, "test:index:echo");
|
|
assert.deepStrictEqual(args, {
|
|
a: "hello",
|
|
b: true,
|
|
c: [0.99, 42, { z: "x" }],
|
|
id: "some-id",
|
|
urn: "some-urn",
|
|
});
|
|
return { failures: undefined, ret: args };
|
|
},
|
|
},
|
|
providerref_in_parent_invokes: {
|
|
pwd: path.join(base, "063.providerref_in_parent_invokes"),
|
|
expectResourceCount: 2,
|
|
registerResource: (
|
|
ctx: any,
|
|
dryrun: boolean,
|
|
t: string,
|
|
name: string,
|
|
res: any,
|
|
dependencies?: string[],
|
|
custom?: boolean,
|
|
protect?: boolean,
|
|
parent?: string,
|
|
provider?: string,
|
|
) => {
|
|
if (name === "c") {
|
|
assert.strictEqual(provider, "");
|
|
}
|
|
|
|
return { urn: makeUrn(t, name), id: name === "p" ? "1" : undefined, props: undefined };
|
|
},
|
|
invoke: (ctx: any, tok: string, args: any, version: string, provider: string) => {
|
|
assert.strictEqual(provider, "pulumi:providers:test::p::1");
|
|
assert.strictEqual(tok, "test:index:echo");
|
|
assert.deepStrictEqual(args, {
|
|
a: "hello",
|
|
b: true,
|
|
c: [0.99, 42, { z: "x" }],
|
|
id: "some-id",
|
|
urn: "some-urn",
|
|
});
|
|
return { failures: undefined, ret: args };
|
|
},
|
|
},
|
|
async_components: {
|
|
pwd: path.join(base, "064.async_components"),
|
|
expectResourceCount: 5,
|
|
registerResource: (
|
|
ctx: any,
|
|
dryrun: boolean,
|
|
t: string,
|
|
name: string,
|
|
res: any,
|
|
dependencies?: string[],
|
|
custom?: boolean,
|
|
protect?: boolean,
|
|
parent?: string,
|
|
provider?: string,
|
|
) => {
|
|
if (name === "c" || name === "d") {
|
|
dependencies = dependencies || [];
|
|
dependencies.sort();
|
|
// resources 'c' and 'd' should see resources 'a' and 'b' as dependencies (even
|
|
// though they are async constructed by the component)
|
|
assert.deepStrictEqual(dependencies, ["test:index:CustResource::a", "test:index:CustResource::b"]);
|
|
}
|
|
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
// Create a resource with a large string to test grpcMaxMessageSize increase.
|
|
large_resource: {
|
|
pwd: path.join(base, "065.large_resource"),
|
|
expectResourceCount: 1,
|
|
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any) => {
|
|
const longString = "a".repeat(1024 * 1024 * 5);
|
|
assert.strictEqual(t, "test:index:MyLargeStringResource");
|
|
assert.strictEqual(name, "testResource1");
|
|
assert.deepStrictEqual(res, { largeStringProp: longString });
|
|
return {
|
|
urn: makeUrn(t, name),
|
|
id: name,
|
|
props: {
|
|
largeStringProp: "a".repeat(1024 * 1024 * 5),
|
|
},
|
|
};
|
|
},
|
|
},
|
|
replace_on_changes: {
|
|
pwd: path.join(base, "066.replace_on_changes"),
|
|
expectResourceCount: 1,
|
|
registerResource: (
|
|
ctx: any,
|
|
dryrun: boolean,
|
|
t: string,
|
|
name: string,
|
|
res: any,
|
|
dependencies?: string[],
|
|
custom?: boolean,
|
|
protect?: boolean,
|
|
parent?: string,
|
|
provider?: string,
|
|
propertyDeps?: any,
|
|
ignoreChanges?: string[],
|
|
version?: string,
|
|
importID?: string,
|
|
replaceOnChanges?: string[],
|
|
) => {
|
|
if (name === "testResource") {
|
|
assert.deepStrictEqual(replaceOnChanges, ["foo"]);
|
|
}
|
|
return {
|
|
urn: makeUrn(t, name),
|
|
id: name,
|
|
props: {},
|
|
};
|
|
},
|
|
},
|
|
// A program that allocates a single resource using a native ES module
|
|
native_es_module: {
|
|
// Dynamic import won't automatically resolve to /index.js on a directory, specifying explicitly
|
|
pwd: path.join(base, "067.native_es_module"),
|
|
main: "./index.js",
|
|
expectResourceCount: 1,
|
|
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any) => {
|
|
assert.strictEqual(t, "test:index:MyResource");
|
|
assert.strictEqual(name, "testResource1");
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
remote_component_providers: {
|
|
pwd: path.join(base, "068.remote_component_providers"),
|
|
expectResourceCount: 8,
|
|
registerResource: (
|
|
ctx: any,
|
|
dryrun: boolean,
|
|
t: string,
|
|
name: string,
|
|
res: any,
|
|
dependencies?: string[],
|
|
custom?: boolean,
|
|
protect?: boolean,
|
|
parent?: string,
|
|
provider?: string,
|
|
propertyDeps?: any,
|
|
ignoreChanges?: string[],
|
|
version?: string,
|
|
importID?: string,
|
|
replaceOnChanges?: string[],
|
|
providers?: any,
|
|
) => {
|
|
if (name === "singular" || name === "map" || name === "array") {
|
|
assert.strictEqual(provider, "pulumi:providers:test::myprovider::1");
|
|
assert.deepStrictEqual(Object.keys(providers), ["test"]);
|
|
}
|
|
if (name === "foo-singular" || name === "foo-map" || name === "foo-array") {
|
|
assert.strictEqual(provider, "");
|
|
assert.deepStrictEqual(Object.keys(providers), ["foo"]);
|
|
}
|
|
return { urn: makeUrn(t, name), id: name === "myprovider" ? "1" : undefined, props: undefined };
|
|
},
|
|
},
|
|
ambiguous_entrypoints: {
|
|
pwd: path.join(base, "069.ambiguous_entrypoints"),
|
|
expectResourceCount: 1,
|
|
expectedLogs: {
|
|
count: 1,
|
|
ignoreDebug: true,
|
|
},
|
|
log: (ctx: any, severity: number, message: string, urn: URN, streamId: number) => {
|
|
assert.strictEqual(
|
|
message,
|
|
"Found a TypeScript project containing an index.js file and no explicit entrypoint" +
|
|
" in Pulumi.yaml - Pulumi will use index.js",
|
|
);
|
|
},
|
|
},
|
|
unusual_alias_names: {
|
|
pwd: path.join(base, "070.unusual_alias_names"),
|
|
expectResourceCount: 4,
|
|
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any, ...args: any) => {
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
large_alias_counts: {
|
|
pwd: path.join(base, "071.large_alias_counts"),
|
|
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: {
|
|
pwd: 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 };
|
|
},
|
|
},
|
|
component_dependencies: {
|
|
pwd: path.join(base, "073.component_dependencies"),
|
|
expectResourceCount: 4,
|
|
registerResource: (
|
|
ctx: any,
|
|
dryrun: boolean,
|
|
t: string,
|
|
name: string,
|
|
res: any,
|
|
dependencies?: string[],
|
|
...args: any
|
|
) => {
|
|
if (name === "second") {
|
|
assert.deepStrictEqual(dependencies, ["test:index:MyCustomResource::firstChild"]);
|
|
}
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
source_position: {
|
|
pwd: path.join(base, "074.source_position"),
|
|
expectResourceCount: 2,
|
|
registerResource: (
|
|
ctx: any,
|
|
dryrun: boolean,
|
|
t: string,
|
|
name: string,
|
|
res: any,
|
|
dependencies?: string[],
|
|
custom?: boolean,
|
|
protect?: boolean,
|
|
parent?: string,
|
|
provider?: string,
|
|
propertyDeps?: any,
|
|
ignoreChanges?: string[],
|
|
version?: string,
|
|
importID?: string,
|
|
replaceOnChanges?: string[],
|
|
providers?: any,
|
|
sourcePosition?: runtime.SourcePosition,
|
|
) => {
|
|
assert(sourcePosition !== undefined);
|
|
assert(sourcePosition.uri.endsWith("index.js"));
|
|
|
|
switch (name) {
|
|
case "custom":
|
|
assert.strictEqual(sourcePosition.line, 2);
|
|
break;
|
|
case "component":
|
|
assert.strictEqual(sourcePosition.line, 2);
|
|
break;
|
|
default:
|
|
throw new Error(`unexpected resource ${name}`);
|
|
}
|
|
|
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
|
},
|
|
},
|
|
invoke_output_depends_on: {
|
|
pwd: path.join(base, "075.invoke_output_depends_on"),
|
|
expectResourceCount: 1,
|
|
invoke: (ctx: any, tok: string, args: any, version: string, provider: string) => {
|
|
assert.strictEqual(tok, "test:index:echo");
|
|
assert.deepStrictEqual(args, { dependency: { resolved: true } });
|
|
return { failures: undefined, ret: args };
|
|
},
|
|
},
|
|
invoke_output_depends_on_non_resource: {
|
|
pwd: path.join(base, "076.invoke_output_depends_on_non_resource"),
|
|
expectResourceCount: 0,
|
|
// We should get the error message saying that a message was reported and the
|
|
// host should bail.
|
|
expectBail: true,
|
|
expectedLogs: {
|
|
count: 1,
|
|
ignoreDebug: true,
|
|
},
|
|
log: (ctx: any, severity: any, message: string) => {
|
|
if (severity === engineproto.LogSeverity.ERROR) {
|
|
if (message.indexOf("'dependsOn' was passed a value that was not a Resource.") < 0) {
|
|
throw new Error("Unexpected error: " + message);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
};
|
|
|
|
for (const casename of Object.keys(cases)) {
|
|
const opts: RunCase = cases[casename];
|
|
|
|
afterEach(async () => {
|
|
runCleanup();
|
|
});
|
|
|
|
const testFn = opts.only ? it.only : it;
|
|
|
|
testFn(`run test: ${casename} (pwd=${opts.pwd},main=${opts.main})`, async () => {
|
|
// For each test case, run it twice: first to preview and then to update.
|
|
for (const dryrun of [true, false]) {
|
|
// First we need to mock the resource monitor.
|
|
const ctx: any = {};
|
|
const regs: any = {};
|
|
let rootResource: string | undefined;
|
|
let regCnt = 0;
|
|
let logCnt = 0;
|
|
let logError = undefined; // Used to track errors or assertion failures in the log callback
|
|
const monitor = await createMockEngineAsync(
|
|
opts,
|
|
// Invoke callback
|
|
(call: any, callback: any) => {
|
|
const resp = new providerproto.InvokeResponse();
|
|
if (opts.invoke) {
|
|
const req: any = call.request;
|
|
const args: any = req.getArgs().toJavaScript();
|
|
const version: string = req.getVersion();
|
|
const { failures, ret } = opts.invoke(ctx, req.getTok(), args, version, req.getProvider());
|
|
resp.setFailuresList(failures);
|
|
resp.setReturn(gstruct.Struct.fromJavaScript(ret));
|
|
}
|
|
callback(undefined, resp);
|
|
},
|
|
// ReadResource callback.
|
|
(call: any, callback: any) => {
|
|
const req: any = call.request;
|
|
const resp = new resproto.ReadResourceResponse();
|
|
if (opts.readResource) {
|
|
const t = req.getType();
|
|
const name = req.getName();
|
|
const id = req.getId();
|
|
const par = req.getParent();
|
|
const state = req.getProperties().toJavaScript();
|
|
const version = req.getVersion();
|
|
const { urn, props } = opts.readResource(ctx, t, name, id, par, state, version);
|
|
resp.setUrn(urn);
|
|
resp.setProperties(gstruct.Struct.fromJavaScript(props));
|
|
}
|
|
callback(undefined, resp);
|
|
},
|
|
// RegisterResource callback
|
|
(call: any, callback: any) => {
|
|
const resp = new resproto.RegisterResourceResponse();
|
|
const req: any = call.request;
|
|
// Skip the automatically generated root component resource.
|
|
if (req.getType() !== runtime.rootPulumiStackTypeName || opts.showRootResourceRegistration) {
|
|
if (opts.registerResource) {
|
|
const t = req.getType();
|
|
const name = req.getName();
|
|
const res: any = req.getObject().toJavaScript();
|
|
const deps: string[] = req.getDependenciesList().sort();
|
|
const custom: boolean = req.getCustom();
|
|
const protect: boolean = req.getProtect();
|
|
const parent: string = req.getParent();
|
|
const provider: string = req.getProvider();
|
|
const ignoreChanges: string[] = req.getIgnorechangesList().sort();
|
|
const replaceOnChanges: string[] = req.getReplaceonchangesList().sort();
|
|
const propertyDeps: any = Array.from(req.getPropertydependenciesMap().entries()).reduce(
|
|
(o: any, [key, value]: any) => {
|
|
return { ...o, [key]: value.getUrnsList().sort() };
|
|
},
|
|
{},
|
|
);
|
|
const version: string = req.getVersion();
|
|
const importID: string = req.getImportid();
|
|
const providers: any = Array.from(req.getProvidersMap().entries()).reduce(
|
|
(o: any, [key, value]: any) => {
|
|
return { ...o, [key]: value };
|
|
},
|
|
{},
|
|
);
|
|
const rpcSourcePosition = req.getSourceposition();
|
|
let sourcePosition: runtime.SourcePosition | undefined;
|
|
if (rpcSourcePosition) {
|
|
sourcePosition = {
|
|
uri: rpcSourcePosition.getUri(),
|
|
line: rpcSourcePosition.getLine(),
|
|
column: rpcSourcePosition.getColumn(),
|
|
};
|
|
}
|
|
const { urn, id, props } = opts.registerResource(
|
|
ctx,
|
|
dryrun,
|
|
t,
|
|
name,
|
|
res,
|
|
deps,
|
|
custom,
|
|
protect,
|
|
parent,
|
|
provider,
|
|
propertyDeps,
|
|
ignoreChanges,
|
|
version,
|
|
importID,
|
|
replaceOnChanges,
|
|
providers,
|
|
sourcePosition,
|
|
);
|
|
resp.setUrn(urn);
|
|
resp.setId(id);
|
|
resp.setObject(gstruct.Struct.fromJavaScript(props));
|
|
if (urn) {
|
|
regs[urn] = { t: t, name: name, props: props };
|
|
}
|
|
}
|
|
regCnt++;
|
|
}
|
|
callback(undefined, resp);
|
|
},
|
|
// RegisterResourceOutputs callback
|
|
(call: any, callback: any) => {
|
|
const req: any = call.request;
|
|
const urn = req.getUrn();
|
|
const res = regs[urn];
|
|
if (res) {
|
|
if (opts.registerResourceOutputs) {
|
|
const outs: any = req.getOutputs().toJavaScript();
|
|
opts.registerResourceOutputs(ctx, dryrun, urn, res.t, res.name, res.props, outs);
|
|
}
|
|
}
|
|
callback(undefined, new gempty.Empty());
|
|
},
|
|
// Log callback
|
|
(call: any, callback: any) => {
|
|
const req: any = call.request;
|
|
const severity = req.getSeverity();
|
|
const message = req.getMessage();
|
|
const urn = req.getUrn();
|
|
const streamId = req.getStreamid();
|
|
if (severity === engineproto.LogSeverity.ERROR) {
|
|
console.log("log: " + message);
|
|
}
|
|
if (opts.expectedLogs) {
|
|
if (!opts.expectedLogs.ignoreDebug || severity !== engineproto.LogSeverity.DEBUG) {
|
|
logCnt++;
|
|
if (opts.log) {
|
|
try {
|
|
opts.log(ctx, severity, message, urn, streamId);
|
|
} catch (e) {
|
|
logError = e;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
callback(undefined, new gempty.Empty());
|
|
},
|
|
// GetRootResource callback
|
|
(call: any, callback: any) => {
|
|
let root: { urn: string };
|
|
if (opts.getRootResource) {
|
|
root = opts.getRootResource(ctx);
|
|
} else {
|
|
root = { urn: rootResource! };
|
|
}
|
|
|
|
const resp = new engineproto.GetRootResourceResponse();
|
|
resp.setUrn(root.urn);
|
|
callback(undefined, resp);
|
|
},
|
|
// SetRootResource callback
|
|
(call: any, callback: any) => {
|
|
const req: any = call.request;
|
|
const urn: string = req.getUrn();
|
|
if (opts.setRootResource) {
|
|
opts.setRootResource(ctx, urn);
|
|
} else {
|
|
rootResource = urn;
|
|
}
|
|
|
|
callback(undefined, new engineproto.SetRootResourceResponse());
|
|
},
|
|
// SupportsFeature callback
|
|
(call: any, callback: any) => {
|
|
const resp = new resproto.SupportsFeatureResponse();
|
|
resp.setHassupport(true);
|
|
callback(undefined, resp);
|
|
},
|
|
);
|
|
|
|
// Next, go ahead and spawn a new language host that connects to said monitor.
|
|
const langHost = serveLanguageHostProcess(monitor.addr);
|
|
const langHostAddr: string = await langHost.addr;
|
|
|
|
// Fake up a client RPC connection to the language host so that we can invoke run.
|
|
const langHostClient = new langrpc.LanguageRuntimeClient(
|
|
langHostAddr,
|
|
grpc.credentials.createInsecure(),
|
|
);
|
|
|
|
// Invoke our little test program; it will allocate a few resources, which we will record. It will
|
|
// throw an error if anything doesn't look right, which gets reflected back in the run results.
|
|
const [runError, runBail] = await mockRun(langHostClient, monitor.addr, opts, dryrun);
|
|
|
|
// Validate that everything looks right.
|
|
let expectError = opts.expectError;
|
|
if (expectError === undefined) {
|
|
expectError = "";
|
|
}
|
|
assert.strictEqual(runError, expectError, `Expected an error of "${expectError}"; got "${runError}"`);
|
|
|
|
let expectBail = opts.expectBail;
|
|
if (expectBail === undefined) {
|
|
expectBail = false;
|
|
}
|
|
assert.strictEqual(runBail, expectBail, `Expected an 'bail' of "${expectBail}"; got "${runBail}"`);
|
|
|
|
let expectResourceCount: number | undefined = opts.expectResourceCount;
|
|
if (expectResourceCount === undefined) {
|
|
expectResourceCount = 0;
|
|
}
|
|
assert.strictEqual(
|
|
regCnt,
|
|
expectResourceCount,
|
|
`Expected exactly ${expectResourceCount} resource registrations; got ${regCnt}`,
|
|
);
|
|
|
|
if (opts.expectedLogs) {
|
|
const logs = opts.expectedLogs;
|
|
if (logs.count) {
|
|
assert.strictEqual(logCnt, logs.count, `Expected exactly ${logs.count} logs; got ${logCnt}`);
|
|
}
|
|
}
|
|
|
|
if (logError) {
|
|
assert.fail(logError);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
function parentDefaultsRegisterResource(
|
|
ctx: any,
|
|
dryrun: boolean,
|
|
t: string,
|
|
name: string,
|
|
res: any,
|
|
dependencies?: string[],
|
|
custom?: boolean,
|
|
protect?: boolean,
|
|
parent?: string,
|
|
provider?: string,
|
|
) {
|
|
if (custom && !t.startsWith("pulumi:providers:")) {
|
|
let expectProtect = false;
|
|
let expectProviderName = "";
|
|
|
|
const rpath = name.split("/");
|
|
for (let i = 1; i < rpath.length; i++) {
|
|
switch (rpath[i]) {
|
|
case "c0":
|
|
case "r0":
|
|
// Pass through parent values
|
|
break;
|
|
case "c1":
|
|
case "r1":
|
|
// Force protect to false
|
|
expectProtect = false;
|
|
break;
|
|
case "c2":
|
|
case "r2":
|
|
// Force protect to true
|
|
expectProtect = true;
|
|
break;
|
|
case "c3":
|
|
case "r3":
|
|
// Force provider
|
|
expectProviderName = `${rpath.slice(0, i).join("/")}-p`;
|
|
break;
|
|
default:
|
|
assert.fail(`unexpected path element in name: ${rpath[i]}`);
|
|
}
|
|
}
|
|
|
|
// r3 explicitly overrides its provider.
|
|
if (rpath[rpath.length - 1] === "r3") {
|
|
expectProviderName = `${rpath.slice(0, rpath.length - 1).join("/")}-p`;
|
|
}
|
|
|
|
const providerName = provider!.split("::").reduce((_, v) => v);
|
|
|
|
assert.strictEqual(`${name}.protect: ${protect!}`, `${name}.protect: ${expectProtect}`);
|
|
assert.strictEqual(`${name}.provider: ${providerName}`, `${name}.provider: ${expectProviderName}`);
|
|
}
|
|
|
|
return { urn: makeUrn(t, name), id: name, props: {} };
|
|
}
|
|
|
|
function mockRun(
|
|
langHostClient: any,
|
|
monitor: string,
|
|
opts: RunCase,
|
|
dryrun: boolean,
|
|
): Promise<[string | undefined, boolean]> {
|
|
return new Promise<[string | undefined, boolean]>((resolve, reject) => {
|
|
const runReq = new langproto.RunRequest();
|
|
runReq.setMonitorAddress(monitor);
|
|
runReq.setProject(opts.project || "project");
|
|
runReq.setStack(opts.stack || "stack");
|
|
runReq.setPwd(opts.pwd);
|
|
runReq.setProgram(opts.main || ".");
|
|
if (opts.args) {
|
|
runReq.setArgsList(opts.args);
|
|
}
|
|
if (opts.config) {
|
|
const cfgmap = runReq.getConfigMap();
|
|
for (const cfgkey of Object.keys(opts.config)) {
|
|
cfgmap.set(cfgkey, opts.config[cfgkey]);
|
|
}
|
|
}
|
|
|
|
const info = new langproto.ProgramInfo();
|
|
info.setEntryPoint(opts.main || ".");
|
|
info.setRootDirectory(opts.pwd);
|
|
info.setProgramDirectory(opts.pwd);
|
|
runReq.setInfo(info);
|
|
|
|
runReq.setDryrun(dryrun);
|
|
langHostClient.run(runReq, (err: Error, res: any) => {
|
|
if (err && err.message.indexOf("UNAVAILABLE") !== 0) {
|
|
// Sometimes it takes a little bit until the engine is ready to accept connections. We'll
|
|
// retry after a short delay.
|
|
setTimeout(() => {
|
|
langHostClient.run(runReq, (e: Error, r: any) => {
|
|
if (e) {
|
|
reject(e);
|
|
} else {
|
|
resolve([r.getError(), r.getBail()]);
|
|
}
|
|
});
|
|
}, 200);
|
|
} else if (err) {
|
|
reject(err);
|
|
} else {
|
|
// The response has a single field, the error, if any, that occurred (blank means success).
|
|
resolve([res.getError(), res.getBail()]);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Despite the name, the "engine" RPC endpoint is only a logging endpoint. createMockEngine fires up a fake
|
|
// logging server so tests can assert that certain things get logged.
|
|
async function createMockEngineAsync(
|
|
opts: RunCase,
|
|
invokeCallback: (call: any, request: any) => any,
|
|
readResourceCallback: (call: any, request: any) => any,
|
|
registerResourceCallback: (call: any, request: any) => any,
|
|
registerResourceOutputsCallback: (call: any, request: any) => any,
|
|
logCallback: (call: any, request: any) => any,
|
|
getRootResourceCallback: (call: any, request: any) => any,
|
|
setRootResourceCallback: (call: any, request: any) => any,
|
|
supportsFeatureCallback: (call: any, request: any) => any,
|
|
) {
|
|
// The resource monitor is hosted in the current process so it can record state, etc.
|
|
const server = new grpc.Server({
|
|
"grpc.max_receive_message_length": runtime.maxRPCMessageSize,
|
|
});
|
|
server.addService(resrpc.ResourceMonitorService, {
|
|
supportsFeature: supportsFeatureCallback,
|
|
invoke: invokeCallback,
|
|
streamInvoke: () => {
|
|
throw new Error("StreamInvoke not implemented in mock engine");
|
|
},
|
|
readResource: readResourceCallback,
|
|
registerResource: registerResourceCallback,
|
|
registerResourceOutputs: registerResourceOutputsCallback,
|
|
});
|
|
|
|
let engineImpl: grpc.UntypedServiceImplementation = {
|
|
log: logCallback,
|
|
};
|
|
|
|
if (!opts.skipRootResourceEndpoints) {
|
|
engineImpl = {
|
|
...engineImpl,
|
|
getRootResource: getRootResourceCallback,
|
|
setRootResource: setRootResourceCallback,
|
|
};
|
|
}
|
|
|
|
server.addService(enginerpc.EngineService, engineImpl);
|
|
|
|
const port = await new Promise<number>((resolve, reject) => {
|
|
server.bindAsync("127.0.0.1:0", grpc.ServerCredentials.createInsecure(), (err, p) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve(p);
|
|
}
|
|
});
|
|
});
|
|
|
|
cleanup(async () => server.forceShutdown());
|
|
|
|
return { server: server, addr: `127.0.0.1:${port}` };
|
|
}
|
|
|
|
function serveLanguageHostProcess(engineAddr: string): { proc: childProcess.ChildProcess; addr: Promise<string> } {
|
|
// A quick note about this:
|
|
//
|
|
// Normally, `pulumi-language-nodejs` launches `./node-modules/@pulumi/pulumi/cmd/run` which is
|
|
// responsible for setting up some state and then running the actual user program. However, in this case,
|
|
// we don't have a folder structure like the above because we are setting the package as we've built it,
|
|
// not it installed in another application.
|
|
//
|
|
// `pulumi-language-nodejs` allows us to set `PULUMI_LANGUAGE_NODEJS_RUN_PATH` in the environment, and
|
|
// when set, it will use that path instead of the default value. For our tests here, we set it and point
|
|
// at the just built version of run.
|
|
//
|
|
// We set this to an absolute path because the runtime will search for the module from the programs
|
|
// directory which is changed by by the pwd option.
|
|
|
|
process.env.PULUMI_LANGUAGE_NODEJS_RUN_PATH = path.normalize(path.join(__dirname, "..", "..", "..", "cmd", "run"));
|
|
const proc = childProcess.spawn("pulumi-language-nodejs", [engineAddr]);
|
|
|
|
// Hook the first line so we can parse the address. Then we hook the rest to print for debugging purposes, and
|
|
// hand back the resulting process object plus the address we plucked out.
|
|
let addrResolve: ((addr: string) => void) | undefined;
|
|
const addr = new Promise<string>((resolve) => {
|
|
addrResolve = resolve;
|
|
});
|
|
proc.stdout.on("data", (data: string) => {
|
|
const dataString: string = stripEOL(data);
|
|
if (addrResolve) {
|
|
// The first line is the address; strip off the newline and resolve the promise.
|
|
addrResolve(`127.0.0.1:${dataString}`);
|
|
addrResolve = undefined;
|
|
} else {
|
|
console.log(`langhost.stdout: ${dataString}`);
|
|
}
|
|
});
|
|
proc.stderr.on("data", (data: string | Buffer) => {
|
|
console.error(`langhost.stderr: ${stripEOL(data)}`);
|
|
});
|
|
|
|
cleanup(async () => {
|
|
proc.kill("SIGKILL");
|
|
});
|
|
|
|
return { proc: proc, addr: addr };
|
|
}
|
|
|
|
function stripEOL(data: string | Buffer): string {
|
|
let dataString: string;
|
|
if (typeof data === "string") {
|
|
dataString = data;
|
|
} else {
|
|
dataString = data.toString("utf-8");
|
|
}
|
|
return dataString.trimRight();
|
|
}
|