noop migrate copy, add -lock and -input
A couple commits got rebased together here, and it's easier to enumerate them in a single commit. Skip copying of states during migration if they are the same state. This can happen when trying to reconfigure a backend's options, or if the state was manually transferred. This can fail unexpectedly with locking enabled. Honor the `-input` flag for all confirmations (the new test hit some more). Also unify where we reference the Meta.forceInitCopy and transfer the value to the existing backendMigrateOpts.force field.
This commit is contained in:
parent
aad143b6d1
commit
fb4a365d12
|
@ -237,7 +237,6 @@ Options:
|
||||||
-force-copy Suppress prompts about copying state data. This is
|
-force-copy Suppress prompts about copying state data. This is
|
||||||
equivalent to providing a "yes" to all confirmation
|
equivalent to providing a "yes" to all confirmation
|
||||||
prompts.
|
prompts.
|
||||||
|
|
||||||
`
|
`
|
||||||
return strings.TrimSpace(helpText)
|
return strings.TrimSpace(helpText)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -53,7 +54,7 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
|
||||||
// Setup defaults
|
// Setup defaults
|
||||||
opts.oneEnv = backend.DefaultStateName
|
opts.oneEnv = backend.DefaultStateName
|
||||||
opts.twoEnv = backend.DefaultStateName
|
opts.twoEnv = backend.DefaultStateName
|
||||||
opts.force = false
|
opts.force = m.forceInitCopy
|
||||||
|
|
||||||
// Determine migration behavior based on whether the source/destionation
|
// Determine migration behavior based on whether the source/destionation
|
||||||
// supports multi-state.
|
// supports multi-state.
|
||||||
|
@ -163,7 +164,7 @@ func (m *Meta) backendMigrateState_S_S(opts *backendMigrateOpts) error {
|
||||||
func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
|
func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
|
||||||
currentEnv := m.Env()
|
currentEnv := m.Env()
|
||||||
|
|
||||||
migrate := m.forceInitCopy
|
migrate := opts.force
|
||||||
if !migrate {
|
if !migrate {
|
||||||
var err error
|
var err error
|
||||||
// Ask the user if they want to migrate their existing remote state
|
// Ask the user if they want to migrate their existing remote state
|
||||||
|
@ -218,6 +219,19 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
|
||||||
errMigrateSingleLoadDefault), opts.TwoType, err)
|
errMigrateSingleLoadDefault), opts.TwoType, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if we need migration at all.
|
||||||
|
// This is before taking a lock, because they may also correspond to the same lock.
|
||||||
|
one := stateOne.State()
|
||||||
|
two := stateTwo.State()
|
||||||
|
|
||||||
|
// no reason to migrate if the state is already there
|
||||||
|
if one.Equal(two) {
|
||||||
|
// Equal isn't identical; it doesn't check lineage.
|
||||||
|
if one != nil && two != nil && one.Lineage == two.Lineage {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if m.stateLock {
|
if m.stateLock {
|
||||||
lockCtx, cancel := context.WithTimeout(context.Background(), m.stateLockTimeout)
|
lockCtx, cancel := context.WithTimeout(context.Background(), m.stateLockTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
@ -241,10 +255,21 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
|
||||||
return fmt.Errorf("Error locking destination state: %s", err)
|
return fmt.Errorf("Error locking destination state: %s", err)
|
||||||
}
|
}
|
||||||
defer clistate.Unlock(stateTwo, lockIDTwo, m.Ui, m.Colorize())
|
defer clistate.Unlock(stateTwo, lockIDTwo, m.Ui, m.Colorize())
|
||||||
|
|
||||||
|
// We now own a lock, so double check that we have the version
|
||||||
|
// corresponding to the lock.
|
||||||
|
if err := stateOne.RefreshState(); err != nil {
|
||||||
|
return fmt.Errorf(strings.TrimSpace(
|
||||||
|
errMigrateSingleLoadDefault), opts.OneType, err)
|
||||||
|
}
|
||||||
|
if err := stateTwo.RefreshState(); err != nil {
|
||||||
|
return fmt.Errorf(strings.TrimSpace(
|
||||||
|
errMigrateSingleLoadDefault), opts.OneType, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
one := stateOne.State()
|
one = stateOne.State()
|
||||||
two := stateTwo.State()
|
two = stateTwo.State()
|
||||||
|
}
|
||||||
|
|
||||||
// Clear the legacy remote state in both cases. If we're at the migration
|
// Clear the legacy remote state in both cases. If we're at the migration
|
||||||
// step then this won't be used anymore.
|
// step then this won't be used anymore.
|
||||||
|
@ -281,6 +306,11 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !opts.force {
|
if !opts.force {
|
||||||
|
// Abort if we can't ask for input.
|
||||||
|
if !m.input {
|
||||||
|
return errors.New("error asking for state migration action: inptut disabled")
|
||||||
|
}
|
||||||
|
|
||||||
// Confirm with the user whether we want to copy state over
|
// Confirm with the user whether we want to copy state over
|
||||||
confirm, err := confirmFunc(stateOne, stateTwo, opts)
|
confirm, err := confirmFunc(stateOne, stateTwo, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -306,10 +336,6 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Meta) backendMigrateEmptyConfirm(one, two state.State, opts *backendMigrateOpts) (bool, error) {
|
func (m *Meta) backendMigrateEmptyConfirm(one, two state.State, opts *backendMigrateOpts) (bool, error) {
|
||||||
if m.forceInitCopy {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
inputOpts := &terraform.InputOpts{
|
inputOpts := &terraform.InputOpts{
|
||||||
Id: "backend-migrate-copy-to-empty",
|
Id: "backend-migrate-copy-to-empty",
|
||||||
Query: fmt.Sprintf(
|
Query: fmt.Sprintf(
|
||||||
|
@ -372,10 +398,6 @@ func (m *Meta) backendMigrateNonEmptyConfirm(
|
||||||
return false, fmt.Errorf("Error saving temporary state: %s", err)
|
return false, fmt.Errorf("Error saving temporary state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.forceInitCopy {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask for confirmation
|
// Ask for confirmation
|
||||||
inputOpts := &terraform.InputOpts{
|
inputOpts := &terraform.InputOpts{
|
||||||
Id: "backend-migrate-to-backend",
|
Id: "backend-migrate-to-backend",
|
||||||
|
|
|
@ -426,6 +426,57 @@ func TestMetaBackend_configureNewWithState(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Newly configured backend with matching local and remote state doesn't prompt
|
||||||
|
// for copy.
|
||||||
|
func TestMetaBackend_configureNewWithoutCopy(t *testing.T) {
|
||||||
|
// Create a temporary working directory that is empty
|
||||||
|
td := tempDir(t)
|
||||||
|
copy.CopyDir(testFixturePath("backend-new-migrate"), td)
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
defer testChdir(t, td)()
|
||||||
|
|
||||||
|
if err := copy.CopyFile(DefaultStateFilename, "local-state.tfstate"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the meta
|
||||||
|
m := testMetaBackend(t, nil)
|
||||||
|
m.input = false
|
||||||
|
|
||||||
|
// init the backend
|
||||||
|
_, err := m.Backend(&BackendOpts{Init: true})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the state is where we expect
|
||||||
|
f, err := os.Open("local-state.tfstate")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
actual, err := terraform.ReadState(f)
|
||||||
|
f.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actual.Lineage != "backend-new-migrate" {
|
||||||
|
t.Fatalf("incorrect state lineage: %q", actual.Lineage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the default paths don't exist
|
||||||
|
if !isEmptyState(DefaultStateFilename) {
|
||||||
|
data, _ := ioutil.ReadFile(DefaultStateFilename)
|
||||||
|
|
||||||
|
t.Fatal("state should not exist, but contains:\n", string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify a backup does exist
|
||||||
|
if isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||||
|
t.Fatal("backup state is empty or missing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Newly configured backend with prior local state and no remote state,
|
// Newly configured backend with prior local state and no remote state,
|
||||||
// but opting to not migrate.
|
// but opting to not migrate.
|
||||||
func TestMetaBackend_configureNewWithStateNoMigrate(t *testing.T) {
|
func TestMetaBackend_configureNewWithStateNoMigrate(t *testing.T) {
|
||||||
|
|
Loading…
Reference in New Issue