From 81639480fb4c84b9b0a34c2e0c4aa16b66dc1f2f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 16 Mar 2017 11:47:59 -0700 Subject: [PATCH] command: recompute config hash with ConfigFile set Fixes #12749 If we merge in an extra partial config we need to recompute the hash to compare with the old value to detect that change. This hash needs to NOT be stored and just used as a temporary. We want to keep the original hash in the state so that we don't detect a change from the config (since the config will always be partial). --- command/init_test.go | 32 +++++++++++++++++++ command/meta_backend.go | 16 ++++++++-- .../.terraform/terraform.tfstate | 22 +++++++++++++ .../input.config | 1 + .../init-backend-config-file-change/main.tf | 5 +++ 5 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 command/test-fixtures/init-backend-config-file-change/.terraform/terraform.tfstate create mode 100644 command/test-fixtures/init-backend-config-file-change/input.config create mode 100644 command/test-fixtures/init-backend-config-file-change/main.tf diff --git a/command/init_test.go b/command/init_test.go index 6030b877e9..295098c624 100644 --- a/command/init_test.go +++ b/command/init_test.go @@ -321,6 +321,38 @@ func TestInit_backendConfigFile(t *testing.T) { } } +func TestInit_backendConfigFileChange(t *testing.T) { + // Create a temporary working directory that is empty + td := tempDir(t) + copy.CopyDir(testFixturePath("init-backend-config-file-change"), td) + defer os.RemoveAll(td) + defer testChdir(t, td)() + + // Ask input + defer testInputMap(t, map[string]string{ + "backend-migrate-to-new": "no", + })() + + ui := new(cli.MockUi) + c := &InitCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(testProvider()), + Ui: ui, + }, + } + + args := []string{"-backend-config", "input.config"} + if code := c.Run(args); code != 0 { + t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) + } + + // Read our saved backend config and verify we have our settings + state := testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) + if v := state.Backend.Config["path"]; v != "hello" { + t.Fatalf("bad: %#v", v) + } +} + func TestInit_copyBackendDst(t *testing.T) { // Create a temporary working directory that is empty td := tempDir(t) diff --git a/command/meta_backend.go b/command/meta_backend.go index 31dc9b5665..f1ad37190a 100644 --- a/command/meta_backend.go +++ b/command/meta_backend.go @@ -302,6 +302,16 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, error) { return nil, fmt.Errorf("Error loading backend config: %s", err) } + // cHash defaults to zero unless c is set + var cHash uint64 + if c != nil { + // We need to rehash to get the value since we may have merged the + // config with an extra ConfigFile. We don't do this when merging + // because we do want the ORIGINAL value on c so that we store + // that to not detect drift. This is covered in tests. + cHash = c.Rehash() + } + // Get the path to where we store a local cache of backend configuration // if we're using a remote backend. This may not yet exist which means // we haven't used a non-local backend before. That is okay. @@ -384,7 +394,7 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, error) { case c != nil && s.Remote.Empty() && !s.Backend.Empty(): // If our configuration is the same, then we're just initializing // a previously configured remote backend. - if !s.Backend.Empty() && s.Backend.Hash == c.Hash { + if !s.Backend.Empty() && s.Backend.Hash == cHash { return m.backend_C_r_S_unchanged(c, sMgr) } @@ -398,7 +408,7 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, error) { log.Printf( "[WARN] command: backend config change! saved: %d, new: %d", - s.Backend.Hash, c.Hash) + s.Backend.Hash, cHash) return m.backend_C_r_S_changed(c, sMgr, true) // Configuring a backend for the first time while having legacy @@ -420,7 +430,7 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, error) { case c != nil && !s.Remote.Empty() && !s.Backend.Empty(): // If the hashes are the same, we have a legacy remote state with // an unchanged stored backend state. - if s.Backend.Hash == c.Hash { + if s.Backend.Hash == cHash { if !opts.Init { initReason := fmt.Sprintf( "Legacy remote state found with configured backend %q", diff --git a/command/test-fixtures/init-backend-config-file-change/.terraform/terraform.tfstate b/command/test-fixtures/init-backend-config-file-change/.terraform/terraform.tfstate new file mode 100644 index 0000000000..073bd7a822 --- /dev/null +++ b/command/test-fixtures/init-backend-config-file-change/.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/init-backend-config-file-change/input.config b/command/test-fixtures/init-backend-config-file-change/input.config new file mode 100644 index 0000000000..6cd14f4a3d --- /dev/null +++ b/command/test-fixtures/init-backend-config-file-change/input.config @@ -0,0 +1 @@ +path = "hello" diff --git a/command/test-fixtures/init-backend-config-file-change/main.tf b/command/test-fixtures/init-backend-config-file-change/main.tf new file mode 100644 index 0000000000..ca1bd3921e --- /dev/null +++ b/command/test-fixtures/init-backend-config-file-change/main.tf @@ -0,0 +1,5 @@ +terraform { + backend "local" { + path = "local-state.tfstate" + } +}