diff --git a/command/init_test.go b/command/init_test.go index 05ad340938..b47936f057 100644 --- a/command/init_test.go +++ b/command/init_test.go @@ -13,6 +13,8 @@ import ( "github.com/hashicorp/terraform/helper/copy" "github.com/hashicorp/terraform/plugin/discovery" + "github.com/hashicorp/terraform/state" + "github.com/hashicorp/terraform/terraform" "github.com/mitchellh/cli" ) @@ -530,10 +532,48 @@ func TestInit_inputFalse(t *testing.T) { t.Fatalf("bad: \n%s", ui.ErrorWriter) } + // write different states for foo and bar + s := terraform.NewState() + s.Lineage = "foo" + if err := (&state.LocalState{Path: "foo"}).WriteState(s); err != nil { + t.Fatal(err) + } + s.Lineage = "bar" + if err := (&state.LocalState{Path: "bar"}).WriteState(s); err != nil { + t.Fatal(err) + } + + ui = new(cli.MockUi) + c = &InitCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + }, + } + args = []string{"-input=false", "-backend-config=path=bar"} if code := c.Run(args); code == 0 { t.Fatal("init should have failed", ui.OutputWriter) } + + errMsg := ui.ErrorWriter.String() + if !strings.Contains(errMsg, "input disabled") { + t.Fatal("expected input disabled error, got", errMsg) + } + + ui = new(cli.MockUi) + c = &InitCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + }, + } + + // A missing input=false should abort rather than loop infinitely + args = []string{"-backend-config=path=bar"} + if code := c.Run(args); code == 0 { + t.Fatal("init should have failed", ui.OutputWriter) + } } func TestInit_getProvider(t *testing.T) { diff --git a/command/meta.go b/command/meta.go index 27f7765f95..04c757497f 100644 --- a/command/meta.go +++ b/command/meta.go @@ -476,7 +476,8 @@ func (m *Meta) confirm(opts *terraform.InputOpts) (bool, error) { if !m.Input() { return false, errors.New("input is disabled") } - for { + + for i := 0; i < 2; i++ { v, err := m.UIInput().Input(opts) if err != nil { return false, fmt.Errorf( @@ -490,6 +491,7 @@ func (m *Meta) confirm(opts *terraform.InputOpts) (bool, error) { return true, nil } } + return false, nil } // showDiagnostics displays error and warning messages in the UI. diff --git a/command/meta_backend.go b/command/meta_backend.go index 4a52ef00ec..b6adf17f7a 100644 --- a/command/meta_backend.go +++ b/command/meta_backend.go @@ -678,47 +678,30 @@ func (m *Meta) backend_c_r_S( // Get the backend type for output backendType := s.Backend.Type - copy := m.forceInitCopy - if !copy { - var err error - // Confirm with the user that the copy should occur - copy, err = m.confirm(&terraform.InputOpts{ - Id: "backend-migrate-to-local", - Query: fmt.Sprintf("Do you want to copy the state from %q?", s.Backend.Type), - Description: fmt.Sprintf( - strings.TrimSpace(inputBackendMigrateLocal), s.Backend.Type), - }) - if err != nil { - return nil, fmt.Errorf( - "Error asking for state copy action: %s", err) - } + m.Ui.Output(fmt.Sprintf(strings.TrimSpace(outputBackendMigrateLocal), s.Backend.Type)) + + // Grab a purely local backend to get the local state if it exists + localB, err := m.Backend(&BackendOpts{ForceLocal: true}) + if err != nil { + return nil, fmt.Errorf(strings.TrimSpace(errBackendLocalRead), err) } - // If we're copying, perform the migration - if copy { - // Grab a purely local backend to get the local state if it exists - localB, err := m.Backend(&BackendOpts{ForceLocal: true}) - if err != nil { - return nil, fmt.Errorf(strings.TrimSpace(errBackendLocalRead), err) - } + // Initialize the configured backend + b, err := m.backend_C_r_S_unchanged(c, sMgr) + if err != nil { + return nil, fmt.Errorf( + strings.TrimSpace(errBackendSavedUnsetConfig), s.Backend.Type, err) + } - // Initialize the configured backend - b, err := m.backend_C_r_S_unchanged(c, sMgr) - if err != nil { - return nil, fmt.Errorf( - strings.TrimSpace(errBackendSavedUnsetConfig), s.Backend.Type, err) - } - - // Perform the migration - err = m.backendMigrateState(&backendMigrateOpts{ - OneType: s.Backend.Type, - TwoType: "local", - One: b, - Two: localB, - }) - if err != nil { - return nil, err - } + // Perform the migration + err = m.backendMigrateState(&backendMigrateOpts{ + OneType: s.Backend.Type, + TwoType: "local", + One: b, + Two: localB, + }) + if err != nil { + return nil, err } // Remove the stored metadata @@ -802,40 +785,22 @@ func (m *Meta) backend_c_R_S( // Grab the state s := sMgr.State() - // Ask the user if they want to migrate their existing remote state - copy := m.forceInitCopy - if !copy { - copy, err = m.confirm(&terraform.InputOpts{ - Id: "backend-migrate-to-new", - Query: fmt.Sprintf( - "Do you want to copy the legacy remote state from %q?", - s.Remote.Type), - Description: strings.TrimSpace(inputBackendMigrateLegacyLocal), - }) - if err != nil { - return nil, fmt.Errorf( - "Error asking for state copy action: %s", err) - } + m.Ui.Output(strings.TrimSpace(outputBackendMigrateLegacy)) + // Initialize the legacy backend + oldB, err := m.backendInitFromLegacy(s.Remote) + if err != nil { + return nil, err } - // If the user wants a copy, copy! - if copy { - // Initialize the legacy backend - oldB, err := m.backendInitFromLegacy(s.Remote) - if err != nil { - return nil, err - } - - // Perform the migration - err = m.backendMigrateState(&backendMigrateOpts{ - OneType: s.Remote.Type, - TwoType: "local", - One: oldB, - Two: localB, - }) - if err != nil { - return nil, err - } + // Perform the migration + err = m.backendMigrateState(&backendMigrateOpts{ + OneType: s.Remote.Type, + TwoType: "local", + One: oldB, + Two: localB, + }) + if err != nil { + return nil, err } // Unset the remote state @@ -897,41 +862,22 @@ func (m *Meta) backend_C_R_s( return b, nil } - // Finally, ask the user if they want to copy the state from - // their old remote state location. - copy := m.forceInitCopy - if !copy { - copy, err = m.confirm(&terraform.InputOpts{ - Id: "backend-migrate-to-new", - Query: fmt.Sprintf( - "Do you want to copy the legacy remote state from %q?", - s.Remote.Type), - Description: strings.TrimSpace(inputBackendMigrateLegacy), - }) - if err != nil { - return nil, fmt.Errorf( - "Error asking for state copy action: %s", err) - } + m.Ui.Output(strings.TrimSpace(outputBackendMigrateLegacy)) + // Initialize the legacy backend + oldB, err := m.backendInitFromLegacy(s.Remote) + if err != nil { + return nil, err } - // If the user wants a copy, copy! - if copy { - // Initialize the legacy backend - oldB, err := m.backendInitFromLegacy(s.Remote) - if err != nil { - return nil, err - } - - // Perform the migration - err = m.backendMigrateState(&backendMigrateOpts{ - OneType: s.Remote.Type, - TwoType: c.Type, - One: oldB, - Two: b, - }) - if err != nil { - return nil, err - } + // Perform the migration + err = m.backendMigrateState(&backendMigrateOpts{ + OneType: s.Remote.Type, + TwoType: c.Type, + One: oldB, + Two: b, + }) + if err != nil { + return nil, err } // Unset the remote state @@ -1077,40 +1023,27 @@ func (m *Meta) backend_C_r_S_changed( "Error initializing new backend: %s", err) } - // Check with the user if we want to migrate state - copy := m.forceInitCopy - if !copy { - copy, err = m.confirm(&terraform.InputOpts{ - Id: "backend-migrate-to-new", - Query: fmt.Sprintf("Do you want to copy the state from %q?", s.Backend.Type), - Description: strings.TrimSpace(fmt.Sprintf(inputBackendMigrateChange, s.Backend.Type, c.Type)), - }) - if err != nil { - return nil, fmt.Errorf( - "Error asking for state copy action: %s", err) - } + // no need to confuse the user if the backend types are the same + if s.Backend.Type != c.Type { + m.Ui.Output(strings.TrimSpace(fmt.Sprintf(outputBackendMigrateChange, s.Backend.Type, c.Type))) } - // If we are, then we need to initialize the old backend and - // perform the copy. - if copy { - // Grab the existing backend - oldB, err := m.backend_C_r_S_unchanged(c, sMgr) - if err != nil { - return nil, fmt.Errorf( - "Error loading previously configured backend: %s", err) - } + // Grab the existing backend + oldB, err := m.backend_C_r_S_unchanged(c, sMgr) + if err != nil { + return nil, fmt.Errorf( + "Error loading previously configured backend: %s", err) + } - // Perform the migration - err = m.backendMigrateState(&backendMigrateOpts{ - OneType: s.Backend.Type, - TwoType: c.Type, - One: oldB, - Two: b, - }) - if err != nil { - return nil, err - } + // Perform the migration + err = m.backendMigrateState(&backendMigrateOpts{ + OneType: s.Backend.Type, + TwoType: c.Type, + One: oldB, + Two: b, + }) + if err != nil { + return nil, err } if m.stateLock { @@ -1237,40 +1170,23 @@ func (m *Meta) backend_C_R_S_unchanged( return nil, err } - // Ask if the user wants to move their legacy remote state - copy := m.forceInitCopy - if !copy { - copy, err = m.confirm(&terraform.InputOpts{ - Id: "backend-migrate-to-new", - Query: fmt.Sprintf( - "Do you want to copy the legacy remote state from %q?", - s.Remote.Type), - Description: strings.TrimSpace(inputBackendMigrateLegacy), - }) - if err != nil { - return nil, fmt.Errorf( - "Error asking for state copy action: %s", err) - } + m.Ui.Output(strings.TrimSpace(outputBackendMigrateLegacy)) + + // Initialize the legacy backend + oldB, err := m.backendInitFromLegacy(s.Remote) + if err != nil { + return nil, err } - // If the user wants a copy, copy! - if copy { - // Initialize the legacy backend - oldB, err := m.backendInitFromLegacy(s.Remote) - if err != nil { - return nil, err - } - - // Perform the migration - err = m.backendMigrateState(&backendMigrateOpts{ - OneType: s.Remote.Type, - TwoType: s.Backend.Type, - One: oldB, - Two: b, - }) - if err != nil { - return nil, err - } + // Perform the migration + err = m.backendMigrateState(&backendMigrateOpts{ + OneType: s.Remote.Type, + TwoType: s.Backend.Type, + One: oldB, + Two: b, + }) + if err != nil { + return nil, err } if m.stateLock { @@ -1638,27 +1554,16 @@ Plan Serial: %[1]d Current Serial: %[2]d ` -const inputBackendMigrateChange = ` -Would you like to copy the state from your prior backend %q to the -newly configured %q backend? If you're reconfiguring the same backend, -answering "yes" or "no" shouldn't make a difference. Please answer exactly -"yes" or "no". +const outputBackendMigrateChange = ` +Terraform detected that the backend type changed from %q to %q. ` -const inputBackendMigrateLegacy = ` -Terraform can copy the existing state in your legacy remote state -backend to your newly configured backend. Please answer "yes" or "no". +const outputBackendMigrateLegacy = ` +Terraform detected legacy remote state. ` -const inputBackendMigrateLegacyLocal = ` -Terraform can copy the existing state in your legacy remote state -backend to your local state. Please answer "yes" or "no". -` - -const inputBackendMigrateLocal = ` -Terraform has detected you're unconfiguring your previously set backend. -Would you like to copy the state from %q to local state? Please answer -"yes" or "no". If you answer "no", you will start with a blank local state. +const outputBackendMigrateLocal = ` +Terraform has detected you're unconfiguring your previously set %q backend. ` const outputBackendConfigureWithLegacy = ` @@ -1674,9 +1579,7 @@ const outputBackendReconfigure = ` [reset][bold]Backend configuration changed![reset] Terraform has detected that the configuration specified for the backend -has changed. Terraform will now reconfigure for this backend. If you didn't -intend to reconfigure your backend please undo any changes to the "backend" -section in your Terraform configuration. +has changed. Terraform will now check for existing state in the backends. ` const outputBackendSavedWithLegacy = ` diff --git a/command/meta_backend_migrate.go b/command/meta_backend_migrate.go index 552d70887a..7f60a6605d 100644 --- a/command/meta_backend_migrate.go +++ b/command/meta_backend_migrate.go @@ -337,31 +337,14 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error { func (m *Meta) backendMigrateEmptyConfirm(one, two state.State, opts *backendMigrateOpts) (bool, error) { inputOpts := &terraform.InputOpts{ - Id: "backend-migrate-copy-to-empty", - Query: fmt.Sprintf( - "Do you want to copy state from %q to %q?", - opts.OneType, opts.TwoType), + Id: "backend-migrate-copy-to-empty", + Query: "Do you want to copy existing state to the new backend?", Description: fmt.Sprintf( strings.TrimSpace(inputBackendMigrateEmpty), opts.OneType, opts.TwoType), } - // Confirm with the user that the copy should occur - for { - v, err := m.UIInput().Input(inputOpts) - if err != nil { - return false, fmt.Errorf( - "Error asking for state copy action: %s", err) - } - - switch strings.ToLower(v) { - case "no": - return false, nil - - case "yes": - return true, nil - } - } + return m.confirm(inputOpts) } func (m *Meta) backendMigrateNonEmptyConfirm( @@ -400,31 +383,15 @@ func (m *Meta) backendMigrateNonEmptyConfirm( // Ask for confirmation inputOpts := &terraform.InputOpts{ - Id: "backend-migrate-to-backend", - Query: fmt.Sprintf( - "Do you want to copy state from %q to %q?", - opts.OneType, opts.TwoType), + Id: "backend-migrate-to-backend", + Query: "Do you want to copy existing state to the new backend?", Description: fmt.Sprintf( strings.TrimSpace(inputBackendMigrateNonEmpty), opts.OneType, opts.TwoType, onePath, twoPath), } // Confirm with the user that the copy should occur - for { - v, err := m.UIInput().Input(inputOpts) - if err != nil { - return false, fmt.Errorf( - "Error asking for state copy action: %s", err) - } - - switch strings.ToLower(v) { - case "no": - return false, nil - - case "yes": - return true, nil - } - } + return m.confirm(inputOpts) } type backendMigrateOpts struct { @@ -439,7 +406,8 @@ type backendMigrateOpts struct { } const errMigrateLoadStates = ` -Error inspecting state in %q: %s +Error inspecting states in the %q backend: + %s Prior to changing backends, Terraform inspects the source and destination states to determine what kind of migration steps need to be taken, if any. @@ -448,9 +416,10 @@ destination remain unmodified. Please resolve the above error and try again. ` const errMigrateSingleLoadDefault = ` -Error loading state from %q: %s +Error loading state: + %[2]s -Terraform failed to load the default state from %[1]q. +Terraform failed to load the default state from the %[1]q backend. State migration cannot occur unless the state can be loaded. Backend modification and state migration has been aborted. The state in both the source and the destination remain unmodified. Please resolve the @@ -458,9 +427,9 @@ above error and try again. ` const errMigrateMulti = ` -Error migrating the workspace %q from %q to %q: - -%s +Error migrating the workspace %q from the previous %q backend to the newly +configured %q backend: + %s Terraform copies workspaces in alphabetical order. Any workspaces alphabetically earlier than this one have been copied. Any workspaces @@ -472,41 +441,45 @@ This will attempt to copy (with permission) all workspaces again. ` const errBackendStateCopy = ` -Error copying state from %q to %q: %s +Error copying state from the previous %q backend to the newly configured %q backend: + %s -The state in %[1]q remains intact and unmodified. Please resolve the -error above and try again. +The state in the previous backend remains intact and unmodified. Please resolve +the error above and try again. ` const inputBackendMigrateEmpty = ` -Pre-existing state was found in %q while migrating to %q. No existing -state was found in %[2]q. Do you want to copy the state from %[1]q to -%[2]q? Enter "yes" to copy and "no" to start with an empty state. +Pre-existing state was found while migrating the previous %q backend to the +newly configured %q backend. No existing state was found in the newly +configured %[2]q backend. Do you want to copy this state to the new %[2]q +backend? Enter "yes" to copy and "no" to start with an empty state. ` const inputBackendMigrateNonEmpty = ` -Pre-existing state was found in %q while migrating to %q. An existing -non-empty state exists in %[2]q. The two states have been saved to temporary -files that will be removed after responding to this query. +Pre-existing state was found while migrating the previous %q backend to the +newly configured %q backend. An existing non-empty state already exists in +the new backend. The two states have been saved to temporary files that will be +removed after responding to this query. -One (%[1]q): %[3]s -Two (%[2]q): %[4]s +Previous (type %[1]q): %[3]s +New (type %[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. +Do you want to overwrite the state in the new backend with the previous state? +Enter "yes" to copy and "no" to start with the existing state in the newly +configured %[2]q backend. ` const inputBackendMigrateMultiToSingle = ` -The existing backend %[1]q supports workspaces and you currently are -using more than one. The target backend %[2]q doesn't support workspaces. -If you continue, Terraform will offer to copy your current workspace -%[3]q to the default workspace in the target. Your existing workspaces -in the source backend won't be modified. If you want to switch workspaces, -back them up, or cancel altogether, answer "no" and Terraform will abort. +The existing %[1]q backend supports workspaces and you currently are +using more than one. The newly configured %[2]q backend doesn't support +workspaces. If you continue, Terraform will copy your current workspace %[3]q +to the default workspace in the target backend. Your existing workspaces in the +source backend won't be modified. If you want to switch workspaces, back them +up, or cancel altogether, answer "no" and Terraform will abort. ` const inputBackendMigrateMultiToMulti = ` -Both the existing backend %[1]q and the target backend %[2]q support +Both the existing %[1]q backend and the newly configured %[2]q backend support workspaces. When migrating between backends, Terraform will copy all workspaces (with the same names). THIS WILL OVERWRITE any conflicting states in the destination. diff --git a/command/meta_backend_test.go b/command/meta_backend_test.go index f7ece2ccbd..8f059693ce 100644 --- a/command/meta_backend_test.go +++ b/command/meta_backend_test.go @@ -1898,7 +1898,7 @@ func TestMetaBackend_configuredChangedLegacyCopyBackend(t *testing.T) { defer testChdir(t, td)() // Ask input - defer testInteractiveInput(t, []string{"yes", "yes", "no"})() + defer testInteractiveInput(t, []string{"yes", "no"})() // Setup the meta m := testMetaBackend(t, nil) @@ -2297,7 +2297,7 @@ func TestMetaBackend_configuredUnsetWithLegacyCopyBackend(t *testing.T) { defer testChdir(t, td)() // Ask input - defer testInteractiveInput(t, []string{"yes", "yes", "no"})() + defer testInteractiveInput(t, []string{"yes", "no"})() // Setup the meta m := testMetaBackend(t, nil)