diff --git a/command/apply.go b/command/apply.go index 448258dec..13b1b7167 100644 --- a/command/apply.go +++ b/command/apply.go @@ -20,36 +20,39 @@ type ApplyCommand struct { func (c *ApplyCommand) Run(args []string) int { var init bool - var stateOutPath string + var statePath, stateOutPath string cmdFlags := flag.NewFlagSet("apply", flag.ContinueOnError) cmdFlags.BoolVar(&init, "init", false, "init") - cmdFlags.StringVar(&stateOutPath, "out", "", "path") + cmdFlags.StringVar(&statePath, "state", DefaultStateFilename, "path") + cmdFlags.StringVar(&stateOutPath, "state-out", "", "path") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 } args = cmdFlags.Args() - if len(args) != 2 { - c.Ui.Error("The apply command expects two arguments.\n") + if len(args) > 1 { + c.Ui.Error("The apply command expacts at most one argument.") cmdFlags.Usage() return 1 } + configPath := args[0] - statePath := args[0] - configPath := args[1] - + // If we don't specify an output path, default to out normal state + // path. if stateOutPath == "" { stateOutPath = statePath } + // The state path to use to generate a plan. If we're initializing + // a new infrastructure, then we don't use a state path. planStatePath := statePath if init { planStatePath = "" } - // Initialize Terraform right away + // 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) if err != nil { @@ -113,18 +116,24 @@ func (c *ApplyCommand) Run(args []string) int { func (c *ApplyCommand) Help() string { helpText := ` -Usage: terraform apply [options] STATE PATH +Usage: terraform apply [options] [dir] - Builds or changes infrastructure according to the Terraform configuration - file. + Builds or changes infrastructure according to Terraform configuration + files . Options: - -init If specified, it is okay to build brand new - infrastructure (with no state file specified). + -init If specified, new infrastructure can be built (no + previous state). This is just a safety switch + to prevent accidentally spinning up a new + infrastructure. - -out=file.tfstate Path to save the new state. If not specified, the - state path argument will be used. + -state=path Path to read and save state (unless state-out + is specified). Defaults to "terraform.tfstate". + + -state-out=path Path to write state to that is different than + "-state". This can be used to preserve the old + state. ` return strings.TrimSpace(helpText) diff --git a/command/apply_test.go b/command/apply_test.go index 750e533f5..b068e7600 100644 --- a/command/apply_test.go +++ b/command/apply_test.go @@ -2,7 +2,9 @@ package command import ( "fmt" + "io/ioutil" "os" + "path/filepath" "reflect" "sync" "testing" @@ -25,7 +27,7 @@ func TestApply(t *testing.T) { args := []string{ "-init", - statePath, + "-state", statePath, testFixturePath("apply"), } if code := c.Run(args); code != 0 { @@ -61,7 +63,7 @@ func TestApply_configInvalid(t *testing.T) { args := []string{ "-init", - testTempFile(t), + "-state", testTempFile(t), testFixturePath("apply-config-invalid"), } if code := c.Run(args); code != 1 { @@ -69,6 +71,57 @@ func TestApply_configInvalid(t *testing.T) { } } +func TestApply_defaultState(t *testing.T) { + td, err := ioutil.TempDir("", "tf") + if err != nil { + t.Fatalf("err: %s", err) + } + statePath := filepath.Join(td, DefaultStateFilename) + + // Change to the temporary directory + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("err: %s", err) + } + if err := os.Chdir(filepath.Dir(statePath)); err != nil { + t.Fatalf("err: %s", err) + } + defer os.Chdir(cwd) + + p := testProvider() + ui := new(cli.MockUi) + c := &ApplyCommand{ + ContextOpts: testCtxConfig(p), + Ui: ui, + } + + args := []string{ + "-init", + testFixturePath("apply"), + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + if _, err := os.Stat(statePath); err != nil { + t.Fatalf("err: %s", err) + } + + f, err := os.Open(statePath) + if err != nil { + t.Fatalf("err: %s", err) + } + defer f.Close() + + state, err := terraform.ReadState(f) + if err != nil { + t.Fatalf("err: %s", err) + } + if state == nil { + t.Fatal("state should not be nil") + } +} + func TestApply_error(t *testing.T) { statePath := testTempFile(t) @@ -108,7 +161,7 @@ func TestApply_error(t *testing.T) { args := []string{ "-init", - statePath, + "-state", statePath, testFixturePath("apply-error"), } if code := c.Run(args); code != 1 { @@ -151,7 +204,7 @@ func TestApply_plan(t *testing.T) { } args := []string{ - statePath, + "-state", statePath, planPath, } if code := c.Run(args); code != 0 { @@ -235,7 +288,7 @@ func TestApply_shutdown(t *testing.T) { args := []string{ "-init", - statePath, + "-state", statePath, testFixturePath("apply-shutdown"), } if code := c.Run(args); code != 0 { @@ -294,7 +347,7 @@ func TestApply_state(t *testing.T) { // Run the apply command pointing to our existing state args := []string{ - statePath, + "-state", statePath, testFixturePath("apply"), } if code := c.Run(args); code != 0 { diff --git a/command/command.go b/command/command.go index 6ca862d83..b8f4aab9d 100644 --- a/command/command.go +++ b/command/command.go @@ -55,7 +55,7 @@ func ContextArg( } } - config, err := config.Load(path) + config, err := config.LoadDir(path) if err != nil { return nil, fmt.Errorf("Error loading config: %s", err) }