command: initial work on migrating envs, basic cases first
This commit is contained in:
parent
177400dbbf
commit
1d8b76c89d
|
@ -13,9 +13,13 @@ import (
|
|||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
// This is the name of the default, initial state that every backend
|
||||
// must have. This state cannot be deleted.
|
||||
const DefaultStateName = "default"
|
||||
|
||||
// Error value to return when a named state operation isn't supported
|
||||
// Error value to return when a named state operation isn't supported.
|
||||
// This must be returned rather than a custom error so that the Terraform
|
||||
// CLI can detect it and handle it appropriately.
|
||||
var ErrNamedStatesNotSupported = errors.New("named states not supported")
|
||||
|
||||
// Backend is the minimal interface that must be implemented to enable Terraform.
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
|
@ -56,6 +58,38 @@ func TestLocalProvider(t *testing.T, b *Local, name string) *terraform.MockResou
|
|||
return p
|
||||
}
|
||||
|
||||
// TestNewLocalSingle is a factory for creating a TestLocalSingleState.
|
||||
// This function matches the signature required for backend/init.
|
||||
func TestNewLocalSingle() backend.Backend {
|
||||
return &TestLocalSingleState{}
|
||||
}
|
||||
|
||||
// TestLocalSingleState is a backend implementation that wraps Local
|
||||
// and modifies it to only support single states (returns
|
||||
// ErrNamedStatesNotSupported for multi-state operations).
|
||||
//
|
||||
// This isn't an actual use case, this is exported just to provide a
|
||||
// easy way to test that behavior.
|
||||
type TestLocalSingleState struct {
|
||||
Local
|
||||
}
|
||||
|
||||
func (b *TestLocalSingleState) State(name string) (state.State, error) {
|
||||
if name != backend.DefaultStateName {
|
||||
return nil, backend.ErrNamedStatesNotSupported
|
||||
}
|
||||
|
||||
return b.Local.State(name)
|
||||
}
|
||||
|
||||
func (b *TestLocalSingleState) States() ([]string, error) {
|
||||
return nil, backend.ErrNamedStatesNotSupported
|
||||
}
|
||||
|
||||
func (b *TestLocalSingleState) DeleteState(string) error {
|
||||
return backend.ErrNamedStatesNotSupported
|
||||
}
|
||||
|
||||
func testTempDir(t *testing.T) string {
|
||||
d, err := ioutil.TempDir("", "tf")
|
||||
if err != nil {
|
||||
|
|
|
@ -449,6 +449,28 @@ func testInteractiveInput(t *testing.T, answers []string) func() {
|
|||
}
|
||||
}
|
||||
|
||||
// testInputMap configures tests so that the given answers are returned
|
||||
// for calls to Input when the right question is asked. The key is the
|
||||
// question "Id" that is used.
|
||||
func testInputMap(t *testing.T, answers map[string]string) func() {
|
||||
// Disable test mode so input is called
|
||||
test = false
|
||||
|
||||
// Setup reader/writers
|
||||
defaultInputReader = bytes.NewBufferString("")
|
||||
defaultInputWriter = new(bytes.Buffer)
|
||||
|
||||
// Setup answers
|
||||
testInputResponse = nil
|
||||
testInputResponseMap = answers
|
||||
|
||||
// Return the cleanup
|
||||
return func() {
|
||||
test = true
|
||||
testInputResponseMap = nil
|
||||
}
|
||||
}
|
||||
|
||||
// testBackendState is used to make a test HTTP server to test a configured
|
||||
// backend. This returns the complete state that can be saved. Use
|
||||
// `testStateFileRemote` to write the returned state.
|
||||
|
|
|
@ -646,38 +646,19 @@ func (m *Meta) backend_c_r_S(
|
|||
return nil, fmt.Errorf(strings.TrimSpace(errBackendLocalRead), err)
|
||||
}
|
||||
|
||||
env := m.Env()
|
||||
|
||||
localState, err := localB.State(env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(strings.TrimSpace(errBackendLocalRead), err)
|
||||
}
|
||||
if err := localState.RefreshState(); 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)
|
||||
}
|
||||
backendState, err := b.State(env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
strings.TrimSpace(errBackendSavedUnsetConfig), s.Backend.Type, err)
|
||||
}
|
||||
if err := backendState.RefreshState(); 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: backendState,
|
||||
Two: localState,
|
||||
One: b,
|
||||
Two: localB,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -758,16 +739,6 @@ func (m *Meta) backend_c_R_S(
|
|||
return nil, fmt.Errorf(errBackendLocalRead, err)
|
||||
}
|
||||
|
||||
env := m.Env()
|
||||
|
||||
localState, err := localB.State(env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errBackendLocalRead, err)
|
||||
}
|
||||
if err := localState.RefreshState(); err != nil {
|
||||
return nil, fmt.Errorf(errBackendLocalRead, err)
|
||||
}
|
||||
|
||||
// Grab the state
|
||||
s := sMgr.State()
|
||||
|
||||
|
@ -791,22 +762,13 @@ func (m *Meta) backend_c_R_S(
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oldState, err := oldB.State(env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
strings.TrimSpace(errBackendSavedUnsetConfig), s.Backend.Type, err)
|
||||
}
|
||||
if err := oldState.RefreshState(); err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
strings.TrimSpace(errBackendSavedUnsetConfig), s.Backend.Type, err)
|
||||
}
|
||||
|
||||
// Perform the migration
|
||||
err = m.backendMigrateState(&backendMigrateOpts{
|
||||
OneType: s.Remote.Type,
|
||||
TwoType: "local",
|
||||
One: oldState,
|
||||
Two: localState,
|
||||
One: oldB,
|
||||
Two: localB,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -894,33 +856,12 @@ func (m *Meta) backend_C_R_s(
|
|||
return nil, err
|
||||
}
|
||||
|
||||
env := m.Env()
|
||||
|
||||
oldState, err := oldB.State(env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
strings.TrimSpace(errBackendSavedUnsetConfig), s.Backend.Type, err)
|
||||
}
|
||||
if err := oldState.RefreshState(); err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
strings.TrimSpace(errBackendSavedUnsetConfig), s.Backend.Type, err)
|
||||
}
|
||||
|
||||
// Get the new state
|
||||
newState, err := b.State(env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(strings.TrimSpace(errBackendNewRead), err)
|
||||
}
|
||||
if err := newState.RefreshState(); err != nil {
|
||||
return nil, fmt.Errorf(strings.TrimSpace(errBackendNewRead), err)
|
||||
}
|
||||
|
||||
// Perform the migration
|
||||
err = m.backendMigrateState(&backendMigrateOpts{
|
||||
OneType: s.Remote.Type,
|
||||
TwoType: c.Type,
|
||||
One: oldState,
|
||||
Two: newState,
|
||||
One: oldB,
|
||||
Two: b,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -975,20 +916,12 @@ func (m *Meta) backend_C_r_s(
|
|||
// If the local state is not empty, we need to potentially do a
|
||||
// state migration to the new backend (with user permission).
|
||||
if localS := localState.State(); !localS.Empty() {
|
||||
backendState, err := b.State(env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errBackendRemoteRead, err)
|
||||
}
|
||||
if err := backendState.RefreshState(); err != nil {
|
||||
return nil, fmt.Errorf(errBackendRemoteRead, err)
|
||||
}
|
||||
|
||||
// Perform the migration
|
||||
err = m.backendMigrateState(&backendMigrateOpts{
|
||||
OneType: "local",
|
||||
TwoType: c.Type,
|
||||
One: localState,
|
||||
Two: backendState,
|
||||
One: localB,
|
||||
Two: b,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -1080,33 +1013,12 @@ func (m *Meta) backend_C_r_S_changed(
|
|||
"Error loading previously configured backend: %s", err)
|
||||
}
|
||||
|
||||
env := m.Env()
|
||||
|
||||
oldState, err := oldB.State(env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
strings.TrimSpace(errBackendSavedUnsetConfig), s.Backend.Type, err)
|
||||
}
|
||||
if err := oldState.RefreshState(); err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
strings.TrimSpace(errBackendSavedUnsetConfig), s.Backend.Type, err)
|
||||
}
|
||||
|
||||
// Get the new state
|
||||
newState, err := b.State(env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(strings.TrimSpace(errBackendNewRead), err)
|
||||
}
|
||||
if err := newState.RefreshState(); err != nil {
|
||||
return nil, fmt.Errorf(strings.TrimSpace(errBackendNewRead), err)
|
||||
}
|
||||
|
||||
// Perform the migration
|
||||
err = m.backendMigrateState(&backendMigrateOpts{
|
||||
OneType: s.Backend.Type,
|
||||
TwoType: c.Type,
|
||||
One: oldState,
|
||||
Two: newState,
|
||||
One: oldB,
|
||||
Two: b,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -1244,33 +1156,12 @@ func (m *Meta) backend_C_R_S_unchanged(
|
|||
return nil, err
|
||||
}
|
||||
|
||||
env := m.Env()
|
||||
|
||||
oldState, err := oldB.State(env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
strings.TrimSpace(errBackendSavedUnsetConfig), s.Remote.Type, err)
|
||||
}
|
||||
if err := oldState.RefreshState(); err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
strings.TrimSpace(errBackendSavedUnsetConfig), s.Remote.Type, err)
|
||||
}
|
||||
|
||||
// Get the new state
|
||||
newState, err := b.State(env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(strings.TrimSpace(errBackendNewRead), err)
|
||||
}
|
||||
if err := newState.RefreshState(); err != nil {
|
||||
return nil, fmt.Errorf(strings.TrimSpace(errBackendNewRead), err)
|
||||
}
|
||||
|
||||
// Perform the migration
|
||||
err = m.backendMigrateState(&backendMigrateOpts{
|
||||
OneType: s.Remote.Type,
|
||||
TwoType: s.Backend.Type,
|
||||
One: oldState,
|
||||
Two: newState,
|
||||
One: oldB,
|
||||
Two: b,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
clistate "github.com/hashicorp/terraform/command/state"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
@ -24,30 +25,131 @@ import (
|
|||
//
|
||||
// This will attempt to lock both states for the migration.
|
||||
func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
|
||||
// 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.States()
|
||||
if err == backend.ErrNamedStatesNotSupported {
|
||||
oneSingle = true
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf(strings.TrimSpace(
|
||||
errMigrateLoadStates), opts.OneType, err)
|
||||
}
|
||||
|
||||
_, err = opts.Two.States()
|
||||
if err == backend.ErrNamedStatesNotSupported {
|
||||
twoSingle = true
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf(strings.TrimSpace(
|
||||
errMigrateLoadStates), opts.TwoType, err)
|
||||
}
|
||||
|
||||
// Determine migration behavior based on whether the source/destionation
|
||||
// supports multi-state.
|
||||
switch {
|
||||
// Single-state to single-state. This is the easiest case: we just
|
||||
// copy the default state directly.
|
||||
case oneSingle && twoSingle:
|
||||
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:
|
||||
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:
|
||||
// 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 {
|
||||
panic("YO")
|
||||
return m.backendMigrateState_s_s(opts)
|
||||
}
|
||||
|
||||
panic("unhandled")
|
||||
|
||||
// Multi-state to multi-state. We merge the states together (migrating
|
||||
// each from the source to the destination one by one).
|
||||
case !oneSingle && !twoSingle:
|
||||
// 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 {
|
||||
return m.backendMigrateState_s_s(opts)
|
||||
}
|
||||
|
||||
panic("unhandled")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// State Migration Scenarios
|
||||
//
|
||||
// The functions below cover handling all the various scenarios that
|
||||
// can exist when migrating state. They are named in an immediately not
|
||||
// obvious format but is simple:
|
||||
//
|
||||
// Format: backendMigrateState_s1_s2[_suffix]
|
||||
//
|
||||
// When s1 or s2 is lower case, it means that it is a single state backend.
|
||||
// When either is uppercase, it means that state is a multi-state backend.
|
||||
// The suffix is used to disambiguate multiple cases with the same type of
|
||||
// states.
|
||||
//
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
// Single state to single state, assumed default state name.
|
||||
func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
|
||||
stateOne, err := opts.One.State(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
return fmt.Errorf(strings.TrimSpace(
|
||||
errMigrateSingleLoadDefault), opts.OneType, err)
|
||||
}
|
||||
if err := stateOne.RefreshState(); err != nil {
|
||||
return fmt.Errorf(strings.TrimSpace(
|
||||
errMigrateSingleLoadDefault), opts.OneType, err)
|
||||
}
|
||||
|
||||
stateTwo, err := opts.Two.State(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
return fmt.Errorf(strings.TrimSpace(
|
||||
errMigrateSingleLoadDefault), opts.TwoType, err)
|
||||
}
|
||||
if err := stateTwo.RefreshState(); err != nil {
|
||||
return fmt.Errorf(strings.TrimSpace(
|
||||
errMigrateSingleLoadDefault), opts.TwoType, err)
|
||||
}
|
||||
|
||||
lockInfoOne := state.NewLockInfo()
|
||||
lockInfoOne.Operation = "migration"
|
||||
lockInfoOne.Info = "source state"
|
||||
|
||||
lockIDOne, err := clistate.Lock(opts.One, lockInfoOne, m.Ui, m.Colorize())
|
||||
lockIDOne, err := clistate.Lock(stateOne, lockInfoOne, m.Ui, m.Colorize())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error locking source state: %s", err)
|
||||
}
|
||||
defer clistate.Unlock(opts.One, lockIDOne, m.Ui, m.Colorize())
|
||||
defer clistate.Unlock(stateOne, lockIDOne, m.Ui, m.Colorize())
|
||||
|
||||
lockInfoTwo := state.NewLockInfo()
|
||||
lockInfoTwo.Operation = "migration"
|
||||
lockInfoTwo.Info = "destination state"
|
||||
|
||||
lockIDTwo, err := clistate.Lock(opts.Two, lockInfoTwo, m.Ui, m.Colorize())
|
||||
lockIDTwo, err := clistate.Lock(stateTwo, lockInfoTwo, m.Ui, m.Colorize())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error locking destination state: %s", err)
|
||||
}
|
||||
defer clistate.Unlock(opts.Two, lockIDTwo, m.Ui, m.Colorize())
|
||||
defer clistate.Unlock(stateTwo, lockIDTwo, m.Ui, m.Colorize())
|
||||
|
||||
one := opts.One.State()
|
||||
two := opts.Two.State()
|
||||
one := stateOne.State()
|
||||
two := stateTwo.State()
|
||||
|
||||
var confirmFunc func(opts *backendMigrateOpts) (bool, error)
|
||||
var confirmFunc func(state.State, state.State, *backendMigrateOpts) (bool, error)
|
||||
switch {
|
||||
// No migration necessary
|
||||
case one.Empty() && two.Empty():
|
||||
|
@ -73,7 +175,7 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
|
|||
}
|
||||
|
||||
// Confirm with the user whether we want to copy state over
|
||||
confirm, err := confirmFunc(opts)
|
||||
confirm, err := confirmFunc(stateOne, stateTwo, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -82,11 +184,11 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
|
|||
}
|
||||
|
||||
// Confirmed! Write.
|
||||
if err := opts.Two.WriteState(one); err != nil {
|
||||
if err := stateTwo.WriteState(one); err != nil {
|
||||
return fmt.Errorf(strings.TrimSpace(errBackendStateCopy),
|
||||
opts.OneType, opts.TwoType, err)
|
||||
}
|
||||
if err := opts.Two.PersistState(); err != nil {
|
||||
if err := stateTwo.PersistState(); err != nil {
|
||||
return fmt.Errorf(strings.TrimSpace(errBackendStateCopy),
|
||||
opts.OneType, opts.TwoType, err)
|
||||
}
|
||||
|
@ -95,9 +197,9 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *Meta) backendMigrateEmptyConfirm(opts *backendMigrateOpts) (bool, error) {
|
||||
func (m *Meta) backendMigrateEmptyConfirm(one, two state.State, opts *backendMigrateOpts) (bool, error) {
|
||||
inputOpts := &terraform.InputOpts{
|
||||
Id: "backend-migrate-to-backend",
|
||||
Id: "backend-migrate-copy-to-empty",
|
||||
Query: fmt.Sprintf(
|
||||
"Do you want to copy state from %q to %q?",
|
||||
opts.OneType, opts.TwoType),
|
||||
|
@ -124,10 +226,11 @@ func (m *Meta) backendMigrateEmptyConfirm(opts *backendMigrateOpts) (bool, error
|
|||
}
|
||||
}
|
||||
|
||||
func (m *Meta) backendMigrateNonEmptyConfirm(opts *backendMigrateOpts) (bool, error) {
|
||||
func (m *Meta) backendMigrateNonEmptyConfirm(
|
||||
stateOne, stateTwo state.State, opts *backendMigrateOpts) (bool, error) {
|
||||
// We need to grab both states so we can write them to a file
|
||||
one := opts.One.State()
|
||||
two := opts.Two.State()
|
||||
one := stateOne.State()
|
||||
two := stateTwo.State()
|
||||
|
||||
// Save both to a temporary
|
||||
td, err := ioutil.TempDir("", "terraform")
|
||||
|
@ -188,9 +291,28 @@ func (m *Meta) backendMigrateNonEmptyConfirm(opts *backendMigrateOpts) (bool, er
|
|||
|
||||
type backendMigrateOpts struct {
|
||||
OneType, TwoType string
|
||||
One, Two state.State
|
||||
One, Two backend.Backend
|
||||
}
|
||||
|
||||
const errMigrateLoadStates = `
|
||||
Error inspecting state in %q: %s
|
||||
|
||||
Prior to changing backends, Terraform inspects the source and destionation
|
||||
states to determine what kind of migration steps need to be taken, if any.
|
||||
Terraform failed to load the states. The data in both the source and the
|
||||
destination remain unmodified. Please resolve the above error and try again.
|
||||
`
|
||||
|
||||
const errMigrateSingleLoadDefault = `
|
||||
Error loading state from %q: %s
|
||||
|
||||
Terraform failed to load the default state from %[1]q.
|
||||
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
|
||||
above error and try again.
|
||||
`
|
||||
|
||||
const errBackendStateCopy = `
|
||||
Error copying state from %q to %q: %s
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
backendinit "github.com/hashicorp/terraform/backend/init"
|
||||
backendlocal "github.com/hashicorp/terraform/backend/local"
|
||||
"github.com/hashicorp/terraform/helper/copy"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
@ -951,6 +953,61 @@ func TestMetaBackend_configuredChangeCopy(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// Changing a configured backend that supports only single states to another
|
||||
// backend that only supports single states.
|
||||
func TestMetaBackend_configuredChangeCopy_singleState(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
td := tempDir(t)
|
||||
copy.CopyDir(testFixturePath("backend-change-single-to-single"), td)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
// Register the single-state backend
|
||||
backendinit.Set("local-single", backendlocal.TestNewLocalSingle)
|
||||
defer backendinit.Set("local-single", nil)
|
||||
|
||||
// Ask input
|
||||
defer testInputMap(t, map[string]string{
|
||||
"backend-migrate-to-new": "yes",
|
||||
"backend-migrate-copy-to-empty": "yes",
|
||||
})()
|
||||
|
||||
// Setup the meta
|
||||
m := testMetaBackend(t, nil)
|
||||
|
||||
// Get the backend
|
||||
b, err := m.Backend(&BackendOpts{Init: true})
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
|
||||
// Check the state
|
||||
s, err := b.State(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
if err := s.RefreshState(); err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
state := s.State()
|
||||
if state == nil {
|
||||
t.Fatal("state should not be nil")
|
||||
}
|
||||
if state.Lineage != "backend-change" {
|
||||
t.Fatalf("bad: %#v", state)
|
||||
}
|
||||
|
||||
// Verify no local state
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify no local backup
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
// Unsetting a saved backend
|
||||
func TestMetaBackend_configuredUnset(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"version": 3,
|
||||
"serial": 0,
|
||||
"lineage": "666f9301-7e65-4b19-ae23-71184bb19b03",
|
||||
"backend": {
|
||||
"type": "local-single",
|
||||
"config": {
|
||||
"path": "local-state.tfstate"
|
||||
},
|
||||
"hash": 9073424445967744180
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"path": [
|
||||
"root"
|
||||
],
|
||||
"outputs": {},
|
||||
"resources": {},
|
||||
"depends_on": []
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"version": 3,
|
||||
"terraform_version": "0.8.2",
|
||||
"serial": 7,
|
||||
"lineage": "backend-change"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
terraform {
|
||||
backend "local-single" {
|
||||
path = "local-state-2.tfstate"
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ import (
|
|||
var defaultInputReader io.Reader
|
||||
var defaultInputWriter io.Writer
|
||||
var testInputResponse []string
|
||||
var testInputResponseMap map[string]string
|
||||
|
||||
// UIInput is an implementation of terraform.UIInput that asks the CLI
|
||||
// for input stdin.
|
||||
|
@ -65,13 +66,25 @@ func (i *UIInput) Input(opts *terraform.InputOpts) (string, error) {
|
|||
return "", errors.New("interrupted")
|
||||
}
|
||||
|
||||
// If we have test results, return those
|
||||
// If we have test results, return those. testInputResponse is the
|
||||
// "old" way of doing it and we should remove that.
|
||||
if testInputResponse != nil {
|
||||
v := testInputResponse[0]
|
||||
testInputResponse = testInputResponse[1:]
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// testInputResponseMap is the new way for test responses, based on
|
||||
// the query ID.
|
||||
if testInputResponseMap != nil {
|
||||
v, ok := testInputResponseMap[opts.Id]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unexpected input request in test: %s", opts.Id)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] command: asking for input: %q", opts.Query)
|
||||
|
||||
// Listen for interrupts so we can cancel the input ask
|
||||
|
|
Loading…
Reference in New Issue