pulumi/cmd/stack_ls.go
Matt Ellis 328734f874 Define backend interface, move local implementation behind it
This change introduces an abstraction for a `backend` which manages
the implementation of some CLI commands. As part of defining the
interface, we introduce a new local backend implementation that just
uses data local to the machine.

This will let us share argument parsing and some display information
between the local case and the pulumi.com case in the CLI. We can
continue to refine this interface over time (e.g. today we have the
implementation of the Destroy/Update/Preview actually writing output
but instead they should be returning strongly typed data that the CLI
knows how to display and is unified across Pulumi.com deploys and
local deploys).

But this is a good first step.
2017-11-02 11:19:00 -07:00

166 lines
4 KiB
Go

// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package cmd
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/apitype"
"github.com/pulumi/pulumi/pkg/encoding"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/spf13/cobra"
"github.com/pulumi/pulumi/pkg/util/cmdutil"
)
func newStackLsCmd() *cobra.Command {
if usePulumiCloudCommands() {
return newCloudStackLsCmd()
}
return newFAFStackLsCmd()
}
func newFAFStackLsCmd() *cobra.Command {
return &cobra.Command{
Use: "ls",
Short: "List all known stacks",
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
var backend pulumiBackend = &localPulumiBackend{}
currentStack, err := getCurrentStack()
if err != nil {
// If we couldn't figure out the current stack, just don't print the '*' later
// on instead of failing.
currentStack = tokens.QName("")
}
summaries, err := backend.GetStacks()
if err != nil {
return err
}
displayStacks(summaries, currentStack)
return nil
}),
}
}
func getStacks() ([]tokens.QName, error) {
var stacks []tokens.QName
w, err := newWorkspace()
if err != nil {
return nil, err
}
// Read the stack directory.
path := w.StackPath("")
files, err := ioutil.ReadDir(path)
if err != nil && !os.IsNotExist(err) {
return nil, errors.Errorf("could not read stacks: %v", err)
}
for _, file := range files {
// Ignore directories.
if file.IsDir() {
continue
}
// Skip files without valid extensions (e.g., *.bak files).
stackfn := file.Name()
ext := filepath.Ext(stackfn)
if _, has := encoding.Marshalers[ext]; !has {
continue
}
// Read in this stack's information.
name := tokens.QName(stackfn[:len(stackfn)-len(ext)])
_, _, _, err := getStack(name)
if err != nil {
continue // failure reading the stack information.
}
stacks = append(stacks, name)
}
return stacks, nil
}
func newCloudStackLsCmd() *cobra.Command {
return &cobra.Command{
Use: "ls",
Short: "List all known stacks",
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
stacks, err := getCloudStacks()
if err != nil {
return nil
}
// Map to a summary slice.
var summaries []stackSummary
for _, stack := range stacks {
summary := stackSummary{
Name: stack.StackName,
LastDeploy: "n/a", // TODO(pulumi-service/issues#249): Make this info available.
ResourceCount: strconv.Itoa(len(stack.Resources)),
}
// If the stack hasn't been pushed to, it's resource count doesn't matter.
if stack.ActiveUpdate == "" {
summary.ResourceCount = "n/a"
}
summaries = append(summaries, summary)
}
// Ignore the error, since current stack is just cosmetic for the display.
currentStack, err := getCurrentStack()
if err != nil {
currentStack = tokens.QName("")
}
displayStacks(summaries, currentStack)
return nil
}),
}
}
// getCloudStacks returns all stacks for the current repository x workspace on the Pulumi Cloud.
func getCloudStacks() ([]apitype.Stack, error) {
// Look up the owner, repository, and project from the workspace and nearest package.
w, err := newWorkspace()
if err != nil {
return nil, err
}
projID, err := getCloudProjectIdentifier(w)
if err != nil {
return nil, err
}
// Query all stacks for the project on Pulumi.
var stacks []apitype.Stack
path := fmt.Sprintf("/orgs/%s/programs/%s/%s/stacks", projID.Owner, projID.Repository, projID.Project)
if err := pulumiRESTCall("GET", path, nil, &stacks); err != nil {
return nil, err
}
return stacks, nil
}
// displayStacks prints the list of stacks to STDOUT. An optional current stack name,
// if present, will have a star by it.
func displayStacks(stacks []stackSummary, current tokens.QName) {
fmt.Printf("%-20s %-48s %-12s\n", "NAME", "LAST UPDATE", "RESOURCE COUNT")
for _, stack := range stacks {
if stack.Name == current {
stack.Name += "*"
}
fmt.Printf("%-20s %-48s %-12s\n", stack.Name, stack.LastDeploy, stack.ResourceCount)
}
}