From 6b5ee73e866fa34a3efca421d0ad329f2c675d1f Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 30 Jun 2016 16:50:56 -0400 Subject: [PATCH] Use -state-out option when applying from a plan When working from an existing plan, we weren't setting the PathOut field for a LocalState. This required adding an outPath argument to the StateFromPlan function to avoid having to introspect the returned state.State interface to find the appropriate field. To test we run a plan first and provide the new plan to apply with `-state-out` set. --- command/apply_test.go | 53 +++++++++++++++++++++++++++++++++++++++++++ command/meta.go | 8 +++++-- command/state.go | 8 +++++-- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/command/apply_test.go b/command/apply_test.go index a01ff03a4..ca0816ba7 100644 --- a/command/apply_test.go +++ b/command/apply_test.go @@ -1326,6 +1326,59 @@ func TestApply_disableBackup(t *testing.T) { } } +// -state-out wasn't taking effect when a plan is supplied. GH-7264 +func TestApply_stateOutWithPlan(t *testing.T) { + p := testProvider() + ui := new(cli.MockUi) + + tmpDir := testTempDir(t) + defer os.RemoveAll(tmpDir) + + statePath := filepath.Join(tmpDir, "state.tfstate") + planPath := filepath.Join(tmpDir, "terraform.tfplan") + + args := []string{ + "-state", statePath, + "-out", planPath, + testFixturePath("plan"), + } + + // Run plan first to get a current plan file + pc := &PlanCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, + } + if code := pc.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + // now run apply with the generated plan + stateOutPath := filepath.Join(tmpDir, "state-new.tfstate") + + args = []string{ + "-state", statePath, + "-state-out", stateOutPath, + planPath, + } + + ac := &ApplyCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, + } + if code := ac.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + // now make sure we wrote out our new state + if _, err := os.Stat(stateOutPath); err != nil { + t.Fatalf("missing new state file: %s", err) + } +} + func testHttpServer(t *testing.T) net.Listener { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { diff --git a/command/meta.go b/command/meta.go index f54fca13d..ddc9606f1 100644 --- a/command/meta.go +++ b/command/meta.go @@ -109,14 +109,18 @@ func (m *Meta) Context(copts contextOpts) (*terraform.Context, bool, error) { f.Close() if err == nil { // Setup our state - state, statePath, err := StateFromPlan(m.statePath, plan) + state, statePath, err := StateFromPlan(m.statePath, m.stateOutPath, plan) if err != nil { return nil, false, fmt.Errorf("Error loading plan: %s", err) } // Set our state m.state = state - m.stateOutPath = statePath + + // this is used for printing the saved location later + if m.stateOutPath == "" { + m.stateOutPath = statePath + } if len(m.variables) > 0 { return nil, false, fmt.Errorf( diff --git a/command/state.go b/command/state.go index c9a4ef7f8..b4ff0e583 100644 --- a/command/state.go +++ b/command/state.go @@ -169,7 +169,8 @@ func State(opts *StateOpts) (*StateResult, error) { // StateFromPlan gets our state from the plan. func StateFromPlan( - localPath string, plan *terraform.Plan) (state.State, string, error) { + localPath, outPath string, + plan *terraform.Plan) (state.State, string, error) { var result state.State resultPath := localPath if plan != nil && plan.State != nil && @@ -186,7 +187,10 @@ func StateFromPlan( } if result == nil { - local := &state.LocalState{Path: resultPath} + local := &state.LocalState{ + Path: resultPath, + PathOut: outPath, + } local.SetState(plan.State) result = local }