pulumi/cmd/stack_ls.go
Matt Ellis 56d7f8eb24 Support new stack identity for the cloud backend
This change introduces support for using the cloud backend when
`pulumi init` has not been run. When this is the case, we use the new
identity model, where a stack is referenced by an owner and a stack
name only.

There are a few things going on here:

- We add a new `--owner` flag to `pulumi stack init` that lets you
  control what account a stack is created in.

- When listing stacks, we show stacks owned by you and any
  organizations you are a member of. So, for example, I can do:

  * `pulumi stack init my-great-stack`
  * `pulumi stack init --owner pulumi my-great-stack`

  To create a stack owned by my user and one owned by my
  organization. When `pulumi stack ls` is run, you'll see both
  stacks (since they are part of the same project).

- When spelling a stack on the CLI, an owner can be optionally
  specified by prefixing the stack name with an owner name. For
  example `my-great-stack` means the stack `my-great-stack` owned by
  the current logged in user, where-as `pulumi/my-great-stack` would
  be the stack owned by the `pulumi` organization

- `--all` can be passed to `pulumi stack ls` to see *all* stacks you
  have access to, not just stacks tied to the current project.
2018-04-18 04:54:02 -07:00

122 lines
3.3 KiB
Go

// Copyright 2016-2018, Pulumi Corporation. All rights reserved.
package cmd
import (
"fmt"
"sort"
"strconv"
"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/cloud"
"github.com/pulumi/pulumi/pkg/backend/state"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/cmdutil"
"github.com/pulumi/pulumi/pkg/workspace"
)
func newStackLsCmd() *cobra.Command {
var allStacks bool
cmd := &cobra.Command{
Use: "ls",
Short: "List all known stacks",
Args: cmdutil.NoArgs,
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
// Ensure we are in a project; if not, we will fail.
projPath, err := workspace.DetectProjectPath()
if err != nil {
return errors.Wrapf(err, "could not detect current project")
} else if projPath == "" {
return errors.New("no Pulumi.yaml found; please run this command in a project directory")
}
proj, err := workspace.LoadProject(projPath)
if err != nil {
return errors.Wrap(err, "could not load current project")
}
// Get a list of all known backends, as we will query them all.
b, err := currentBackend()
if err != nil {
return err
}
// Get the current stack so we can print a '*' next to it.
var current string
if s, _ := state.CurrentStack(b); s != nil {
// If we couldn't figure out the current stack, just don't print the '*' later on instead of failing.
current = s.Name().String()
}
var packageFilter *tokens.PackageName
if !allStacks {
packageFilter = &proj.Name
}
// Now produce a list of summaries, and enumerate them sorted by name.
var result error
var stackNames []string
stacks := make(map[string]backend.Stack)
bs, err := b.ListStacks(packageFilter)
if err != nil {
return err
}
for _, stack := range bs {
name := stack.Name().String()
stacks[name] = stack
stackNames = append(stackNames, name)
}
sort.Strings(stackNames)
// Devote 48 characters to the name width, unless there is a longer name.
maxname := 48
for _, name := range stackNames {
if len(name) > maxname {
maxname = len(name)
}
}
fmt.Printf("%-"+strconv.Itoa(maxname)+"s %-24s %-18s %-25s\n",
"NAME", "LAST UPDATE", "RESOURCE COUNT", "CLOUD")
for _, name := range stackNames {
// Mark the name as current '*' if we've selected it.
stack := stacks[name]
if name == current {
name += "*"
}
// Get last deployment info, provided that it exists.
none := "n/a"
lastUpdate := none
resourceCount := none
if snap := stack.Snapshot(); snap != nil {
if t := snap.Manifest.Time; !t.IsZero() {
lastUpdate = humanize.Time(t)
}
resourceCount = strconv.Itoa(len(snap.Resources))
}
// Print out the cloud URL.
var cloudInfo string
if cs, ok := stack.(cloud.Stack); ok {
cloudInfo = fmt.Sprintf("%s:%s/%s", cs.CloudURL(), cs.OrgName(), cs.CloudName())
} else {
cloudInfo = none
}
fmt.Printf("%-"+strconv.Itoa(maxname)+"s %-24s %-18s %-25s\n",
name, lastUpdate, resourceCount, cloudInfo)
}
return result
}),
}
cmd.PersistentFlags().BoolVar(
&allStacks, "all", false, "List all stacks instead of just stacks for the current project")
return cmd
}