diff --git a/command/state_pull.go b/command/state_pull.go index 6724f6628..0ac531aa1 100644 --- a/command/state_pull.go +++ b/command/state_pull.go @@ -35,7 +35,7 @@ func (c *StatePullCommand) Run(args []string) int { return 1 } - // Get the state + // Get the state manager for the current workspace env := c.Workspace() stateMgr, err := b.StateMgr(env) if err != nil { @@ -47,24 +47,20 @@ func (c *StatePullCommand) Run(args []string) int { return 1 } - state := stateMgr.State() - if state == nil { - // Output on "error" so it shows up on stderr - c.Ui.Error("Empty state (no state)") - return 0 + // Get a statefile object representing the latest snapshot + stateFile := statemgr.Export(stateMgr) + + if stateFile != nil { // we produce no output if the statefile is nil + var buf bytes.Buffer + err = statefile.Write(stateFile, &buf) + if err != nil { + c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err)) + return 1 + } + + c.Ui.Output(buf.String()) } - // Get the state file. - stateFile := statemgr.StateFile(stateMgr, state) - - var buf bytes.Buffer - err = statefile.Write(stateFile, &buf) - if err != nil { - c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err)) - return 1 - } - - c.Ui.Output(buf.String()) return 0 } diff --git a/command/state_pull_test.go b/command/state_pull_test.go index 7338f8493..8c7680601 100644 --- a/command/state_pull_test.go +++ b/command/state_pull_test.go @@ -47,7 +47,7 @@ func TestStatePull_noState(t *testing.T) { defer testFixCwd(t, tmp, cwd) p := testProvider() - ui := new(cli.MockUi) + ui := cli.NewMockUi() c := &StatePullCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(p), diff --git a/command/state_push.go b/command/state_push.go index 9de232abc..2aa39bd8f 100644 --- a/command/state_push.go +++ b/command/state_push.go @@ -70,7 +70,7 @@ func (c *StatePushCommand) Run(args []string) int { return 1 } - // Get the state + // Get the state manager for the currently-selected workspace env := c.Workspace() stateMgr, err := b.StateMgr(env) if err != nil { @@ -81,23 +81,17 @@ func (c *StatePushCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("Failed to refresh destination state: %s", err)) return 1 } - dstState := stateMgr.State() - // If we're not forcing, then perform safety checks - if !flagForce && !dstState.Empty() { - dstStateFile := statemgr.StateFile(stateMgr, dstState) - - if dstStateFile.Lineage != srcStateFile.Lineage { - c.Ui.Error(strings.TrimSpace(errStatePushLineage)) - return 1 - } - if dstStateFile.Serial > srcStateFile.Serial { - c.Ui.Error(strings.TrimSpace(errStatePushSerialNewer)) - return 1 - } + if srcStateFile == nil { + // We'll push a new empty state instead + srcStateFile = statemgr.NewStateFile() } - // Overwrite it + // Import it, forcing through the lineage/serial if requested and possible. + if err := statemgr.Import(srcStateFile, stateMgr, flagForce); err != nil { + c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err)) + return 1 + } if err := stateMgr.WriteState(srcStateFile.State); err != nil { c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err)) return 1 diff --git a/command/test-fixtures/state-push-bad-lineage/local-state.tfstate b/command/test-fixtures/state-push-bad-lineage/local-state.tfstate index 4023b53e0..fe06c362c 100644 --- a/command/test-fixtures/state-push-bad-lineage/local-state.tfstate +++ b/command/test-fixtures/state-push-bad-lineage/local-state.tfstate @@ -1,5 +1,17 @@ { "version": 3, "serial": 1, - "lineage": "mismatch" + "lineage": "mismatch", + "modules": [ + { + "path": ["root"], + "outputs": { + "foo": { + "type": "string", + "value": "bar" + } + }, + "resources": {} + } + ] } diff --git a/command/test-fixtures/state-push-bad-lineage/replace.tfstate b/command/test-fixtures/state-push-bad-lineage/replace.tfstate index 0e3b7013a..dad6859ea 100644 --- a/command/test-fixtures/state-push-bad-lineage/replace.tfstate +++ b/command/test-fixtures/state-push-bad-lineage/replace.tfstate @@ -1,5 +1,17 @@ { "version": 3, "serial": 2, - "lineage": "hello" + "lineage": "hello", + "modules": [ + { + "path": ["root"], + "outputs": { + "foo": { + "type": "string", + "value": "baz" + } + }, + "resources": {} + } + ] } diff --git a/states/statemgr/helper.go b/states/statemgr/helper.go index 5feb09e94..7eef70efc 100644 --- a/states/statemgr/helper.go +++ b/states/statemgr/helper.go @@ -15,28 +15,10 @@ func NewStateFile() *statefile.File { return &statefile.File{ Lineage: NewLineage(), TerraformVersion: version.SemVer, + State: states.NewState(), } } -// StateFile is a special helper to obtain a statefile representation -// of a state snapshot that can be written later by a call -func StateFile(mgr Storage, state *states.State) *statefile.File { - ret := &statefile.File{ - State: state.DeepCopy(), - TerraformVersion: version.SemVer, - } - - // If the given manager uses snapshot metadata then we'll save that - // in our file so we can check it again during WritePlannedStateUpdate. - if mr, ok := mgr.(PersistentMeta); ok { - m := mr.StateSnapshotMeta() - ret.Lineage = m.Lineage - ret.Serial = m.Serial - } - - return ret -} - // RefreshAndRead refreshes the persistent snapshot in the given state manager // and then returns it. //