Backend State Migration: change variable names from one/two to source/destination (#29699)

This commit is contained in:
Omar Ismail 2021-10-05 16:36:50 -04:00 committed by GitHub
parent 44764ffdee
commit bea7e3ebce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 107 additions and 107 deletions

View File

@ -753,10 +753,10 @@ func (m *Meta) backend_c_r_S(c *configs.Backend, cHash int, sMgr *clistate.Local
// Perform the migration
err := m.backendMigrateState(&backendMigrateOpts{
OneType: s.Backend.Type,
TwoType: "local",
One: b,
Two: localB,
SourceType: s.Backend.Type,
DestinationType: "local",
Source: b,
Destination: localB,
})
if err != nil {
diags = diags.Append(err)
@ -829,10 +829,10 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.Local
if len(localStates) > 0 {
// Perform the migration
err = m.backendMigrateState(&backendMigrateOpts{
OneType: "local",
TwoType: c.Type,
One: localB,
Two: b,
SourceType: "local",
DestinationType: c.Type,
Source: localB,
Destination: b,
})
if err != nil {
diags = diags.Append(err)
@ -944,10 +944,10 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clista
// Perform the migration
err := m.backendMigrateState(&backendMigrateOpts{
OneType: s.Backend.Type,
TwoType: c.Type,
One: oldB,
Two: b,
SourceType: s.Backend.Type,
DestinationType: c.Type,
Source: oldB,
Destination: b,
})
if err != nil {
diags = diags.Append(err)

View File

@ -21,14 +21,14 @@ import (
)
type backendMigrateOpts struct {
OneType, TwoType string
One, Two backend.Backend
SourceType, DestinationType string
Source, Destination backend.Backend
// Fields below are set internally when migrate is called
oneEnv string // source env
twoEnv string // dest env
force bool // if true, won't ask for confirmation
sourceWorkspace string
destinationWorkspace string
force bool // if true, won't ask for confirmation
}
// backendMigrateState handles migrating (copying) state from one backend
@ -43,45 +43,45 @@ type backendMigrateOpts struct {
//
// This will attempt to lock both states for the migration.
func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
log.Printf("[TRACE] backendMigrateState: need to migrate from %q to %q backend config", opts.OneType, opts.TwoType)
log.Printf("[TRACE] backendMigrateState: need to migrate from %q to %q backend config", opts.SourceType, opts.DestinationType)
// We need to check what the named state status is. If we're converting
// from multi-state to single-state for example, we need to handle that.
var oneSingle, twoSingle bool
oneStates, err := opts.One.Workspaces()
var sourceSingleState, destinationSingleState bool
sourceWorkspaces, err := opts.Source.Workspaces()
if err == backend.ErrWorkspacesNotSupported {
oneSingle = true
sourceSingleState = true
err = nil
}
if err != nil {
return fmt.Errorf(strings.TrimSpace(
errMigrateLoadStates), opts.OneType, err)
errMigrateLoadStates), opts.SourceType, err)
}
twoWorkspaces, err := opts.Two.Workspaces()
destinationWorkspaces, err := opts.Destination.Workspaces()
if err == backend.ErrWorkspacesNotSupported {
twoSingle = true
destinationSingleState = true
err = nil
}
if err != nil {
return fmt.Errorf(strings.TrimSpace(
errMigrateLoadStates), opts.TwoType, err)
errMigrateLoadStates), opts.DestinationType, err)
}
// Set up defaults
opts.oneEnv = backend.DefaultStateName
opts.twoEnv = backend.DefaultStateName
opts.sourceWorkspace = backend.DefaultStateName
opts.destinationWorkspace = backend.DefaultStateName
opts.force = m.forceInitCopy
// Disregard remote Terraform version for the state source backend. If it's a
// Terraform Cloud remote backend, we don't care about the remote version,
// as we are migrating away and will not break a remote workspace.
m.ignoreRemoteBackendVersionConflict(opts.One)
m.ignoreRemoteBackendVersionConflict(opts.Source)
for _, twoWorkspace := range twoWorkspaces {
for _, workspace := range destinationWorkspaces {
// Check the remote Terraform version for the state destination backend. If
// it's a Terraform Cloud remote backend, we want to ensure that we don't
// break the workspace by uploading an incompatible state file.
diags := m.remoteBackendVersionCheck(opts.Two, twoWorkspace)
diags := m.remoteBackendVersionCheck(opts.Destination, workspace)
if diags.HasErrors() {
return diags.Err()
}
@ -92,20 +92,20 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
switch {
// Single-state to single-state. This is the easiest case: we just
// copy the default state directly.
case oneSingle && twoSingle:
case sourceSingleState && destinationSingleState:
return m.backendMigrateState_s_s(opts)
// Single-state to multi-state. This is easy since we just copy
// the default state and ignore the rest in the destination.
case oneSingle && !twoSingle:
case sourceSingleState && !destinationSingleState:
return m.backendMigrateState_s_s(opts)
// Multi-state to single-state. If the source has more than the default
// state this is complicated since we have to ask the user what to do.
case !oneSingle && twoSingle:
case !sourceSingleState && destinationSingleState:
// If the source only has one state and it is the default,
// treat it as if it doesn't support multi-state.
if len(oneStates) == 1 && oneStates[0] == backend.DefaultStateName {
if len(sourceWorkspaces) == 1 && sourceWorkspaces[0] == backend.DefaultStateName {
return m.backendMigrateState_s_s(opts)
}
@ -113,10 +113,10 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
// Multi-state to multi-state. We merge the states together (migrating
// each from the source to the destination one by one).
case !oneSingle && !twoSingle:
case !sourceSingleState && !destinationSingleState:
// If the source only has one state and it is the default,
// treat it as if it doesn't support multi-state.
if len(oneStates) == 1 && oneStates[0] == backend.DefaultStateName {
if len(sourceWorkspaces) == 1 && sourceWorkspaces[0] == backend.DefaultStateName {
return m.backendMigrateState_s_s(opts)
}
@ -154,10 +154,10 @@ func (m *Meta) backendMigrateState_S_S(opts *backendMigrateOpts) error {
Id: "backend-migrate-multistate-to-multistate",
Query: fmt.Sprintf(
"Do you want to migrate all workspaces to %q?",
opts.TwoType),
opts.DestinationType),
Description: fmt.Sprintf(
strings.TrimSpace(inputBackendMigrateMultiToMulti),
opts.OneType, opts.TwoType),
opts.SourceType, opts.DestinationType),
})
if err != nil {
return fmt.Errorf(
@ -169,20 +169,20 @@ func (m *Meta) backendMigrateState_S_S(opts *backendMigrateOpts) error {
}
// Read all the states
oneStates, err := opts.One.Workspaces()
sourceWorkspaces, err := opts.Source.Workspaces()
if err != nil {
return fmt.Errorf(strings.TrimSpace(
errMigrateLoadStates), opts.OneType, err)
errMigrateLoadStates), opts.SourceType, err)
}
// Sort the states so they're always copied alphabetically
sort.Strings(oneStates)
sort.Strings(sourceWorkspaces)
// Go through each and migrate
for _, name := range oneStates {
for _, name := range sourceWorkspaces {
// Copy the same names
opts.oneEnv = name
opts.twoEnv = name
opts.sourceWorkspace = name
opts.destinationWorkspace = name
// Force it, we confirmed above
opts.force = true
@ -190,7 +190,7 @@ func (m *Meta) backendMigrateState_S_S(opts *backendMigrateOpts) error {
// Perform the migration
if err := m.backendMigrateState_s_s(opts); err != nil {
return fmt.Errorf(strings.TrimSpace(
errMigrateMulti), name, opts.OneType, opts.TwoType, err)
errMigrateMulti), name, opts.SourceType, opts.DestinationType, err)
}
}
@ -199,7 +199,7 @@ func (m *Meta) backendMigrateState_S_S(opts *backendMigrateOpts) error {
// Multi-state to single state.
func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
log.Printf("[TRACE] backendMigrateState: target backend type %q does not support named workspaces", opts.TwoType)
log.Printf("[TRACE] backendMigrateState: destination backend type %q does not support named workspaces", opts.DestinationType)
currentEnv, err := m.Workspace()
if err != nil {
@ -215,10 +215,10 @@ func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
Query: fmt.Sprintf(
"Destination state %q doesn't support workspaces.\n"+
"Do you want to copy only your current workspace?",
opts.TwoType),
opts.DestinationType),
Description: fmt.Sprintf(
strings.TrimSpace(inputBackendMigrateMultiToSingle),
opts.OneType, opts.TwoType, currentEnv),
opts.SourceType, opts.DestinationType, currentEnv),
})
if err != nil {
return fmt.Errorf(
@ -231,7 +231,7 @@ func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
}
// Copy the default state
opts.oneEnv = currentEnv
opts.sourceWorkspace = currentEnv
// now switch back to the default env so we can acccess the new backend
m.SetWorkspace(backend.DefaultStateName)
@ -241,46 +241,46 @@ func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
// Single state to single state, assumed default state name.
func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
log.Printf("[TRACE] backendMigrateState: migrating %q workspace to %q workspace", opts.oneEnv, opts.twoEnv)
log.Printf("[TRACE] backendMigrateState: migrating %q workspace to %q workspace", opts.sourceWorkspace, opts.destinationWorkspace)
stateOne, err := opts.One.StateMgr(opts.oneEnv)
sourceState, err := opts.Source.StateMgr(opts.sourceWorkspace)
if err != nil {
return fmt.Errorf(strings.TrimSpace(
errMigrateSingleLoadDefault), opts.OneType, err)
errMigrateSingleLoadDefault), opts.SourceType, err)
}
if err := stateOne.RefreshState(); err != nil {
if err := sourceState.RefreshState(); err != nil {
return fmt.Errorf(strings.TrimSpace(
errMigrateSingleLoadDefault), opts.OneType, err)
errMigrateSingleLoadDefault), opts.SourceType, err)
}
// Do not migrate workspaces without state.
if stateOne.State().Empty() {
if sourceState.State().Empty() {
log.Print("[TRACE] backendMigrateState: source workspace has empty state, so nothing to migrate")
return nil
}
stateTwo, err := opts.Two.StateMgr(opts.twoEnv)
destinationState, err := opts.Destination.StateMgr(opts.destinationWorkspace)
if err == backend.ErrDefaultWorkspaceNotSupported {
// If the backend doesn't support using the default state, we ask the user
// for a new name and migrate the default state to the given named state.
stateTwo, err = func() (statemgr.Full, error) {
log.Print("[TRACE] backendMigrateState: target doesn't support a default workspace, so we must prompt for a new name")
destinationState, err = func() (statemgr.Full, error) {
log.Print("[TRACE] backendMigrateState: destination doesn't support a default workspace, so we must prompt for a new name")
name, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{
Id: "new-state-name",
Query: fmt.Sprintf(
"[reset][bold][yellow]The %q backend configuration only allows "+
"named workspaces![reset]",
opts.TwoType),
opts.DestinationType),
Description: strings.TrimSpace(inputBackendNewWorkspaceName),
})
if err != nil {
return nil, fmt.Errorf("Error asking for new state name: %s", err)
}
// Update the name of the target state.
opts.twoEnv = name
// Update the name of the destination state.
opts.destinationWorkspace = name
stateTwo, err := opts.Two.StateMgr(opts.twoEnv)
destinationState, err := opts.Destination.StateMgr(opts.destinationWorkspace)
if err != nil {
return nil, err
}
@ -291,34 +291,34 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
// If the currently selected workspace is the default workspace, then set
// the named workspace as the new selected workspace.
if workspace == backend.DefaultStateName {
if err := m.SetWorkspace(opts.twoEnv); err != nil {
if err := m.SetWorkspace(opts.destinationWorkspace); err != nil {
return nil, fmt.Errorf("Failed to set new workspace: %s", err)
}
}
return stateTwo, nil
return destinationState, nil
}()
}
if err != nil {
return fmt.Errorf(strings.TrimSpace(
errMigrateSingleLoadDefault), opts.TwoType, err)
errMigrateSingleLoadDefault), opts.DestinationType, err)
}
if err := stateTwo.RefreshState(); err != nil {
if err := destinationState.RefreshState(); err != nil {
return fmt.Errorf(strings.TrimSpace(
errMigrateSingleLoadDefault), opts.TwoType, err)
errMigrateSingleLoadDefault), opts.DestinationType, 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()
source := sourceState.State()
destination := destinationState.State()
// no reason to migrate if the state is already there
if one.Equal(two) {
if source.Equal(destination) {
// Equal isn't identical; it doesn't check lineage.
sm1, _ := stateOne.(statemgr.PersistentMeta)
sm2, _ := stateTwo.(statemgr.PersistentMeta)
if one != nil && two != nil {
sm1, _ := sourceState.(statemgr.PersistentMeta)
sm2, _ := destinationState.(statemgr.PersistentMeta)
if source != nil && destination != nil {
if sm1 == nil || sm2 == nil {
log.Print("[TRACE] backendMigrateState: both source and destination workspaces have no state, so no migration is needed")
return nil
@ -336,56 +336,56 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
view := views.NewStateLocker(arguments.ViewHuman, m.View)
locker := clistate.NewLocker(m.stateLockTimeout, view)
lockerOne := locker.WithContext(lockCtx)
if diags := lockerOne.Lock(stateOne, "migration source state"); diags.HasErrors() {
lockerSource := locker.WithContext(lockCtx)
if diags := lockerSource.Lock(sourceState, "migration source state"); diags.HasErrors() {
return diags.Err()
}
defer lockerOne.Unlock()
defer lockerSource.Unlock()
lockerTwo := locker.WithContext(lockCtx)
if diags := lockerTwo.Lock(stateTwo, "migration destination state"); diags.HasErrors() {
lockerDestination := locker.WithContext(lockCtx)
if diags := lockerDestination.Lock(destinationState, "migration destination state"); diags.HasErrors() {
return diags.Err()
}
defer lockerTwo.Unlock()
defer lockerDestination.Unlock()
// We now own a lock, so double check that we have the version
// corresponding to the lock.
log.Print("[TRACE] backendMigrateState: refreshing source workspace state")
if err := stateOne.RefreshState(); err != nil {
if err := sourceState.RefreshState(); err != nil {
return fmt.Errorf(strings.TrimSpace(
errMigrateSingleLoadDefault), opts.OneType, err)
errMigrateSingleLoadDefault), opts.SourceType, err)
}
log.Print("[TRACE] backendMigrateState: refreshing target workspace state")
if err := stateTwo.RefreshState(); err != nil {
log.Print("[TRACE] backendMigrateState: refreshing destination workspace state")
if err := destinationState.RefreshState(); err != nil {
return fmt.Errorf(strings.TrimSpace(
errMigrateSingleLoadDefault), opts.OneType, err)
errMigrateSingleLoadDefault), opts.SourceType, err)
}
one = stateOne.State()
two = stateTwo.State()
source = sourceState.State()
destination = destinationState.State()
}
var confirmFunc func(statemgr.Full, statemgr.Full, *backendMigrateOpts) (bool, error)
switch {
// No migration necessary
case one.Empty() && two.Empty():
case source.Empty() && destination.Empty():
log.Print("[TRACE] backendMigrateState: both source and destination workspaces have empty state, so no migration is required")
return nil
// No migration necessary if we're inheriting state.
case one.Empty() && !two.Empty():
case source.Empty() && !destination.Empty():
log.Print("[TRACE] backendMigrateState: source workspace has empty state, so no migration is required")
return nil
// We have existing state moving into no state. Ask the user if
// they'd like to do this.
case !one.Empty() && two.Empty():
log.Print("[TRACE] backendMigrateState: target workspace has empty state, so might copy source workspace state")
case !source.Empty() && destination.Empty():
log.Print("[TRACE] backendMigrateState: destination workspace has empty state, so might copy source workspace state")
confirmFunc = m.backendMigrateEmptyConfirm
// Both states are non-empty, meaning we need to determine which
// state should be used and update accordingly.
case !one.Empty() && !two.Empty():
case !source.Empty() && !destination.Empty():
log.Print("[TRACE] backendMigrateState: both source and destination workspaces have states, so might overwrite destination with source")
confirmFunc = m.backendMigrateNonEmptyConfirm
}
@ -402,7 +402,7 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
}
// Confirm with the user whether we want to copy state over
confirm, err := confirmFunc(stateOne, stateTwo, opts)
confirm, err := confirmFunc(sourceState, destinationState, opts)
if err != nil {
log.Print("[TRACE] backendMigrateState: error reading input, so aborting migration")
return err
@ -417,36 +417,36 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
// includes preserving any lineage/serial information where possible, if
// both managers support such metadata.
log.Print("[TRACE] backendMigrateState: migration confirmed, so migrating")
if err := statemgr.Migrate(stateTwo, stateOne); err != nil {
if err := statemgr.Migrate(destinationState, sourceState); err != nil {
return fmt.Errorf(strings.TrimSpace(errBackendStateCopy),
opts.OneType, opts.TwoType, err)
opts.SourceType, opts.DestinationType, err)
}
if err := stateTwo.PersistState(); err != nil {
if err := destinationState.PersistState(); err != nil {
return fmt.Errorf(strings.TrimSpace(errBackendStateCopy),
opts.OneType, opts.TwoType, err)
opts.SourceType, opts.DestinationType, err)
}
// And we're done.
return nil
}
func (m *Meta) backendMigrateEmptyConfirm(one, two statemgr.Full, opts *backendMigrateOpts) (bool, error) {
func (m *Meta) backendMigrateEmptyConfirm(source, destination statemgr.Full, opts *backendMigrateOpts) (bool, error) {
inputOpts := &terraform.InputOpts{
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),
opts.SourceType, opts.DestinationType),
}
return m.confirm(inputOpts)
}
func (m *Meta) backendMigrateNonEmptyConfirm(
stateOne, stateTwo statemgr.Full, opts *backendMigrateOpts) (bool, error) {
sourceState, destinationState statemgr.Full, opts *backendMigrateOpts) (bool, error) {
// We need to grab both states so we can write them to a file
one := stateOne.State()
two := stateTwo.State()
source := sourceState.State()
destination := destinationState.State()
// Save both to a temporary
td, err := ioutil.TempDir("", "terraform")
@ -462,12 +462,12 @@ func (m *Meta) backendMigrateNonEmptyConfirm(
}
// Write the states
onePath := filepath.Join(td, fmt.Sprintf("1-%s.tfstate", opts.OneType))
twoPath := filepath.Join(td, fmt.Sprintf("2-%s.tfstate", opts.TwoType))
if err := saveHelper(opts.OneType, onePath, one); err != nil {
sourcePath := filepath.Join(td, fmt.Sprintf("1-%s.tfstate", opts.SourceType))
destinationPath := filepath.Join(td, fmt.Sprintf("2-%s.tfstate", opts.DestinationType))
if err := saveHelper(opts.SourceType, sourcePath, source); err != nil {
return false, fmt.Errorf("Error saving temporary state: %s", err)
}
if err := saveHelper(opts.TwoType, twoPath, two); err != nil {
if err := saveHelper(opts.DestinationType, destinationPath, destination); err != nil {
return false, fmt.Errorf("Error saving temporary state: %s", err)
}
@ -477,7 +477,7 @@ func (m *Meta) backendMigrateNonEmptyConfirm(
Query: "Do you want to copy existing state to the new backend?",
Description: fmt.Sprintf(
strings.TrimSpace(inputBackendMigrateNonEmpty),
opts.OneType, opts.TwoType, onePath, twoPath),
opts.SourceType, opts.DestinationType, sourcePath, destinationPath),
}
// Confirm with the user that the copy should occur
@ -553,7 +553,7 @@ const inputBackendMigrateMultiToSingle = `
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
to the default workspace in the new 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.
`