command: convert all to use the new Meta thing

This commit is contained in:
Mitchell Hashimoto 2014-07-12 20:37:30 -07:00
parent 6c736bd3c4
commit 3a851bece0
12 changed files with 207 additions and 180 deletions

View File

@ -7,7 +7,6 @@ import (
"strings"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli"
)
// ApplyCommand is a Command implementation that applies a Terraform
@ -16,8 +15,6 @@ type ApplyCommand struct {
Meta
ShutdownCh <-chan struct{}
ContextOpts *terraform.ContextOpts
Ui cli.Ui
}
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

View File

@ -21,8 +21,10 @@ func TestApply(t *testing.T) {
p := testProvider()
ui := new(cli.MockUi)
c := &ApplyCommand{
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{
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{
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{
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{
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{
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{
Meta: Meta{
ContextOpts: testCtxConfig(p),
ShutdownCh: shutdownCh,
Ui: ui,
},
ShutdownCh: shutdownCh,
}
p.DiffFn = func(
@ -387,8 +402,10 @@ func TestApply_state(t *testing.T) {
ui := new(cli.MockUi)
c := &ApplyCommand{
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{
Meta: Meta{
ContextOpts: testCtxConfig(p),
Ui: ui,
},
}
args := []string{

View File

@ -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(

View File

@ -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)

View File

@ -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

View File

@ -13,8 +13,10 @@ import (
func TestGraph(t *testing.T) {
ui := new(cli.MockUi)
c := &GraphCommand{
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{
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{
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{
Meta: Meta{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
},
}
args := []string{

View File

@ -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

View File

@ -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
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,
}
}

View File

@ -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)
ctx, err := c.Context(path, statePath)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error loading state: %s", err))
c.Ui.Error(err.Error())
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)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error loading config: %s", err))
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
}

View File

@ -24,8 +24,10 @@ func TestPlan(t *testing.T) {
p := testProvider()
ui := new(cli.MockUi)
c := &PlanCommand{
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{
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{
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{
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{
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{
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{
Meta: Meta{
ContextOpts: testCtxConfig(p),
Ui: ui,
},
}
args := []string{

View File

@ -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

View File

@ -25,8 +25,10 @@ func TestRefresh(t *testing.T) {
p := testProvider()
ui := new(cli.MockUi)
c := &RefreshCommand{
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{
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{
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{
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{
Meta: Meta{
ContextOpts: testCtxConfig(p),
Ui: ui,
},
}
p.RefreshFn = nil