Add `--json` flag to `pulumi stack ls`
We've had multiple users ask for this, so let's do it proactively
instead of waiting for
Fixes
@@ -18,11 +18,13 @@ import (
"fmt"
"sort"
"strconv"
+ "time"
"github.com/dustin/go-humanize"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/pulumi/pulumi/pkg/backend"
"github.com/pulumi/pulumi/pkg/backend/display"
"github.com/pulumi/pulumi/pkg/backend/httpstate"
"github.com/pulumi/pulumi/pkg/backend/state"
@@ -33,6 +35,7 @@ import (
func newStackLsCmd() *cobra.Command {
var allStacks bool
+ var jsonOut bool
cmd := &cobra.Command{
Use: "ls",
Short: "List all known stacks",
@@ -78,6 +81,58 @@ func newStackLsCmd() *cobra.Command {
return stackSummaries[i].Name().String() < stackSummaries[j].Name().String()
})
+ if jsonOut {
+ return formatJSON(b, current, stackSummaries)
+ }
+
+ return formatConsole(b, current, stackSummaries)
+ }),
+ }
+ cmd.PersistentFlags().BoolVarP(
+ &jsonOut, "json", "j", false, "Emit outputs as JSON")
+ cmd.PersistentFlags().BoolVarP(
+ &allStacks, "all", "a", false, "List all stacks instead of just stacks for the current project")
+
+ return cmd
+}
+
+// stackSummaryJSON is the shape of the --json output of this command. When --json is passed, we print an array
+// of stackSummaryJSON objects. While we can add fields to this structure in the future, we should not change
+// existing fields.
+type stackSummaryJSON struct {
+ Name string `json:"name"`
+ Current bool `json:"current"`
+ LastUpdate string `json:"lastUpdate,omitEmpty"`
+ ResourceCount *int `json:"resourceCount,omitEmpty"`
+ URL string `json:"url,omitEmpty"`
+}
+
+func formatJSON(b backend.Backend, currentStack string, stackSummaries []backend.StackSummary) error {
+ output := make([]stackSummaryJSON, len(stackSummaries))
+ for idx, summary := range stackSummaries {
+ summaryJSON := stackSummaryJSON{
+ Name: summary.Name().String(),
+ ResourceCount: summary.ResourceCount(),
+ Current: summary.Name().String() == currentStack,
+ }
+
+ if summary.LastUpdate() != nil {
+ summaryJSON.LastUpdate = summary.LastUpdate().UTC().Format(time.RFC3339)
+ }
+
+ if httpBackend, ok := b.(httpstate.Backend); ok {
+ if consoleURL, err := httpBackend.StackConsoleURL(summary.Name()); err == nil {
+ summaryJSON.URL = consoleURL
+ }
+ }
+
+ output[idx] = summaryJSON
+ }
+
+ return printJSON(output)
+}
+
+func formatConsole(b backend.Backend, currentStack string, stackSummaries []backend.StackSummary) error {
_, showURLColumn := b.(httpstate.Backend)
// Devote 48 characters to the name width, unless there is a longer name.
@@ -105,7 +160,7 @@ func newStackLsCmd() *cobra.Command {
// Name column
name := summary.Name().String()
- if name == current {
+ if name == currentStack {
name += "*"
}
@@ -137,10 +192,4 @@ func newStackLsCmd() *cobra.Command {
}
return nil
- }),
- }
- cmd.PersistentFlags().BoolVarP(
- &allStacks, "all", "a", false, "List all stacks instead of just stacks for the current project")
-
- return cmd
}
