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:
parent
d840a86a0a
commit
96e4b74b15
23
cmd/stack.go
23
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
|
||||
}),
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue