From e75b666591e56e2dd93fc86a41329db2377d1088 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 1 Mar 2017 11:34:45 -0800 Subject: [PATCH] command: test multi-state to single state --- backend/local/backend.go | 32 +++++++++- command/meta_backend_migrate.go | 34 +++++++++- command/meta_backend_test.go | 62 +++++++++++++++++++ .../.terraform/terraform.tfstate | 22 +++++++ .../local-state.tfstate | 6 ++ .../backend-change-multi-to-single/main.tf | 5 ++ .../env2/terraform.tfstate | 6 ++ 7 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 command/test-fixtures/backend-change-multi-to-single/.terraform/terraform.tfstate create mode 100644 command/test-fixtures/backend-change-multi-to-single/local-state.tfstate create mode 100644 command/test-fixtures/backend-change-multi-to-single/main.tf create mode 100644 command/test-fixtures/backend-change-multi-to-single/terraform.tfstate.d/env2/terraform.tfstate diff --git a/backend/local/backend.go b/backend/local/backend.go index 0b592e33d..61df56bde 100644 --- a/backend/local/backend.go +++ b/backend/local/backend.go @@ -47,9 +47,13 @@ type Local struct { // // StateBackupPath is the local path where a backup file will be written. // Set this to "-" to disable state backup. + // + // StateEnvPath is the path to the folder containing environments. This + // defaults to DefaultEnvDir if not set. StatePath string StateOutPath string StateBackupPath string + StateEnvDir string // We only want to create a single instance of a local state, so store them // here as they're loaded. @@ -266,6 +270,13 @@ func (b *Local) init() { "path": &schema.Schema{ Type: schema.TypeString, Optional: true, + Default: "", + }, + + "environment_dir": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "", }, }, @@ -288,6 +299,13 @@ func (b *Local) schemaConfigure(ctx context.Context) error { b.StateOutPath = path } + if raw, ok := d.GetOk("environment_dir"); ok { + path := raw.(string) + if path != "" { + b.StateEnvDir = path + } + } + return nil } @@ -302,12 +320,17 @@ func (b *Local) StatePaths(name string) (string, string, string) { name = backend.DefaultStateName } + envDir := DefaultEnvDir + if b.StateEnvDir != "" { + envDir = b.StateEnvDir + } + if name == backend.DefaultStateName { if statePath == "" { statePath = DefaultStateFilename } } else { - statePath = filepath.Join(DefaultEnvDir, name, DefaultStateFilename) + statePath = filepath.Join(envDir, name, DefaultStateFilename) } if stateOutPath == "" { @@ -330,7 +353,12 @@ func (b *Local) createState(name string) error { return nil } - stateDir := filepath.Join(DefaultEnvDir, name) + envDir := DefaultEnvDir + if b.StateEnvDir != "" { + envDir = b.StateEnvDir + } + + stateDir := filepath.Join(envDir, name) s, err := os.Stat(stateDir) if err == nil && s.IsDir() { // no need to check for os.IsNotExist, since that is covered by os.MkdirAll diff --git a/command/meta_backend_migrate.go b/command/meta_backend_migrate.go index 7cabdc3a1..049d5d274 100644 --- a/command/meta_backend_migrate.go +++ b/command/meta_backend_migrate.go @@ -70,7 +70,7 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error { return m.backendMigrateState_s_s(opts) } - panic("unhandled") + return m.backendMigrateState_S_s(opts) // Multi-state to multi-state. We merge the states together (migrating // each from the source to the destination one by one). @@ -103,6 +103,29 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error { // //------------------------------------------------------------------- +// Multi-state to single state. +func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error { + // Ask the user if they want to migrate their existing remote state + migrate, err := m.confirm(&terraform.InputOpts{ + Id: "backend-migrate-multistate-to-single", + Query: fmt.Sprintf( + "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), + }) + if err != nil { + return fmt.Errorf( + "Error asking for state migration action: %s", err) + } + if !migrate { + return fmt.Errorf("Migration aborted by user.") + } + + // Copy the default state + 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) @@ -336,3 +359,12 @@ Two (%[2]q): %[4]s Do you want to copy the state from %[1]q to %[2]q? Enter "yes" to copy 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. +If you continue, Terraform will offer to copy your current environment +%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 d742bce94..f20ae40d6 100644 --- a/command/meta_backend_test.go +++ b/command/meta_backend_test.go @@ -1064,6 +1064,68 @@ func TestMetaBackend_configuredChangeCopy_multiToSingleDefault(t *testing.T) { } } +// Changing a configured backend that supports multi-state to a +// backend that only supports single states. +func TestMetaBackend_configuredChangeCopy_multiToSingle(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) + + // 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" { + 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 diff --git a/command/test-fixtures/backend-change-multi-to-single/.terraform/terraform.tfstate b/command/test-fixtures/backend-change-multi-to-single/.terraform/terraform.tfstate new file mode 100644 index 000000000..073bd7a82 --- /dev/null +++ b/command/test-fixtures/backend-change-multi-to-single/.terraform/terraform.tfstate @@ -0,0 +1,22 @@ +{ + "version": 3, + "serial": 0, + "lineage": "666f9301-7e65-4b19-ae23-71184bb19b03", + "backend": { + "type": "local", + "config": { + "path": "local-state.tfstate" + }, + "hash": 9073424445967744180 + }, + "modules": [ + { + "path": [ + "root" + ], + "outputs": {}, + "resources": {}, + "depends_on": [] + } + ] +} diff --git a/command/test-fixtures/backend-change-multi-to-single/local-state.tfstate b/command/test-fixtures/backend-change-multi-to-single/local-state.tfstate new file mode 100644 index 000000000..88c1d86ec --- /dev/null +++ b/command/test-fixtures/backend-change-multi-to-single/local-state.tfstate @@ -0,0 +1,6 @@ +{ + "version": 3, + "terraform_version": "0.8.2", + "serial": 7, + "lineage": "backend-change" +} diff --git a/command/test-fixtures/backend-change-multi-to-single/main.tf b/command/test-fixtures/backend-change-multi-to-single/main.tf new file mode 100644 index 000000000..2f67c6f1b --- /dev/null +++ b/command/test-fixtures/backend-change-multi-to-single/main.tf @@ -0,0 +1,5 @@ +terraform { + backend "local-single" { + path = "local-state-2.tfstate" + } +} diff --git a/command/test-fixtures/backend-change-multi-to-single/terraform.tfstate.d/env2/terraform.tfstate b/command/test-fixtures/backend-change-multi-to-single/terraform.tfstate.d/env2/terraform.tfstate new file mode 100644 index 000000000..855a27f4c --- /dev/null +++ b/command/test-fixtures/backend-change-multi-to-single/terraform.tfstate.d/env2/terraform.tfstate @@ -0,0 +1,6 @@ +{ + "version": 3, + "terraform_version": "0.8.2", + "serial": 7, + "lineage": "backend-change-env2" +}