diff --git a/command/init.go b/command/init.go index 12cd13592..c4e9554df 100644 --- a/command/init.go +++ b/command/init.go @@ -675,6 +675,12 @@ func (c *InitCommand) backendConfigOverrideBody(flags rawFlags, schema *configsc synthVals = make(map[string]cty.Value) } + if len(items) == 1 && items[0].Value == "" { + // Explicitly remove all -backend-config options. + // We do this by setting an empty but non-nil ConfigOverrides. + return configs.SynthBody("-backend-config=''", synthVals), diags + } + for _, item := range items { eq := strings.Index(item.Value, "=") diff --git a/command/init_test.go b/command/init_test.go index 7aadb0f0f..dfae2e02a 100644 --- a/command/init_test.go +++ b/command/init_test.go @@ -1,6 +1,7 @@ package command import ( + "encoding/json" "fmt" "io/ioutil" "log" @@ -410,6 +411,113 @@ func TestInit_backendConfigKV(t *testing.T) { } } +func TestInit_backendConfigKVReInit(t *testing.T) { + // Create a temporary working directory that is empty + td := tempDir(t) + copy.CopyDir(testFixturePath("init-backend-config-kv"), td) + defer os.RemoveAll(td) + defer testChdir(t, td)() + + ui := new(cli.MockUi) + c := &InitCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + }, + } + + args := []string{"-backend-config", "path=test"} + if code := c.Run(args); code != 0 { + t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) + } + + ui = new(cli.MockUi) + c = &InitCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + }, + } + + // a second init should require no changes, nor should it change the backend. + args = []string{"-input=false"} + if code := c.Run(args); code != 0 { + t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) + } + + // make sure the backend is configured how we expect + configState := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) + cfg := map[string]interface{}{} + if err := json.Unmarshal(configState.Backend.ConfigRaw, &cfg); err != nil { + t.Fatal(err) + } + if cfg["path"] != "test" { + t.Fatalf(`expected backend path="test", got path="%v"`, cfg["path"]) + } + + // override the -backend-config options by settings + args = []string{"-input=false", "-backend-config", ""} + if code := c.Run(args); code != 0 { + t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) + } + + // make sure the backend is configured how we expect + configState = testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) + cfg = map[string]interface{}{} + if err := json.Unmarshal(configState.Backend.ConfigRaw, &cfg); err != nil { + t.Fatal(err) + } + if cfg["path"] != nil { + t.Fatalf(`expected backend path="", got path="%v"`, cfg["path"]) + } +} + +func TestInit_backendConfigKVReInitWithConfigDiff(t *testing.T) { + // Create a temporary working directory that is empty + td := tempDir(t) + copy.CopyDir(testFixturePath("init-backend"), td) + defer os.RemoveAll(td) + defer testChdir(t, td)() + + ui := new(cli.MockUi) + c := &InitCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + }, + } + + args := []string{"-input=false"} + if code := c.Run(args); code != 0 { + t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) + } + + ui = new(cli.MockUi) + c = &InitCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + }, + } + + // a second init with identical config should require no changes, nor + // should it change the backend. + args = []string{"-input=false", "-backend-config", "path=foo"} + if code := c.Run(args); code != 0 { + t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) + } + + // make sure the backend is configured how we expect + configState := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) + cfg := map[string]interface{}{} + if err := json.Unmarshal(configState.Backend.ConfigRaw, &cfg); err != nil { + t.Fatal(err) + } + if cfg["path"] != "foo" { + t.Fatalf(`expected backend path="foo", got path="%v"`, cfg["foo"]) + } +} + func TestInit_targetSubdir(t *testing.T) { // Create a temporary working directory that is empty td := tempDir(t) @@ -625,7 +733,7 @@ func TestInit_inputFalse(t *testing.T) { } // A missing input=false should abort rather than loop infinitely - args = []string{"-backend-config=path=bar"} + args = []string{"-backend-config=path=baz"} if code := c.Run(args); code == 0 { t.Fatal("init should have failed", ui.OutputWriter) } diff --git a/command/meta_backend.go b/command/meta_backend.go index 7f94135f7..c62be4630 100644 --- a/command/meta_backend.go +++ b/command/meta_backend.go @@ -243,9 +243,8 @@ func (m *Meta) BackendForPlan(settings plans.Backend) (backend.Enhanced, tfdiags if validateDiags.HasErrors() { return nil, diags } - configVal = newVal - configureDiags := b.Configure(configVal) + configureDiags := b.Configure(newVal) diags = diags.Append(configureDiags) // If the backend supports CLI initialization, do it. @@ -521,12 +520,11 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di // Potentially changing a backend configuration case c != nil && !s.Backend.Empty(): - // If we're not initializing, then it's sufficient for the configuration - // hashes to match, since that suggests that the static backend - // settings in the configuration files are unchanged. (The only - // record we have of CLI overrides is in the settings cache in this - // case, so we have no other source to compare with. - if !opts.Init && uint64(cHash) == s.Backend.Hash { + // We are not going to migrate if were not initializing and the hashes + // match indicating that the stored config is valid. If we are + // initializing, then we also assume the the backend config is OK if + // the hashes match, as long as we're not providing any new overrides. + if (uint64(cHash) == s.Backend.Hash) && (!opts.Init || opts.ConfigOverride == nil) { log.Printf("[TRACE] Meta.Backend: using already-initialized, unchanged %q backend configuration", c.Type) return m.backend_C_r_S_unchanged(c, cHash, sMgr) } @@ -922,9 +920,8 @@ func (m *Meta) backend_C_r_S_unchanged(c *configs.Backend, cHash int, sMgr *stat if validDiags.HasErrors() { return nil, diags } - configVal = newVal - configDiags := b.Configure(configVal) + configDiags := b.Configure(newVal) diags = diags.Append(configDiags) if configDiags.HasErrors() { return nil, diags @@ -1051,9 +1048,8 @@ func (m *Meta) backendInitFromConfig(c *configs.Backend) (backend.Backend, cty.V if validateDiags.HasErrors() { return nil, cty.NilVal, diags } - configVal = newVal - configureDiags := b.Configure(configVal) + configureDiags := b.Configure(newVal) diags = diags.Append(configureDiags.InConfigBody(c.Config)) return b, configVal, diags @@ -1082,9 +1078,8 @@ func (m *Meta) backendInitFromSaved(s *terraform.BackendState) (backend.Backend, if validateDiags.HasErrors() { return nil, diags } - configVal = newVal - configureDiags := b.Configure(configVal) + configureDiags := b.Configure(newVal) diags = diags.Append(configureDiags) return b, diags