diff --git a/CHANGELOG.md b/CHANGELOG.md index 3168683b8..a7aa866ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ - Allow setting backend URL explicitly in `Pulumi.yaml` file +- `StackReference` now has a `.getOutputSync` function to retrieve exported values from an existing + stack synchronously. This can be valuable when creating another stack that wants to base + flow-control off of the values of an existing stack (i.e. importing the information about all AZs + and basing logic off of that in a new stack). Note: this only works for importing values from + Stacks that have not exported `secrets`. + ## 0.17.17 (Released June 12, 2019) ### Improvements diff --git a/sdk/nodejs/package.json b/sdk/nodejs/package.json index fe64dad62..76d80eb3e 100644 --- a/sdk/nodejs/package.json +++ b/sdk/nodejs/package.json @@ -21,7 +21,8 @@ "source-map-support": "^0.4.16", "ts-node": "^7.0.0", "typescript": "^3.0.0", - "upath": "^1.1.0" + "upath": "^1.1.0", + "deasync": "^0.1.15" }, "devDependencies": { "@types/minimist": "^1.2.0", @@ -30,6 +31,7 @@ "@types/normalize-package-data": "^2.4.0", "@types/read-package-tree": "^5.2.0", "@types/semver": "^5.5.0", + "@types/deasync": "^0.1.0", "istanbul": "^0.4.5", "mocha": "^3.5.0", "node-gyp": "^3.6.2", diff --git a/sdk/nodejs/stackReference.ts b/sdk/nodejs/stackReference.ts index 9b24113b8..8d97f1b23 100644 --- a/sdk/nodejs/stackReference.ts +++ b/sdk/nodejs/stackReference.ts @@ -14,6 +14,7 @@ import { all, Input, Output, output } from "./output"; import { CustomResource, CustomResourceOptions } from "./resource"; +import { promiseResult } from "./utils"; /** * Manages a reference to a Pulumi stack. The referenced stack's outputs are available via the @@ -56,6 +57,25 @@ export class StackReference extends CustomResource { public getOutput(name: Input): Output { return all([output(name), this.outputs]).apply(([n, os]) => os[n]); } + + /** + * Fetches the value promptly of the named stack output. May return undefined if the value is + * not known for some reason. + * + * This operation is not supported (and will throw) if any exported values of the StackReference + * are secrets. + * + * @param name The name of the stack output to fetch. + */ + public getOutputSync(name: string): any { + const out = this.getOutput(name); + const isSecret = promiseResult(out.isSecret); + if (isSecret) { + throw new Error("Cannot call [getOutputSync] if the referenced stack has secret outputs. Use [getOutput] instead."); + } + + return promiseResult(out.promise()); + } } /** diff --git a/sdk/nodejs/utils.ts b/sdk/nodejs/utils.ts index 51438ff43..7c8000810 100644 --- a/sdk/nodejs/utils.ts +++ b/sdk/nodejs/utils.ts @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import * as deasync from "deasync"; + /** * Common code for doing RTTI typechecks. RTTI is done by having a boolean property on an object * with a special name (like "__resource" or "__asset"). This function checks that the object @@ -39,3 +41,42 @@ export function hasTrueBooleanMember(obj: any, memberName: string | number | sym return val === true; } + +/** + * Synchronously blocks until the result of this promise is computed. If the promise is rejected, + * this will throw the error the promise was rejected with. If this promise does not complete this + * will block indefinitely. + * + * Be very careful with this function. Only wait on a promise if you are certain it is safe to do + * so. + * + * @internal + */ +export function promiseResult(promise: Promise): T { + enum State { + running, + finishedSuccessfully, + finishedWithError, + } + + let result: T; + let error = undefined; + let state = State.running; + + promise.then( + val => { + result = val; + state = State.finishedSuccessfully; + }, + err => { + error = err; + state = State.finishedWithError; + }); + + deasync.loopWhile(() => state === State.running); + if (state === State.finishedWithError) { + throw error; + } + + return result!; +} diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index 3252d0946..6f08c285e 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -891,6 +891,16 @@ func TestStackReferenceNodeJS(t *testing.T) { Config: map[string]string{ "org": os.Getenv("PULUMI_TEST_OWNER"), }, + EditDirs: []integration.EditDir{ + { + Dir: "step1", + Additive: true, + }, + { + Dir: "step2", + Additive: true, + }, + }, } integration.ProgramTest(t, opts) } diff --git a/tests/integration/stack_reference/index.ts b/tests/integration/stack_reference/index.ts index 636ddc258..14992352e 100644 --- a/tests/integration/stack_reference/index.ts +++ b/tests/integration/stack_reference/index.ts @@ -6,3 +6,5 @@ let config = new pulumi.Config(); let org = config.require("org"); let slug = `${org}/${pulumi.getProject()}/${pulumi.getStack()}`; let a = new pulumi.StackReference(slug); + +export const val = ["a", "b"]; \ No newline at end of file diff --git a/tests/integration/stack_reference/step1/index.ts b/tests/integration/stack_reference/step1/index.ts new file mode 100644 index 000000000..55867a503 --- /dev/null +++ b/tests/integration/stack_reference/step1/index.ts @@ -0,0 +1,15 @@ +// Copyright 2016-2018, Pulumi Corporation. All rights reserved. + +import * as pulumi from "@pulumi/pulumi"; + +let config = new pulumi.Config(); +let org = config.require("org"); +let slug = `${org}/${pulumi.getProject()}/${pulumi.getStack()}`; +let a = new pulumi.StackReference(slug); + +const oldVal: string[] = a.getOutputSync("val"); +if (oldVal.length !== 2 || oldVal[0] !== "a" || oldVal[1] !== "b") { + throw new Error("Invalid result"); +} + +export const val2 = pulumi.secret(["a", "b"]); \ No newline at end of file diff --git a/tests/integration/stack_reference/step2/index.ts b/tests/integration/stack_reference/step2/index.ts new file mode 100644 index 000000000..ed31d2778 --- /dev/null +++ b/tests/integration/stack_reference/step2/index.ts @@ -0,0 +1,22 @@ +// Copyright 2016-2018, Pulumi Corporation. All rights reserved. + +import * as pulumi from "@pulumi/pulumi"; + +let config = new pulumi.Config(); +let org = config.require("org"); +let slug = `${org}/${pulumi.getProject()}/${pulumi.getStack()}`; +let a = new pulumi.StackReference(slug); + +let gotError = false; +try +{ + a.getOutputSync("val2"); +} +catch (err) +{ + gotError = true; +} + +if (!gotError) { + throw new Error("Expected to get error trying to read secret from stack reference."); +} \ No newline at end of file