command: multistate to multistate conversions
This commit is contained in:
parent
c82d7dd56c
commit
1e3d452613
|
@ -5,6 +5,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
|
@ -51,6 +52,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
|
||||||
|
|
||||||
// Determine migration behavior based on whether the source/destionation
|
// Determine migration behavior based on whether the source/destionation
|
||||||
// supports multi-state.
|
// supports multi-state.
|
||||||
|
@ -85,7 +87,7 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
|
||||||
return m.backendMigrateState_s_s(opts)
|
return m.backendMigrateState_s_s(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
panic("unhandled")
|
return m.backendMigrateState_S_S(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -107,6 +109,55 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
|
||||||
//
|
//
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Multi-state to multi-state.
|
||||||
|
func (m *Meta) backendMigrateState_S_S(opts *backendMigrateOpts) error {
|
||||||
|
// Ask the user if they want to migrate their existing remote state
|
||||||
|
migrate, err := m.confirm(&terraform.InputOpts{
|
||||||
|
Id: "backend-migrate-multistate-to-multistate",
|
||||||
|
Query: fmt.Sprintf(
|
||||||
|
"Do you want to migrate all environments to %q?",
|
||||||
|
opts.TwoType),
|
||||||
|
Description: fmt.Sprintf(
|
||||||
|
strings.TrimSpace(inputBackendMigrateMultiToMulti),
|
||||||
|
opts.OneType, opts.TwoType),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Error asking for state migration action: %s", err)
|
||||||
|
}
|
||||||
|
if !migrate {
|
||||||
|
return fmt.Errorf("Migration aborted by user.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read all the states
|
||||||
|
oneStates, err := opts.One.States()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(strings.TrimSpace(
|
||||||
|
errMigrateLoadStates), opts.OneType, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the states so they're always copied alphabetically
|
||||||
|
sort.Strings(oneStates)
|
||||||
|
|
||||||
|
// Go through each and migrate
|
||||||
|
for _, name := range oneStates {
|
||||||
|
// Copy the same names
|
||||||
|
opts.oneEnv = name
|
||||||
|
opts.twoEnv = name
|
||||||
|
|
||||||
|
// Force it, we confirmed above
|
||||||
|
opts.force = true
|
||||||
|
|
||||||
|
// Perform the migration
|
||||||
|
if err := m.backendMigrateState_s_s(opts); err != nil {
|
||||||
|
return fmt.Errorf(strings.TrimSpace(
|
||||||
|
errMigrateMulti), name, opts.OneType, opts.TwoType, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Multi-state to single state.
|
// Multi-state to single state.
|
||||||
func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
|
func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
|
||||||
currentEnv := m.Env()
|
currentEnv := m.Env()
|
||||||
|
@ -205,13 +256,15 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
|
||||||
panic("confirmFunc must not be nil")
|
panic("confirmFunc must not be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Confirm with the user whether we want to copy state over
|
if !opts.force {
|
||||||
confirm, err := confirmFunc(stateOne, stateTwo, opts)
|
// Confirm with the user whether we want to copy state over
|
||||||
if err != nil {
|
confirm, err := confirmFunc(stateOne, stateTwo, opts)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
if !confirm {
|
}
|
||||||
return nil
|
if !confirm {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Confirmed! Write.
|
// Confirmed! Write.
|
||||||
|
@ -328,6 +381,7 @@ type backendMigrateOpts struct {
|
||||||
|
|
||||||
oneEnv string // source env
|
oneEnv string // source env
|
||||||
twoEnv string // dest env
|
twoEnv string // dest env
|
||||||
|
force bool // if true, won't ask for confirmation
|
||||||
}
|
}
|
||||||
|
|
||||||
const errMigrateLoadStates = `
|
const errMigrateLoadStates = `
|
||||||
|
@ -349,6 +403,20 @@ source and the destination remain unmodified. Please resolve the
|
||||||
above error and try again.
|
above error and try again.
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const errMigrateMulti = `
|
||||||
|
Error migrating the environment %q from %q to %q:
|
||||||
|
|
||||||
|
%s
|
||||||
|
|
||||||
|
Terraform copies environments in alphabetical order. Any environments
|
||||||
|
alphabetically earlier than this one have been copied. Any environments
|
||||||
|
later than this haven't been modified in the destination. No environments
|
||||||
|
in the source state have been modified.
|
||||||
|
|
||||||
|
Please resolve the error above and run the initialization command again.
|
||||||
|
This will attempt to copy (with permission) all environments again.
|
||||||
|
`
|
||||||
|
|
||||||
const errBackendStateCopy = `
|
const errBackendStateCopy = `
|
||||||
Error copying state from %q to %q: %s
|
Error copying state from %q to %q: %s
|
||||||
|
|
||||||
|
@ -382,3 +450,17 @@ If you continue, Terraform will offer to copy your current environment
|
||||||
in the source backend won't be modified. If you want to switch environments,
|
in the source backend won't be modified. If you want to switch environments,
|
||||||
back them up, or cancel altogether, answer "no" and Terraform will abort.
|
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
|
||||||
|
environments. When migrating between backends, Terraform will copy all
|
||||||
|
environments (with the same names). THIS WILL OVERWRITE any conflicting
|
||||||
|
states in the destination.
|
||||||
|
|
||||||
|
Terraform initialization doesn't currently migrate only select environments.
|
||||||
|
If you want to migrate a select number of environments, you must manually
|
||||||
|
pull and push those states.
|
||||||
|
|
||||||
|
If you answer "yes", Terraform will migrate all states. If you answer
|
||||||
|
"no", Terraform will abort.
|
||||||
|
`
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -1193,6 +1195,100 @@ func TestMetaBackend_configuredChangeCopy_multiToSingleCurrentEnv(t *testing.T)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Changing a configured backend that supports multi-state to a
|
||||||
|
// backend that also supports multi-state.
|
||||||
|
func TestMetaBackend_configuredChangeCopy_multiToMulti(t *testing.T) {
|
||||||
|
// Create a temporary working directory that is empty
|
||||||
|
td := tempDir(t)
|
||||||
|
copy.CopyDir(testFixturePath("backend-change-multi-to-multi"), td)
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
defer testChdir(t, td)()
|
||||||
|
|
||||||
|
// Ask input
|
||||||
|
defer testInputMap(t, map[string]string{
|
||||||
|
"backend-migrate-to-new": "yes",
|
||||||
|
"backend-migrate-multistate-to-multistate": "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 resulting states
|
||||||
|
states, err := b.States()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(states)
|
||||||
|
expected := []string{"default", "env2"}
|
||||||
|
if !reflect.DeepEqual(states, expected) {
|
||||||
|
t.Fatalf("bad: %#v", states)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Check the default 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Check the other state
|
||||||
|
s, err := b.State("env2")
|
||||||
|
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-env2" {
|
||||||
|
t.Fatalf("bad: %#v", state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify no local backup
|
||||||
|
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||||
|
t.Fatal("file should not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Verify existing environments exist
|
||||||
|
envPath := filepath.Join(backendlocal.DefaultEnvDir, "env2", backendlocal.DefaultStateFilename)
|
||||||
|
if _, err := os.Stat(envPath); err != nil {
|
||||||
|
t.Fatal("env should exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Verify new environments exist
|
||||||
|
envPath := filepath.Join("envdir-new", "env2", backendlocal.DefaultStateFilename)
|
||||||
|
if _, err := os.Stat(envPath); err != nil {
|
||||||
|
t.Fatal("env should exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Unsetting a saved backend
|
// Unsetting a saved backend
|
||||||
func TestMetaBackend_configuredUnset(t *testing.T) {
|
func TestMetaBackend_configuredUnset(t *testing.T) {
|
||||||
// Create a temporary working directory that is empty
|
// 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",
|
||||||
|
"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" {
|
||||||
|
environment_dir = "envdir-new"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"terraform_version": "0.8.2",
|
||||||
|
"serial": 7,
|
||||||
|
"lineage": "backend-change-env2"
|
||||||
|
}
|
Loading…
Reference in New Issue