Support for stack outputs (#581)

Adds support for top-level exports in the main script of a Pulumi Program to be captured as stack-level output properties.

This create a new `pulumi:pulumi:Stack` component as the root of the resource tree in all Pulumi programs.  That resources has properties for each top-level export in the Node.js script.

Running `pulumi stack` will display the current value of these outputs.
This commit is contained in:
Luke Hoban 2017-11-17 15:22:41 -08:00 committed by GitHub
parent d840a86a0a
commit 96e4b74b15
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 65 additions and 13 deletions

View file

@ -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
}),

View file

@ -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.

View file

@ -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));

View file

@ -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

View file

@ -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.
*/

View file

@ -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);
},
);