diff --git a/cmd/stack.go b/cmd/stack.go index 729ef08d7..4638807e4 100644 --- a/cmd/stack.go +++ b/cmd/stack.go @@ -7,6 +7,8 @@ import ( "github.com/spf13/cobra" + "github.com/pulumi/pulumi/pkg/resource" + "github.com/pulumi/pulumi/pkg/resource/stack" "github.com/pulumi/pulumi/pkg/util/cmdutil" ) @@ -44,6 +46,7 @@ func newStackCmd() *cobra.Command { if len(config) > 0 { fmt.Printf("%v configuration variables set (see `pulumi config` for details)\n", len(config)) } + var stackResource *resource.State if snapshot == nil || len(snapshot.Resources) == 0 { fmt.Printf("No resources currently in this stack\n") } else { @@ -51,6 +54,10 @@ func newStackCmd() *cobra.Command { fmt.Printf("\n") fmt.Printf("%-48s %s\n", "TYPE", "NAME") for _, res := range snapshot.Resources { + if res.Type == stack.RootPulumiStackTypeName { + stackResource = res + continue + } fmt.Printf("%-48s %s\n", res.Type, res.URN.Name()) // If the ID and/or URN is requested, show it on the following line. It would be nice to do this @@ -62,6 +69,22 @@ func newStackCmd() *cobra.Command { fmt.Printf("\tURN: %s\n", res.URN) } } + if stackResource != nil { + fmt.Printf("\n") + // Note: Currently, components place their output properties into the `Inputs` of the resource, so + // we need to extract the outputs from the `Inputs`. + outputs := stack.SerializeResource(stackResource).Inputs + if len(outputs) == 0 { + fmt.Printf("No output values currently in this stack\n") + } else { + fmt.Printf("%v output values currently in this stack:\n", len(outputs)) + fmt.Printf("\n") + fmt.Printf("%-48s %s\n", "OUTPUT", "VALUE") + for key, val := range outputs { + fmt.Printf("%-48s %s\n", key, val) + } + } + } } return nil }), diff --git a/pkg/resource/stack/deployment.go b/pkg/resource/stack/deployment.go index aa030615c..a05e634dd 100644 --- a/pkg/resource/stack/deployment.go +++ b/pkg/resource/stack/deployment.go @@ -34,6 +34,9 @@ type Resource struct { Children []string `json:"children,omitempty" yaml:"children,omitempty"` // an optional list of child resources. } +// RootPulumiStackTypeName is the type name that will be used for the root component in the Pulumi resource tree. +const RootPulumiStackTypeName tokens.Type = "pulumi:pulumi:Stack" + // SerializeDeployment serializes an entire snapshot as a deploy record. func SerializeDeployment(snap *deploy.Snapshot) *Deployment { // Serialize all vertices and only include a vertex section if non-empty. diff --git a/sdk/nodejs/cmd/run/index.ts b/sdk/nodejs/cmd/run/index.ts index dad0313e0..15510ecac 100644 --- a/sdk/nodejs/cmd/run/index.ts +++ b/sdk/nodejs/cmd/run/index.ts @@ -4,6 +4,7 @@ import * as minimist from "minimist"; import * as path from "path"; +import * as pulumi from "../../"; import { RunError } from "../../errors"; import * as log from "../../log"; import * as runtime from "../../runtime"; @@ -145,9 +146,22 @@ export function main(args: string[]): void { } }); - // Now go ahead and execute the code. The process will remain alive until the message loop empties. - log.debug(`Running program '${program}' in pwd '${process.cwd()}' w/ args: ${programArgs}`); - require(program); + // Construct a `Stack` resource to represent the outputs of the program. + const stackResource = new pulumi.ComponentResource( + runtime.rootPulumiStackTypeName, + `${pulumi.getProject()}-${pulumi.getStack()}`, + [], + // We run the program inside this context so that it adopts all resources. + // + // IDEA: This will miss any resources created on other turns of the event loop. I think that's a fundamental + // problem with the current Component design though - not sure what else we could do here. + () => { + // Now go ahead and execute the code. The process will remain alive until the message loop empties. + log.debug(`Running program '${program}' in pwd '${process.cwd()}' w/ args: ${programArgs}`); + const outputs = require(program); + return outputs; + }, + ); } main(process.argv.slice(2)); diff --git a/sdk/nodejs/resource.ts b/sdk/nodejs/resource.ts index 6e04b44ba..becdcc8bb 100644 --- a/sdk/nodejs/resource.ts +++ b/sdk/nodejs/resource.ts @@ -145,7 +145,7 @@ export abstract class CustomResource extends Resource { * ComponentResource is a resource that aggregates one or more other child resources into a higher level abstraction. * The component resource itself is a resource, but does not require custom CRUD operations for provisioning. */ -export abstract class ComponentResource extends Resource { +export class ComponentResource extends Resource { /** * Creates and registers a new component resource. t is the fully qualified type token and name is the "name" part * to use in creating a stable and globally unique URN for the object. init is used to generate whatever children diff --git a/sdk/nodejs/runtime/settings.ts b/sdk/nodejs/runtime/settings.ts index 8db00742c..b58858b21 100644 --- a/sdk/nodejs/runtime/settings.ts +++ b/sdk/nodejs/runtime/settings.ts @@ -2,6 +2,13 @@ import { debuggablePromise } from "./debuggable"; +/** + * rootPulumiStackTypeName is the type name that should be used to construct the root component in the tree of Pulumi + * resources allocated by a deployment. This must be kept up to date with + * `github.com/pulumi/pulumi/pkg/resource/stack.RootPulumiStackTypeName`. + */ +export const rootPulumiStackTypeName = "pulumi:pulumi:Stack"; + /** * excessiveDebugOutput enables, well, pretty excessive debug output pertaining to resources and properties. */ diff --git a/sdk/nodejs/tests/runtime/langhost/run.spec.ts b/sdk/nodejs/tests/runtime/langhost/run.spec.ts index cbc02a9bc..c99ffb374 100644 --- a/sdk/nodejs/tests/runtime/langhost/run.spec.ts +++ b/sdk/nodejs/tests/runtime/langhost/run.spec.ts @@ -301,6 +301,7 @@ describe("rpc", () => { const ctx = {}; let rescnt = 0; const monitor = createMockResourceMonitor( + // Invoke callback (call: any, callback: any) => { const resp = new langproto.InvokeResponse(); if (opts.invoke) { @@ -313,18 +314,22 @@ describe("rpc", () => { } callback(undefined, resp); }, + // NewResources callback (call: any, callback: any) => { const resp = new langproto.NewResourceResponse(); - if (opts.createResource) { - const req: any = call.request; - const res: any = req.getObject().toJavaScript(); - const { id, urn, props } = - opts.createResource(ctx, dryrun, req.getType(), req.getName(), res); - resp.setId(id); - resp.setUrn(urn); - resp.setObject(gstruct.Struct.fromJavaScript(props)); + const req: any = call.request; + // Skip the automatically generated root component resource. + if (req.getType() !== runtime.rootPulumiStackTypeName) { + if (opts.createResource) { + const res: any = req.getObject().toJavaScript(); + const { id, urn, props } = + opts.createResource(ctx, dryrun, req.getType(), req.getName(), res); + resp.setId(id); + resp.setUrn(urn); + resp.setObject(gstruct.Struct.fromJavaScript(props)); + } + rescnt++; } - rescnt++; callback(undefined, resp); }, );