From 57f6e018301361aa6ef2529bce1df3b0ed828e34 Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Wed, 27 Mar 2019 16:01:07 +0100 Subject: [PATCH] backend/local: preserve serial and lineage on failure When failing to write the state, the local backend writes the state to a local file called `errrored.tfstate`. Previously it would do so by creating a new state file which would use a new serial and lineage. By exorting the existing state file and directly assigning the new state, the serial and lineage are preserved. --- backend/local/backend_apply.go | 17 +++++++++++------ states/statemgr/filesystem.go | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/backend/local/backend_apply.go b/backend/local/backend_apply.go index 8263a2089..b12448718 100644 --- a/backend/local/backend_apply.go +++ b/backend/local/backend_apply.go @@ -157,7 +157,15 @@ func (b *Local) opApply( runningOp.State = applyState err := statemgr.WriteAndPersist(opState, applyState) if err != nil { - diags = diags.Append(b.backupStateForError(applyState, err)) + // Export the state file from the state manager and assign the new + // state. This is needed to preserve the existing serial and lineage. + stateFile := statemgr.Export(opState) + if stateFile == nil { + stateFile = &statefile.File{} + } + stateFile.State = applyState + + diags = diags.Append(b.backupStateForError(stateFile, err)) b.ReportResult(runningOp, diags) return } @@ -208,11 +216,11 @@ func (b *Local) opApply( // to local disk to help the user recover. This is a "last ditch effort" sort // of thing, so we really don't want to end up in this codepath; we should do // everything we possibly can to get the state saved _somewhere_. -func (b *Local) backupStateForError(applyState *states.State, err error) error { +func (b *Local) backupStateForError(stateFile *statefile.File, err error) error { b.CLI.Error(fmt.Sprintf("Failed to save state: %s\n", err)) local := statemgr.NewFilesystem("errored.tfstate") - writeErr := local.WriteState(applyState) + writeErr := local.WriteStateForMigration(stateFile, true) if writeErr != nil { b.CLI.Error(fmt.Sprintf( "Also failed to create local state file for recovery: %s\n\n", writeErr, @@ -223,9 +231,6 @@ func (b *Local) backupStateForError(applyState *states.State, err error) error { // but at least the user has _some_ path to recover if we end up // here for some reason. stateBuf := new(bytes.Buffer) - stateFile := &statefile.File{ - State: applyState, - } jsonErr := statefile.Write(stateFile, stateBuf) if jsonErr != nil { b.CLI.Error(fmt.Sprintf( diff --git a/states/statemgr/filesystem.go b/states/statemgr/filesystem.go index ed14a11bf..8338e5741 100644 --- a/states/statemgr/filesystem.go +++ b/states/statemgr/filesystem.go @@ -49,7 +49,7 @@ type Filesystem struct { lockID string // created is set to true if stateFileOut didn't exist before we created it. - // This is mostly so we can clean up emtpy files during tests, but doesn't + // This is mostly so we can clean up empty files during tests, but doesn't // hurt to remove file we never wrote to. created bool