From 8050eda52dddf7a62fba602725ca6afdf637fdaf Mon Sep 17 00:00:00 2001 From: James Bardin Date: Fri, 31 Mar 2017 15:21:32 -0400 Subject: [PATCH] don't delete local state on a local backend Don't erase local state during backend migration if the new and old paths are the same. Skipping the confirmation and copy are handled in another patch, but the local state was always erased by default, even when it was our new state. --- command/meta_backend.go | 28 +++++++++++++++++----- command/meta_backend_test.go | 45 ++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/command/meta_backend.go b/command/meta_backend.go index c50214116..afcfe533a 100644 --- a/command/meta_backend.go +++ b/command/meta_backend.go @@ -1002,7 +1002,8 @@ func (m *Meta) backend_C_r_s( } // If the local state is not empty, we need to potentially do a - // state migration to the new backend (with user permission). + // state migration to the new backend (with user permission), unless the + // destination is also "local" if localS := localState.State(); !localS.Empty() { // Perform the migration err = m.backendMigrateState(&backendMigrateOpts{ @@ -1015,12 +1016,27 @@ func (m *Meta) backend_C_r_s( return nil, err } - // We always delete the local state - if err := localState.WriteState(nil); err != nil { - return nil, fmt.Errorf(errBackendMigrateLocalDelete, err) + // we usually remove the local state after migration to prevent + // confusion, but adding a default local backend block to the config + // can get us here too. Don't delete our state if the old and new paths + // are the same. + erase := true + if newLocalB, ok := b.(*backendlocal.Local); ok { + if localB, ok := localB.(*backendlocal.Local); ok { + if newLocalB.StatePath == localB.StatePath { + erase = false + } + } } - if err := localState.PersistState(); err != nil { - return nil, fmt.Errorf(errBackendMigrateLocalDelete, err) + + if erase { + // We always delete the local state, unless that was our new state too. + if err := localState.WriteState(nil); err != nil { + return nil, fmt.Errorf(errBackendMigrateLocalDelete, err) + } + if err := localState.PersistState(); err != nil { + return nil, fmt.Errorf(errBackendMigrateLocalDelete, err) + } } } diff --git a/command/meta_backend_test.go b/command/meta_backend_test.go index 143759f98..ea0085dba 100644 --- a/command/meta_backend_test.go +++ b/command/meta_backend_test.go @@ -3275,6 +3275,51 @@ func TestMetaBackend_configureWithExtra(t *testing.T) { } } +// when confniguring a default local state, don't delete local state +func TestMetaBackend_localDoesNotDeleteLocal(t *testing.T) { + // Create a temporary working directory that is empty + td := tempDir(t) + copy.CopyDir(testFixturePath("init-backend-empty"), td) + defer os.RemoveAll(td) + defer testChdir(t, td)() + + // create our local state + orig := &terraform.State{ + Modules: []*terraform.ModuleState{ + { + Path: []string{"root"}, + Outputs: map[string]*terraform.OutputState{ + "foo": { + Value: "bar", + Type: "string", + }, + }, + }, + }, + } + + err := (&state.LocalState{Path: DefaultStateFilename}).WriteState(orig) + if err != nil { + t.Fatal(err) + } + + m := testMetaBackend(t, nil) + m.forceInitCopy = true + // init the backend + _, err = m.Backend(&BackendOpts{ + Init: true, + }) + if err != nil { + t.Fatalf("bad: %s", err) + } + + // check that we can read the state + s := testStateRead(t, DefaultStateFilename) + if s.Empty() { + t.Fatal("our state was deleted") + } +} + // move options from config to -backend-config func TestMetaBackend_configToExtra(t *testing.T) { // Create a temporary working directory that is empty