diff --git a/command/meta_backend_migrate.go b/command/meta_backend_migrate.go index 049d5d274..74b685338 100644 --- a/command/meta_backend_migrate.go +++ b/command/meta_backend_migrate.go @@ -48,6 +48,10 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error { errMigrateLoadStates), opts.TwoType, err) } + // Setup defaults + opts.oneEnv = backend.DefaultStateName + opts.twoEnv = backend.DefaultStateName + // Determine migration behavior based on whether the source/destionation // supports multi-state. switch { @@ -105,6 +109,8 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error { // Multi-state to single state. func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error { + currentEnv := m.Env() + // Ask the user if they want to migrate their existing remote state migrate, err := m.confirm(&terraform.InputOpts{ Id: "backend-migrate-multistate-to-single", @@ -112,7 +118,9 @@ func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error { "Destination state %q doesn't support environments (named states).\n"+ "Do you want to copy only your current environment?", opts.TwoType), - Description: strings.TrimSpace(inputBackendMigrateMultiToSingle), + Description: fmt.Sprintf( + strings.TrimSpace(inputBackendMigrateMultiToSingle), + opts.OneType, opts.TwoType, currentEnv), }) if err != nil { return fmt.Errorf( @@ -123,12 +131,13 @@ func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error { } // Copy the default state + opts.oneEnv = currentEnv return m.backendMigrateState_s_s(opts) } // Single state to single state, assumed default state name. func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error { - stateOne, err := opts.One.State(backend.DefaultStateName) + stateOne, err := opts.One.State(opts.oneEnv) if err != nil { return fmt.Errorf(strings.TrimSpace( errMigrateSingleLoadDefault), opts.OneType, err) @@ -138,7 +147,7 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error { errMigrateSingleLoadDefault), opts.OneType, err) } - stateTwo, err := opts.Two.State(backend.DefaultStateName) + stateTwo, err := opts.Two.State(opts.twoEnv) if err != nil { return fmt.Errorf(strings.TrimSpace( errMigrateSingleLoadDefault), opts.TwoType, err) @@ -314,6 +323,11 @@ func (m *Meta) backendMigrateNonEmptyConfirm( type backendMigrateOpts struct { OneType, TwoType string One, Two backend.Backend + + // Fields below are set internally when migrate is called + + oneEnv string // source env + twoEnv string // dest env } const errMigrateLoadStates = ` @@ -361,10 +375,10 @@ and "no" to start with the existing state in %[2]q. ` const inputBackendMigrateMultiToSingle = ` -The existing backend %q supports environments and you currently are -using more than one. The target backend %q doesn't support environments. +The existing backend %[1]q supports environments and you currently are +using more than one. The target backend %[2]q doesn't support environments. If you continue, Terraform will offer to copy your current environment -%q to the default environment in the target. Your existing environments +%[3]q to the default environment in the target. Your existing environments in the source backend won't be modified. If you want to switch environments, back them up, or cancel altogether, answer "no" and Terraform will abort. ` diff --git a/command/meta_backend_test.go b/command/meta_backend_test.go index f20ae40d6..75d84068f 100644 --- a/command/meta_backend_test.go +++ b/command/meta_backend_test.go @@ -1126,6 +1126,73 @@ func TestMetaBackend_configuredChangeCopy_multiToSingle(t *testing.T) { } } +// Changing a configured backend that supports multi-state to a +// backend that only supports single states. +func TestMetaBackend_configuredChangeCopy_multiToSingleCurrentEnv(t *testing.T) { + // Create a temporary working directory that is empty + td := tempDir(t) + copy.CopyDir(testFixturePath("backend-change-multi-to-single"), td) + defer os.RemoveAll(td) + defer testChdir(t, td)() + + // Register the single-state backend + backendinit.Set("local-single", backendlocal.TestNewLocalSingle) + defer backendinit.Set("local-single", nil) + + // Ask input + defer testInputMap(t, map[string]string{ + "backend-migrate-to-new": "yes", + "backend-migrate-multistate-to-single": "yes", + "backend-migrate-copy-to-empty": "yes", + })() + + // Setup the meta + m := testMetaBackend(t, nil) + + // Change env + if err := m.SetEnv("env2"); err != nil { + t.Fatalf("bad: %s", err) + } + + // Get the backend + b, err := m.Backend(&BackendOpts{Init: true}) + if err != nil { + t.Fatalf("bad: %s", err) + } + + // Check the state + s, err := b.State(backend.DefaultStateName) + if err != nil { + t.Fatalf("bad: %s", err) + } + if err := s.RefreshState(); err != nil { + t.Fatalf("bad: %s", err) + } + state := s.State() + if state == nil { + t.Fatal("state should not be nil") + } + if state.Lineage != "backend-change-env2" { + t.Fatalf("bad: %#v", state) + } + + // Verify no local state + if _, err := os.Stat(DefaultStateFilename); err == nil { + t.Fatal("file should not exist") + } + + // Verify no local backup + if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil { + t.Fatal("file should not exist") + } + + // Verify existing environments exist + envPath := filepath.Join(backendlocal.DefaultEnvDir, "env2", backendlocal.DefaultStateFilename) + if _, err := os.Stat(envPath); err != nil { + t.Fatal("env should exist") + } +} + // Unsetting a saved backend func TestMetaBackend_configuredUnset(t *testing.T) { // Create a temporary working directory that is empty