From 87e5a441f58b3e4ca622a5d417509363d925abb7 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Thu, 3 Jan 2019 10:03:11 -0800 Subject: [PATCH] Convert resource to pojo objects when used as stack outputs. (#2311) --- CHANGELOG.md | 2 + sdk/nodejs/runtime/stack.ts | 99 ++++++++++++++++++++++++++++++++++++- 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1f0ca1cd..306c9ad00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ## 0.16.9 (Released December 24th, 2018) +- Exporting a Resource from an application Stack now exports it as a rich recursive pojo instead of just being an opaque URN (fixes https://github.com/pulumi/pulumi/issues/1858). + ### Improvements - Update the error message when When `pulumi` commands fail to detect your project to mention that `pulumi new` can be used to create a new project (fixes [pulumi/pulumi#2234](https://github.com/pulumi/pulumi/issues/2234)) diff --git a/sdk/nodejs/runtime/stack.ts b/sdk/nodejs/runtime/stack.ts index cbbab64dc..b0b44bd1b 100644 --- a/sdk/nodejs/runtime/stack.ts +++ b/sdk/nodejs/runtime/stack.ts @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +import * as asset from "../asset"; import { getProject, getStack } from "../metadata"; -import { ComponentResource, Inputs, Output, output } from "../resource"; +import { ComponentResource, Inputs, Output, output, Resource } from "../resource"; import { getRootResource, setRootResource } from "./settings"; /** @@ -64,9 +65,103 @@ class Stack extends ComponentResource { try { outputs = init(); } finally { - super.registerOutputs(outputs); + // We want to expose stack outputs as simple pojo objects (including Resources). This + // helps ensure that outputs can point to resources, and that that is stored and + // presented as something reasonable, and not as just an id/urn in the case of + // Resources. + const massaged = output(outputs).apply(v => massage(v, new Set())); + super.registerOutputs(massaged); } return outputs; } } + +async function massage(prop: any, seenObjects: Set): Promise { + if (prop === undefined || + prop === null || + typeof prop === "boolean" || + typeof prop === "number" || + typeof prop === "string") { + + return prop; + } + + if (prop instanceof Promise) { + return await massage(await prop, seenObjects); + } + + if (Output.isInstance(prop)) { + return await massage(await prop.promise(), seenObjects); + } + + // from this point on, we have complex objects. If we see them again, we don't want to emit + // them again fully or else we'd loop infinitely. + if (seenObjects.has(prop)) { + // Note: for Resources we hit again, emit their urn so cycles can be easily understood + // in the pojo objects. + if (Resource.isInstance(prop)) { + return await massage(prop.urn, seenObjects); + } + + return undefined; + } + + seenObjects.add(prop); + + if (asset.Asset.isInstance(prop)) { + if ((prop).path !== undefined) { + return { path: (prop).path }; + } + else if ((prop).uri !== undefined) { + return { uri: (prop).uri }; + } + else if ((prop).text !== undefined) { + return { text: "..." }; + } + + return undefined; + } + + if (asset.Archive.isInstance(prop)) { + if ((prop).assets) { + return { assets: massage((prop).assets, seenObjects) }; + } + else if ((prop).path !== undefined) { + return { path: (prop).path }; + } + else if ((prop).uri !== undefined) { + return { uri: (prop).uri }; + } + + return undefined; + } + + if (Resource.isInstance(prop)) { + // Emit a resource as a normal pojo. But filter out all our internal properties so that + // they don't clutter the display/checkpoint with values not relevant to the application. + return serializeAllKeys(n => !n.startsWith("__")); + } + + if (prop instanceof Array) { + const result = []; + for (let i = 0; i < prop.length; i++) { + result[i] = await massage(prop[i], seenObjects); + } + + return result; + } + + return await serializeAllKeys(n => true); + + async function serializeAllKeys(include: (name: string) => boolean) { + const obj: Record = {}; + for (const k of Object.keys(prop)) { + if (include(k)) { + obj[k] = await massage(prop[k], seenObjects); + } + } + + return obj; + } +}