From 3a851bece0febdd97ff3007877e75fea13d9ada2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 12 Jul 2014 20:37:30 -0700 Subject: [PATCH] command: convert all to use the new Meta thing --- command/apply.go | 8 +--- command/apply_test.go | 57 ++++++++++++++++--------- command/command.go | 77 ---------------------------------- command/format_state.go | 8 ++-- command/graph.go | 9 ++-- command/graph_test.go | 24 +++++++---- command/hook_ui.go | 2 +- command/meta.go | 92 ++++++++++++++++++++++++++++++++++++++++- command/plan.go | 31 +------------- command/plan_test.go | 42 ++++++++++++------- command/refresh.go | 7 +--- command/refresh_test.go | 30 +++++++++----- 12 files changed, 207 insertions(+), 180 deletions(-) diff --git a/command/apply.go b/command/apply.go index b27483aed..d87e1954e 100644 --- a/command/apply.go +++ b/command/apply.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/hashicorp/terraform/terraform" - "github.com/mitchellh/cli" ) // ApplyCommand is a Command implementation that applies a Terraform @@ -15,9 +14,7 @@ import ( type ApplyCommand struct { Meta - ShutdownCh <-chan struct{} - ContextOpts *terraform.ContextOpts - Ui cli.Ui + ShutdownCh <-chan struct{} } func (c *ApplyCommand) Run(args []string) int { @@ -65,8 +62,7 @@ func (c *ApplyCommand) Run(args []string) int { } // Build the context based on the arguments given - c.ContextOpts.Hooks = append(c.ContextOpts.Hooks, &UiHook{Ui: c.Ui}) - ctx, err := ContextArg(configPath, planStatePath, c.ContextOpts) + ctx, err := c.Context(configPath, planStatePath) if err != nil { c.Ui.Error(err.Error()) return 1 diff --git a/command/apply_test.go b/command/apply_test.go index 58a831a2f..48a1a1c70 100644 --- a/command/apply_test.go +++ b/command/apply_test.go @@ -21,8 +21,10 @@ func TestApply(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &ApplyCommand{ - ContextOpts: testCtxConfig(p), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, } args := []string{ @@ -57,8 +59,10 @@ func TestApply_configInvalid(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &ApplyCommand{ - ContextOpts: testCtxConfig(p), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, } args := []string{ @@ -91,8 +95,10 @@ func TestApply_defaultState(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &ApplyCommand{ - ContextOpts: testCtxConfig(p), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, } args := []string{ @@ -128,8 +134,10 @@ func TestApply_error(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &ApplyCommand{ - ContextOpts: testCtxConfig(p), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, } var lock sync.Mutex @@ -205,8 +213,10 @@ func TestApply_noArgs(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &ApplyCommand{ - ContextOpts: testCtxConfig(p), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, } args := []string{ @@ -245,8 +255,10 @@ func TestApply_plan(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &ApplyCommand{ - ContextOpts: testCtxConfig(p), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, } args := []string{ @@ -287,9 +299,12 @@ func TestApply_shutdown(t *testing.T) { shutdownCh := make(chan struct{}) ui := new(cli.MockUi) c := &ApplyCommand{ - ContextOpts: testCtxConfig(p), - ShutdownCh: shutdownCh, - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, + + ShutdownCh: shutdownCh, } p.DiffFn = func( @@ -387,8 +402,10 @@ func TestApply_state(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ - ContextOpts: testCtxConfig(p), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, } // Run the apply command pointing to our existing state @@ -434,8 +451,10 @@ func TestApply_stateNoExist(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &ApplyCommand{ - ContextOpts: testCtxConfig(p), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, } args := []string{ diff --git a/command/command.go b/command/command.go index cef5c2924..9d27321d5 100644 --- a/command/command.go +++ b/command/command.go @@ -2,91 +2,14 @@ package command import ( "fmt" - "os" - "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/terraform" "github.com/mitchellh/cli" - "github.com/mitchellh/colorstring" ) // DefaultStateFilename is the default filename used for the state file. const DefaultStateFilename = "terraform.tfstate" -// Colorize returns our default coloring settings for strings. -func Colorize() *colorstring.Colorize { - return &colorstring.Colorize{ - Colors: colorstring.DefaultColors, - Reset: true, - } -} - -// ContextArg is a helper function that creates a context based on -// the arguments given. If the path is a plan file, it creates a context -// from that. Otherwise, it creates a context based on the state file -// (if given). -func ContextArg( - path string, - statePath string, - opts *terraform.ContextOpts) (*terraform.Context, error) { - // First try to just read the plan directly from the path given. - f, err := os.Open(path) - if err == nil { - plan, err := terraform.ReadPlan(f) - f.Close() - if err == nil { - return plan.Context(opts), nil - } - } - - if statePath != "" { - if _, err := os.Stat(statePath); err != nil { - return nil, fmt.Errorf( - "There was an error reading the state file. The path\n"+ - "and error are shown below. If you're trying to build a\n"+ - "brand new infrastructure, explicitly pass the '-init'\n"+ - "flag to Terraform to tell it it is okay to build new\n"+ - "state.\n\n"+ - "Path: %s\n"+ - "Error: %s", - statePath, - err) - } - } - - // Load up the state - var state *terraform.State - if statePath != "" { - f, err := os.Open(statePath) - if err == nil { - state, err = terraform.ReadState(f) - f.Close() - } - - if err != nil { - return nil, fmt.Errorf("Error loading state: %s", err) - } - } - - config, err := config.LoadDir(path) - if err != nil { - return nil, fmt.Errorf("Error loading config: %s", err) - } - if err := config.Validate(); err != nil { - return nil, fmt.Errorf("Error validating config: %s", err) - } - - opts.Config = config - opts.State = state - ctx := terraform.NewContext(opts) - - if _, err := ctx.Plan(nil); err != nil { - return nil, fmt.Errorf("Error running plan: %s", err) - } - - return ctx, nil -} - func validateContext(ctx *terraform.Context, ui cli.Ui) bool { if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 { ui.Output( diff --git a/command/format_state.go b/command/format_state.go index f63e1e352..59f02e590 100644 --- a/command/format_state.go +++ b/command/format_state.go @@ -12,12 +12,12 @@ import ( // 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 { + panic("colorize not given") } - if c == nil { - c = Colorize() + if len(s.Resources) == 0 { + return "The state file is empty. No resources are represented." } buf := new(bytes.Buffer) diff --git a/command/graph.go b/command/graph.go index 791f074f6..f0c4bcf3f 100644 --- a/command/graph.go +++ b/command/graph.go @@ -8,18 +8,17 @@ import ( "strings" "github.com/hashicorp/terraform/digraph" - "github.com/hashicorp/terraform/terraform" - "github.com/mitchellh/cli" ) // GraphCommand is a Command implementation that takes a Terraform // configuration and outputs the dependency tree in graphical form. type GraphCommand struct { - ContextOpts *terraform.ContextOpts - Ui cli.Ui + Meta } func (c *GraphCommand) Run(args []string) int { + args = c.Meta.process(args) + cmdFlags := flag.NewFlagSet("graph", flag.ContinueOnError) cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { @@ -42,7 +41,7 @@ func (c *GraphCommand) Run(args []string) int { } } - ctx, err := ContextArg(path, "", c.ContextOpts) + ctx, err := c.Context(path, "") if err != nil { c.Ui.Error(fmt.Sprintf("Error loading Terraform: %s", err)) return 1 diff --git a/command/graph_test.go b/command/graph_test.go index 0a74726e7..cbc564e8f 100644 --- a/command/graph_test.go +++ b/command/graph_test.go @@ -13,8 +13,10 @@ import ( func TestGraph(t *testing.T) { ui := new(cli.MockUi) c := &GraphCommand{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(testProvider()), + Ui: ui, + }, } args := []string{ @@ -33,8 +35,10 @@ func TestGraph(t *testing.T) { func TestGraph_multipleArgs(t *testing.T) { ui := new(cli.MockUi) c := &GraphCommand{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(testProvider()), + Ui: ui, + }, } args := []string{ @@ -58,8 +62,10 @@ func TestGraph_noArgs(t *testing.T) { ui := new(cli.MockUi) c := &GraphCommand{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(testProvider()), + Ui: ui, + }, } args := []string{} @@ -80,8 +86,10 @@ func TestGraph_plan(t *testing.T) { ui := new(cli.MockUi) c := &GraphCommand{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(testProvider()), + Ui: ui, + }, } args := []string{ diff --git a/command/hook_ui.go b/command/hook_ui.go index 8f7c26e8c..3f4d9bf5f 100644 --- a/command/hook_ui.go +++ b/command/hook_ui.go @@ -97,7 +97,7 @@ func (h *UiHook) PreRefresh( func (h *UiHook) init() { if h.Colorize == nil { - h.Colorize = Colorize() + panic("colorize not given") } // Wrap the ui so that it is safe for concurrency regardless of the diff --git a/command/meta.go b/command/meta.go index ee16f45c0..a7758a444 100644 --- a/command/meta.go +++ b/command/meta.go @@ -1,12 +1,20 @@ package command import ( + "fmt" + "os" + + "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/terraform" + "github.com/mitchellh/cli" "github.com/mitchellh/colorstring" ) // Meta are the meta-options that are available on all or most commands. type Meta struct { - Color bool + Color bool + ContextOpts *terraform.ContextOpts + Ui cli.Ui } // Colorize returns the colorization structure for a command. @@ -18,6 +26,80 @@ func (m *Meta) Colorize() *colorstring.Colorize { } } +// Context returns a Terraform Context taking into account the context +// options used to initialize this meta configuration. +func (m *Meta) Context(path, statePath string) (*terraform.Context, error) { + opts := m.contextOpts() + + // First try to just read the plan directly from the path given. + f, err := os.Open(path) + if err == nil { + plan, err := terraform.ReadPlan(f) + f.Close() + if err == nil { + return plan.Context(opts), nil + } + } + + if statePath != "" { + if _, err := os.Stat(statePath); err != nil { + return nil, fmt.Errorf( + "There was an error reading the state file. The path\n"+ + "and error are shown below. If you're trying to build a\n"+ + "brand new infrastructure, explicitly pass the '-init'\n"+ + "flag to Terraform to tell it it is okay to build new\n"+ + "state.\n\n"+ + "Path: %s\n"+ + "Error: %s", + statePath, + err) + } + } + + // Load up the state + var state *terraform.State + if statePath != "" { + f, err := os.Open(statePath) + if err == nil { + state, err = terraform.ReadState(f) + f.Close() + } + + if err != nil { + return nil, fmt.Errorf("Error loading state: %s", err) + } + } + + config, err := config.LoadDir(path) + if err != nil { + return nil, fmt.Errorf("Error loading config: %s", err) + } + if err := config.Validate(); err != nil { + return nil, fmt.Errorf("Error validating config: %s", err) + } + + opts.Config = config + opts.State = state + ctx := terraform.NewContext(opts) + + if _, err := ctx.Plan(nil); err != nil { + return nil, fmt.Errorf("Error running plan: %s", err) + } + + return ctx, nil + +} + +// contextOpts returns the options to use to initialize a Terraform +// context with the settings from this Meta. +func (m *Meta) contextOpts() *terraform.ContextOpts { + var opts terraform.ContextOpts = *m.ContextOpts + opts.Hooks = make([]terraform.Hook, len(m.ContextOpts.Hooks)+1) + opts.Hooks[0] = m.uiHook() + copy(opts.Hooks[1:], m.ContextOpts.Hooks) + return &opts +} + // process will process the meta-parameters out of the arguments. This // will potentially modify the args in-place. It will return the resulting // slice. @@ -33,3 +115,11 @@ func (m *Meta) process(args []string) []string { return args } + +// uiHook returns the UiHook to use with the context. +func (m *Meta) uiHook() *UiHook { + return &UiHook{ + Colorize: m.Colorize(), + Ui: m.Ui, + } +} diff --git a/command/plan.go b/command/plan.go index b1a77f7d5..44790f72b 100644 --- a/command/plan.go +++ b/command/plan.go @@ -7,18 +7,13 @@ import ( "os" "strings" - "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/terraform" - "github.com/mitchellh/cli" ) // PlanCommand is a Command implementation that compares a Terraform // configuration to an actual infrastructure and shows the differences. type PlanCommand struct { Meta - - ContextOpts *terraform.ContextOpts - Ui cli.Ui } func (c *PlanCommand) Run(args []string) int { @@ -64,33 +59,11 @@ func (c *PlanCommand) Run(args []string) int { } } - // Load up the state - var state *terraform.State - if statePath != "" { - f, err := os.Open(statePath) - if err != nil { - c.Ui.Error(fmt.Sprintf("Error loading state: %s", err)) - return 1 - } - - state, err = terraform.ReadState(f) - f.Close() - if err != nil { - c.Ui.Error(fmt.Sprintf("Error loading state: %s", err)) - return 1 - } - } - - b, err := config.LoadDir(path) + ctx, err := c.Context(path, statePath) if err != nil { - c.Ui.Error(fmt.Sprintf("Error loading config: %s", err)) + c.Ui.Error(err.Error()) return 1 } - - c.ContextOpts.Config = b - c.ContextOpts.Hooks = append(c.ContextOpts.Hooks, &UiHook{Ui: c.Ui}) - c.ContextOpts.State = state - ctx := terraform.NewContext(c.ContextOpts) if !validateContext(ctx, c.Ui) { return 1 } diff --git a/command/plan_test.go b/command/plan_test.go index f31c82356..473f68a0c 100644 --- a/command/plan_test.go +++ b/command/plan_test.go @@ -24,8 +24,10 @@ func TestPlan(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &PlanCommand{ - ContextOpts: testCtxConfig(p), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, } args := []string{} @@ -50,8 +52,10 @@ func TestPlan_destroy(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &PlanCommand{ - ContextOpts: testCtxConfig(p), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, } args := []string{ @@ -79,8 +83,10 @@ func TestPlan_noState(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &PlanCommand{ - ContextOpts: testCtxConfig(p), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, } args := []string{ @@ -115,8 +121,10 @@ func TestPlan_outPath(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &PlanCommand{ - ContextOpts: testCtxConfig(p), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, } p.DiffReturn = &terraform.ResourceDiff{ @@ -146,8 +154,10 @@ func TestPlan_refresh(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &PlanCommand{ - ContextOpts: testCtxConfig(p), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, } args := []string{ @@ -190,8 +200,10 @@ func TestPlan_state(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &PlanCommand{ - ContextOpts: testCtxConfig(p), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, } args := []string{ @@ -250,8 +262,10 @@ func TestPlan_stateDefault(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &PlanCommand{ - ContextOpts: testCtxConfig(p), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, } args := []string{ diff --git a/command/refresh.go b/command/refresh.go index 56a63c572..256bf70dd 100644 --- a/command/refresh.go +++ b/command/refresh.go @@ -8,16 +8,12 @@ import ( "strings" "github.com/hashicorp/terraform/terraform" - "github.com/mitchellh/cli" ) // RefreshCommand is a cli.Command implementation that refreshes the state // file. type RefreshCommand struct { Meta - - ContextOpts *terraform.ContextOpts - Ui cli.Ui } func (c *RefreshCommand) Run(args []string) int { @@ -82,8 +78,7 @@ func (c *RefreshCommand) Run(args []string) int { } // Build the context based on the arguments given - c.ContextOpts.Hooks = append(c.ContextOpts.Hooks, &UiHook{Ui: c.Ui}) - ctx, err := ContextArg(configPath, statePath, c.ContextOpts) + ctx, err := c.Context(configPath, statePath) if err != nil { c.Ui.Error(err.Error()) return 1 diff --git a/command/refresh_test.go b/command/refresh_test.go index c7abc09c8..7acf39e5f 100644 --- a/command/refresh_test.go +++ b/command/refresh_test.go @@ -25,8 +25,10 @@ func TestRefresh(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &RefreshCommand{ - ContextOpts: testCtxConfig(p), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, } p.RefreshFn = nil @@ -66,8 +68,10 @@ func TestRefresh_badState(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &RefreshCommand{ - ContextOpts: testCtxConfig(p), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, } args := []string{ @@ -102,8 +106,10 @@ func TestRefresh_cwd(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &RefreshCommand{ - ContextOpts: testCtxConfig(p), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, } p.RefreshFn = nil @@ -179,8 +185,10 @@ func TestRefresh_defaultState(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &RefreshCommand{ - ContextOpts: testCtxConfig(p), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, } p.RefreshFn = nil @@ -238,8 +246,10 @@ func TestRefresh_outPath(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &RefreshCommand{ - ContextOpts: testCtxConfig(p), - Ui: ui, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, } p.RefreshFn = nil