diff --git a/internal/command/init.go b/internal/command/init.go index 551a4d0b8..8083b7513 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -44,6 +44,8 @@ func (c *InitCommand) Run(args []string) int { cmdFlags.StringVar(&flagFromModule, "from-module", "", "copy the source of the given module into the directory before init") cmdFlags.BoolVar(&flagGet, "get", true, "") cmdFlags.BoolVar(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data") + cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") + cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") cmdFlags.BoolVar(&c.reconfigure, "reconfigure", false, "reconfigure") cmdFlags.BoolVar(&c.migrateState, "migrate-state", false, "migrate state") cmdFlags.BoolVar(&flagUpgrade, "upgrade", false, "") @@ -932,6 +934,8 @@ func (c *InitCommand) AutocompleteFlags() complete.Flags { "-from-module": completePredictModuleSource, "-get": completePredictBoolean, "-input": completePredictBoolean, + "-lock": completePredictBoolean, + "-lock-timeout": complete.PredictAnything, "-no-color": complete.PredictNothing, "-plugin-dir": complete.PredictDirs(""), "-reconfigure": complete.PredictNothing, @@ -959,7 +963,8 @@ Usage: terraform [global options] init [options] Options: - -backend=true Configure the backend for this configuration. + -backend=false Disable backend initialization for this configuration + and use the previously initialized backend instead. -backend-config=path This can be either a path to an HCL file with key/value assignments (same format as terraform.tfvars) or a @@ -975,10 +980,17 @@ Options: -from-module=SOURCE Copy the contents of the given module into the target directory before initialization. - -get=true Download any modules for this configuration. + -get=false Disable downloading modules for this configuration. - -input=true Ask for input if necessary. If false, will error if - input was required. + -input=false Disable prompting for missing backend configuration + values. This will result in an error if the backend + configuration is not fully specified. + + -lock=false Don't hold a state lock during backend migration. + This is dangerous if others might concurrently run + commands against the same workspace. + + -lock-timeout=0s Duration to retry a state lock. -no-color If specified, output won't contain any color. @@ -993,9 +1005,10 @@ Options: -migrate-state Reconfigure the backend, and attempt to migrate any existing state. - -upgrade=false If installing modules (-get) or plugins, ignore - previously-downloaded objects and install the - latest version allowed within configured constraints. + -upgrade Install the latest module and provider versions + allowed within configured constraints, overriding the + default behavior of selecting exactly the version + recorded in the dependency lockfile. -lockfile=MODE Set a dependency lockfile mode. Currently only "readonly" is valid. diff --git a/internal/command/init_test.go b/internal/command/init_test.go index 74d4b0f64..56bc1f911 100644 --- a/internal/command/init_test.go +++ b/internal/command/init_test.go @@ -562,6 +562,60 @@ func TestInit_backendConfigFileChange(t *testing.T) { } } +func TestInit_backendMigrateWhileLocked(t *testing.T) { + // Create a temporary working directory that is empty + td := tempDir(t) + testCopyDir(t, testFixturePath("init-backend-migrate-while-locked"), td) + defer os.RemoveAll(td) + defer testChdir(t, td)() + + providerSource, close := newMockProviderSource(t, map[string][]string{ + "hashicorp/test": {"1.2.3"}, + }) + defer close() + + ui := new(cli.MockUi) + view, _ := testView(t) + c := &InitCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(testProvider()), + ProviderSource: providerSource, + Ui: ui, + View: view, + }, + } + + // Create some state, so the backend has something to migrate from + f, err := os.Create("local-state.tfstate") + if err != nil { + t.Fatalf("err: %s", err) + } + err = writeStateForTesting(testState(), f) + f.Close() + if err != nil { + t.Fatalf("err: %s", err) + } + + // Lock the source state + unlock, err := testLockState(testDataDir, "local-state.tfstate") + if err != nil { + t.Fatal(err) + } + defer unlock() + + // Attempt to migrate + args := []string{"-backend-config", "input.config", "-migrate-state", "-force-copy"} + if code := c.Run(args); code == 0 { + t.Fatalf("expected nonzero exit code: %s", ui.OutputWriter.String()) + } + + // Disabling locking should work + args = []string{"-backend-config", "input.config", "-migrate-state", "-force-copy", "-lock=false"} + if code := c.Run(args); code != 0 { + t.Fatalf("expected zero exit code, got %d: %s", code, ui.ErrorWriter.String()) + } +} + func TestInit_backendConfigKV(t *testing.T) { // Create a temporary working directory that is empty td := tempDir(t) diff --git a/internal/command/testdata/init-backend-migrate-while-locked/.terraform/terraform.tfstate b/internal/command/testdata/init-backend-migrate-while-locked/.terraform/terraform.tfstate new file mode 100644 index 000000000..073bd7a82 --- /dev/null +++ b/internal/command/testdata/init-backend-migrate-while-locked/.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/internal/command/testdata/init-backend-migrate-while-locked/input.config b/internal/command/testdata/init-backend-migrate-while-locked/input.config new file mode 100644 index 000000000..6cd14f4a3 --- /dev/null +++ b/internal/command/testdata/init-backend-migrate-while-locked/input.config @@ -0,0 +1 @@ +path = "hello" diff --git a/internal/command/testdata/init-backend-migrate-while-locked/main.tf b/internal/command/testdata/init-backend-migrate-while-locked/main.tf new file mode 100644 index 000000000..bea8e789f --- /dev/null +++ b/internal/command/testdata/init-backend-migrate-while-locked/main.tf @@ -0,0 +1,5 @@ +terraform { + backend "local" { + path = "local-state.tfstate" + } +}