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.
122 lines
3.3 KiB
Go
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
|
|
}
|