From 57f6e018301361aa6ef2529bce1df3b0ed828e34 Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Wed, 27 Mar 2019 16:01:07 +0100 Subject: [PATCH 1/2] 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 From 21cca34716d6968244885e1926c5ad36af9b35c8 Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Wed, 27 Mar 2019 17:41:11 +0100 Subject: [PATCH 2/2] Fixup the docs for WriteStateForMigration --- states/statemgr/migrate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/states/statemgr/migrate.go b/states/statemgr/migrate.go index 8e263e07b..8f0d6799e 100644 --- a/states/statemgr/migrate.go +++ b/states/statemgr/migrate.go @@ -31,7 +31,7 @@ type Migrator interface { // the given file and the current file and complete the update only if // that function returns nil. If force is set this may override such // checks, but some backends do not support forcing and so will act - // as if force is always true. + // as if force is always false. WriteStateForMigration(f *statefile.File, force bool) error }