command/show

This commit is contained in:
Mitchell Hashimoto 2014-07-12 19:47:31 -07:00
parent 3d35158170
commit dbc1c63d79
6 changed files with 276 additions and 1 deletions

View File

@ -12,6 +12,10 @@ import (
// FormatPlan takes a plan and returns a
func FormatPlan(p *terraform.Plan, c *colorstring.Colorize) string {
if p.Diff == nil || p.Diff.Empty() {
return "This plan does nothing."
}
if c == nil {
c = &colorstring.Colorize{
Colors: colorstring.DefaultColors,

85
command/format_state.go Normal file
View File

@ -0,0 +1,85 @@
package command
import (
"bytes"
"fmt"
"sort"
"strings"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/colorstring"
)
// FormatState takes a state and returns a string
func FormatState(s *terraform.State, c *colorstring.Colorize) string {
if len(s.Resources) == 0 {
return "The state file is empty. No resources are represented."
}
if c == nil {
c = &colorstring.Colorize{
Colors: colorstring.DefaultColors,
Reset: false,
}
}
buf := new(bytes.Buffer)
buf.WriteString("[reset]")
// First get the names of all the resources so we can show them
// in alphabetical order.
names := make([]string, 0, len(s.Resources))
for name, _ := range s.Resources {
names = append(names, name)
}
sort.Strings(names)
// Go through each resource and begin building up the output.
for _, k := range names {
rs := s.Resources[k]
id := rs.ID
if id == "" {
id = "<not created>"
}
buf.WriteString(fmt.Sprintf("%s:\n", k))
buf.WriteString(fmt.Sprintf(" id = %s\n", id))
// Sort the attributes
attrKeys := make([]string, 0, len(rs.Attributes))
for ak, _ := range rs.Attributes {
// Skip the id attribute since we just show the id directly
if ak == "id" {
continue
}
attrKeys = append(attrKeys, ak)
}
sort.Strings(attrKeys)
// Output each attribute
for _, ak := range attrKeys {
av := rs.Attributes[ak]
buf.WriteString(fmt.Sprintf(" %s = %s\n", ak, av))
}
}
if len(s.Outputs) > 0 {
buf.WriteString("\nOutputs:\n\n")
// Sort the outputs
ks := make([]string, 0, len(s.Outputs))
for k, _ := range s.Outputs {
ks = append(ks, k)
}
sort.Strings(ks)
// Output each output k/v pair
for _, k := range ks {
v := s.Outputs[k]
buf.WriteString(fmt.Sprintf("%s = %s\n", k, v))
}
}
return strings.TrimSpace(buf.String())
}

View File

@ -32,7 +32,7 @@ func TestGraph(t *testing.T) {
func TestGraph_multipleArgs(t *testing.T) {
ui := new(cli.MockUi)
c := &ApplyCommand{
c := &GraphCommand{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
}

96
command/show.go Normal file
View File

@ -0,0 +1,96 @@
package command
import (
"flag"
"fmt"
"os"
"strings"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli"
)
// ShowCommand is a Command implementation that reads and outputs the
// contents of a Terraform plan or state file.
type ShowCommand struct {
ContextOpts *terraform.ContextOpts
Ui cli.Ui
}
func (c *ShowCommand) Run(args []string) int {
cmdFlags := flag.NewFlagSet("show", flag.ContinueOnError)
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
return 1
}
args = cmdFlags.Args()
if len(args) != 1 {
c.Ui.Error(
"The show command expects exactly one argument with the path\n" +
"to a Terraform state or plan file.\n")
cmdFlags.Usage()
return 1
}
path := args[0]
var plan *terraform.Plan
var state *terraform.State
f, err := os.Open(path)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error loading file: %s", err))
return 1
}
var planErr, stateErr error
plan, err = terraform.ReadPlan(f)
if err != nil {
if _, err := f.Seek(0, 0); err != nil {
c.Ui.Error(fmt.Sprintf("Error reading file: %s", err))
return 1
}
plan = nil
planErr = err
}
if plan == nil {
state, err = terraform.ReadState(f)
if err != nil {
stateErr = err
}
}
if plan == nil && state == nil {
c.Ui.Error(fmt.Sprintf(
"Terraform couldn't read the given file as a state or plan file.\n"+
"The errors while attempting to read the file as each format are\n"+
"shown below.\n\n"+
"State read error: %s\n\nPlan read error: %s",
stateErr,
planErr))
return 1
}
if plan != nil {
c.Ui.Output(FormatPlan(plan, nil))
return 0
}
c.Ui.Output(FormatState(state, nil))
return 0
}
func (c *ShowCommand) Help() string {
helpText := `
Usage: terraform show [options] path
Reads and outputs a Terraform state or plan file in a human-readable
form.
`
return strings.TrimSpace(helpText)
}
func (c *ShowCommand) Synopsis() string {
return "Inspect Terraform state or plan"
}

83
command/show_test.go Normal file
View File

@ -0,0 +1,83 @@
package command
import (
"testing"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli"
)
func TestShow(t *testing.T) {
ui := new(cli.MockUi)
c := &ShowCommand{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
}
args := []string{
"bad",
"bad",
}
if code := c.Run(args); code != 1 {
t.Fatalf("bad: \n%s", ui.OutputWriter.String())
}
}
func TestShow_noArgs(t *testing.T) {
ui := new(cli.MockUi)
c := &ShowCommand{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
}
args := []string{}
if code := c.Run(args); code != 1 {
t.Fatalf("bad: \n%s", ui.OutputWriter.String())
}
}
func TestShow_plan(t *testing.T) {
planPath := testPlanFile(t, &terraform.Plan{
Config: new(config.Config),
})
ui := new(cli.MockUi)
c := &ShowCommand{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
}
args := []string{
planPath,
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
}
}
func TestShow_state(t *testing.T) {
originalState := &terraform.State{
Resources: map[string]*terraform.ResourceState{
"test_instance.foo": &terraform.ResourceState{
ID: "bar",
Type: "test_instance",
},
},
}
statePath := testStateFile(t, originalState)
ui := new(cli.MockUi)
c := &ShowCommand{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
}
args := []string{
statePath,
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
}
}

View File

@ -56,6 +56,13 @@ func init() {
}, nil
},
"show": func() (cli.Command, error) {
return &command.ShowCommand{
ContextOpts: &ContextOpts,
Ui: Ui,
}, nil
},
"version": func() (cli.Command, error) {
return &command.VersionCommand{
Revision: GitCommit,