fix nodejs resource functions to properly propagate errors (#6644)

This commit is contained in:
Evan Boyle 2021-03-30 20:16:25 -07:00 committed by GitHub
parent 1140e9f2bf
commit d098f9181b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 93 additions and 17 deletions

View file

@ -18,6 +18,9 @@
### Bug Fixes
- [sdk/nodejs] Fix error propagation in registerResource and other resource methods.
[#6644](https://github.com/pulumi/pulumi/pull/6644)
- [automation/python] Fix passing of additional environment variables.
[#6639](https://github.com/pulumi/pulumi/pull/6639)

View file

@ -416,21 +416,34 @@ async function prepareResource(label: string, res: Resource, custom: boolean,
let resolveURN: (urn: URN, err?: Error) => void;
{
let resolveValue: (urn: URN) => void;
let rejectValue: (err: Error) => void;
let resolveIsKnown: (isKnown: boolean) => void;
let rejectIsKnown: (err: Error) => void;
(res as any).urn = new Output(
res,
debuggablePromise(
new Promise<URN>(resolve => resolveValue = resolve),
new Promise<URN>((resolve, reject) => {
resolveValue = resolve;
rejectValue = reject;
}),
`resolveURN(${label})`),
debuggablePromise(
new Promise<boolean>(resolve => resolveIsKnown = resolve),
new Promise<boolean>((resolve, reject) => {
resolveIsKnown = resolve;
rejectIsKnown = reject;
}),
`resolveURNIsKnown(${label})`),
/*isSecret:*/ Promise.resolve(false),
Promise.resolve(res));
resolveURN = (v, err) => {
resolveValue(v);
resolveIsKnown(err === undefined);
if (!!err) {
rejectValue(err);
rejectIsKnown(err);
} else {
resolveValue(v);
resolveIsKnown(true);
}
};
}
@ -438,19 +451,32 @@ async function prepareResource(label: string, res: Resource, custom: boolean,
let resolveID: ((v: any, performApply: boolean, err?: Error) => void) | undefined;
if (custom) {
let resolveValue: (v: ID) => void;
let rejectValue: (err: Error) => void;
let resolveIsKnown: (v: boolean) => void;
let rejectIsKnown: (err: Error) => void;
(res as any).id = new Output(
res,
debuggablePromise(new Promise<ID>(resolve => resolveValue = resolve),
debuggablePromise(new Promise<ID>((resolve, reject) => {
resolveValue = resolve;
rejectValue = reject;
}),
`resolveID(${label})`),
debuggablePromise(new Promise<boolean>(
resolve => resolveIsKnown = resolve), `resolveIDIsKnown(${label})`),
debuggablePromise(new Promise<boolean>((resolve, reject) => {
resolveIsKnown = resolve;
rejectIsKnown = reject;
}), `resolveIDIsKnown(${label})`),
Promise.resolve(false),
Promise.resolve(res));
resolveID = (v, isKnown, err) => {
resolveValue(v);
resolveIsKnown(err ? false : isKnown);
if (!!err) {
rejectValue(err);
rejectIsKnown(err);
} else {
resolveValue(v);
resolveIsKnown(isKnown);
}
};
}

View file

@ -52,31 +52,54 @@ export function transferProperties(onto: Resource, label: string, props: Inputs)
}
let resolveValue: (v: any) => void;
let rejectValue: (err: Error) => void;
let resolveIsKnown: (v: boolean) => void;
let rejectIsKnown: (err: Error) => void;
let resolveIsSecret: (v: boolean) => void;
let rejectIsSecret: (err: Error) => void;
let resolveDeps: (v: Resource[]) => void;
let rejectDeps: (err: Error) => void;
resolvers[k] = (v: any, isKnown: boolean, isSecret: boolean, deps: Resource[] = [], err?: Error) => {
resolveValue(v);
resolveIsKnown(err ? false : isKnown);
resolveIsSecret(isSecret);
resolveDeps(deps);
if (!!err) {
rejectValue(err);
rejectIsKnown(err);
rejectIsSecret(err);
rejectDeps(err);
} else {
resolveValue(v);
resolveIsKnown(isKnown);
resolveIsSecret(isSecret);
resolveDeps(deps);
}
};
const propString = Output.isInstance(props[k]) ? "Output<T>" : `${props[k]}`;
(<any>onto)[k] = new Output(
onto,
debuggablePromise(
new Promise<any>(resolve => resolveValue = resolve),
new Promise<any>((resolve, reject) => {
resolveValue = resolve;
rejectValue = reject;
}),
`transferProperty(${label}, ${k}, ${propString})`),
debuggablePromise(
new Promise<boolean>(resolve => resolveIsKnown = resolve),
new Promise<boolean>((resolve, reject) => {
resolveIsKnown = resolve;
rejectIsKnown = reject;
}),
`transferIsStable(${label}, ${k}, ${propString})`),
debuggablePromise(
new Promise<boolean>(resolve => resolveIsSecret = resolve),
new Promise<boolean>((resolve, reject) => {
resolveIsSecret = resolve;
rejectIsSecret = reject;
}),
`transferIsSecret(${label}, ${k}, ${propString})`),
debuggablePromise(
new Promise<Resource[]>(resolve => resolveDeps = resolve),
new Promise<Resource[]>((resolve, reject) => {
resolveDeps = resolve;
rejectDeps = reject;
}),
`transferDeps(${label}, ${k}, ${propString})`));
}

View file

@ -32,6 +32,12 @@ class TestCustomResource extends CustomResource {
}
}
class TestErrorResource extends CustomResource {
constructor(name: string) {
super("error", name, {});
}
}
class TestResourceModule implements runtime.ResourceModule {
construct(name: string, type: string, urn: string): Resource {
switch (type) {
@ -60,6 +66,8 @@ class TestMocks implements runtime.Mocks {
id: runtime.isDryRun() ? undefined : "test-id",
state: {},
};
case "error":
throw new Error("this is an intentional error");
default:
throw new Error(`unknown resource type ${type}`);
}
@ -349,4 +357,20 @@ describe("runtime", () => {
assert.deepEqual(deserialized["unregistered"], unregisteredID);
}));
});
describe("resource error handling", () => {
it("registerResource errors propagate appropriately", asyncTest(async () => {
runtime.setMocks(new TestMocks());
await assert.rejects(async () => {
const errResource = new TestErrorResource("test");
const customURN = await errResource.urn.promise();
const customID = await errResource.id.promise();
}, (err: Error) => {
const containsMessage = err.stack!.indexOf("this is an intentional error") >= 0;
const containsRegisterResource = err.stack!.indexOf("registerResource") >= 0;
return containsMessage && containsRegisterResource;
});
}));
});
});