Merge pull request #16939 from hashicorp/jbardin/migrate-confirm

remove extra backend migration prompts
This commit is contained in:
James Bardin 2018-01-04 16:27:25 -05:00 committed by GitHub
commit f62b71710a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 172 additions and 254 deletions

View File

@ -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) {

View File

@ -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.

View File

@ -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 = `

View File

@ -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.

View File

@ -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)