github.com/simonswine/terraform@v0.9.0-beta2/command/meta_backend_migrate.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/hashicorp/terraform/backend"
    12  	clistate "github.com/hashicorp/terraform/command/state"
    13  	"github.com/hashicorp/terraform/state"
    14  	"github.com/hashicorp/terraform/terraform"
    15  )
    16  
    17  // backendMigrateState handles migrating (copying) state from one backend
    18  // to another. This function handles asking the user for confirmation
    19  // as well as the copy itself.
    20  //
    21  // This function can handle all scenarios of state migration regardless
    22  // of the existence of state in either backend.
    23  //
    24  // After migrating the state, the existing state in the first backend
    25  // remains untouched.
    26  //
    27  // This will attempt to lock both states for the migration.
    28  func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
    29  	// We need to check what the named state status is. If we're converting
    30  	// from multi-state to single-state for example, we need to handle that.
    31  	var oneSingle, twoSingle bool
    32  	oneStates, err := opts.One.States()
    33  	if err == backend.ErrNamedStatesNotSupported {
    34  		oneSingle = true
    35  		err = nil
    36  	}
    37  	if err != nil {
    38  		return fmt.Errorf(strings.TrimSpace(
    39  			errMigrateLoadStates), opts.OneType, err)
    40  	}
    41  
    42  	_, err = opts.Two.States()
    43  	if err == backend.ErrNamedStatesNotSupported {
    44  		twoSingle = true
    45  		err = nil
    46  	}
    47  	if err != nil {
    48  		return fmt.Errorf(strings.TrimSpace(
    49  			errMigrateLoadStates), opts.TwoType, err)
    50  	}
    51  
    52  	// Setup defaults
    53  	opts.oneEnv = backend.DefaultStateName
    54  	opts.twoEnv = backend.DefaultStateName
    55  	opts.force = false
    56  
    57  	// Determine migration behavior based on whether the source/destionation
    58  	// supports multi-state.
    59  	switch {
    60  	// Single-state to single-state. This is the easiest case: we just
    61  	// copy the default state directly.
    62  	case oneSingle && twoSingle:
    63  		return m.backendMigrateState_s_s(opts)
    64  
    65  	// Single-state to multi-state. This is easy since we just copy
    66  	// the default state and ignore the rest in the destination.
    67  	case oneSingle && !twoSingle:
    68  		return m.backendMigrateState_s_s(opts)
    69  
    70  	// Multi-state to single-state. If the source has more than the default
    71  	// state this is complicated since we have to ask the user what to do.
    72  	case !oneSingle && twoSingle:
    73  		// If the source only has one state and it is the default,
    74  		// treat it as if it doesn't support multi-state.
    75  		if len(oneStates) == 1 && oneStates[0] == backend.DefaultStateName {
    76  			return m.backendMigrateState_s_s(opts)
    77  		}
    78  
    79  		return m.backendMigrateState_S_s(opts)
    80  
    81  	// Multi-state to multi-state. We merge the states together (migrating
    82  	// each from the source to the destination one by one).
    83  	case !oneSingle && !twoSingle:
    84  		// If the source only has one state and it is the default,
    85  		// treat it as if it doesn't support multi-state.
    86  		if len(oneStates) == 1 && oneStates[0] == backend.DefaultStateName {
    87  			return m.backendMigrateState_s_s(opts)
    88  		}
    89  
    90  		return m.backendMigrateState_S_S(opts)
    91  	}
    92  
    93  	return nil
    94  }
    95  
    96  //-------------------------------------------------------------------
    97  // State Migration Scenarios
    98  //
    99  // The functions below cover handling all the various scenarios that
   100  // can exist when migrating state. They are named in an immediately not
   101  // obvious format but is simple:
   102  //
   103  // Format: backendMigrateState_s1_s2[_suffix]
   104  //
   105  // When s1 or s2 is lower case, it means that it is a single state backend.
   106  // When either is uppercase, it means that state is a multi-state backend.
   107  // The suffix is used to disambiguate multiple cases with the same type of
   108  // states.
   109  //
   110  //-------------------------------------------------------------------
   111  
   112  // Multi-state to multi-state.
   113  func (m *Meta) backendMigrateState_S_S(opts *backendMigrateOpts) error {
   114  	// Ask the user if they want to migrate their existing remote state
   115  	migrate, err := m.confirm(&terraform.InputOpts{
   116  		Id: "backend-migrate-multistate-to-multistate",
   117  		Query: fmt.Sprintf(
   118  			"Do you want to migrate all environments to %q?",
   119  			opts.TwoType),
   120  		Description: fmt.Sprintf(
   121  			strings.TrimSpace(inputBackendMigrateMultiToMulti),
   122  			opts.OneType, opts.TwoType),
   123  	})
   124  	if err != nil {
   125  		return fmt.Errorf(
   126  			"Error asking for state migration action: %s", err)
   127  	}
   128  	if !migrate {
   129  		return fmt.Errorf("Migration aborted by user.")
   130  	}
   131  
   132  	// Read all the states
   133  	oneStates, err := opts.One.States()
   134  	if err != nil {
   135  		return fmt.Errorf(strings.TrimSpace(
   136  			errMigrateLoadStates), opts.OneType, err)
   137  	}
   138  
   139  	// Sort the states so they're always copied alphabetically
   140  	sort.Strings(oneStates)
   141  
   142  	// Go through each and migrate
   143  	for _, name := range oneStates {
   144  		// Copy the same names
   145  		opts.oneEnv = name
   146  		opts.twoEnv = name
   147  
   148  		// Force it, we confirmed above
   149  		opts.force = true
   150  
   151  		// Perform the migration
   152  		if err := m.backendMigrateState_s_s(opts); err != nil {
   153  			return fmt.Errorf(strings.TrimSpace(
   154  				errMigrateMulti), name, opts.OneType, opts.TwoType, err)
   155  		}
   156  	}
   157  
   158  	return nil
   159  }
   160  
   161  // Multi-state to single state.
   162  func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
   163  	currentEnv := m.Env()
   164  
   165  	// Ask the user if they want to migrate their existing remote state
   166  	migrate, err := m.confirm(&terraform.InputOpts{
   167  		Id: "backend-migrate-multistate-to-single",
   168  		Query: fmt.Sprintf(
   169  			"Destination state %q doesn't support environments (named states).\n"+
   170  				"Do you want to copy only your current environment?",
   171  			opts.TwoType),
   172  		Description: fmt.Sprintf(
   173  			strings.TrimSpace(inputBackendMigrateMultiToSingle),
   174  			opts.OneType, opts.TwoType, currentEnv),
   175  	})
   176  	if err != nil {
   177  		return fmt.Errorf(
   178  			"Error asking for state migration action: %s", err)
   179  	}
   180  	if !migrate {
   181  		return fmt.Errorf("Migration aborted by user.")
   182  	}
   183  
   184  	// Copy the default state
   185  	opts.oneEnv = currentEnv
   186  	return m.backendMigrateState_s_s(opts)
   187  }
   188  
   189  // Single state to single state, assumed default state name.
   190  func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
   191  	stateOne, err := opts.One.State(opts.oneEnv)
   192  	if err != nil {
   193  		return fmt.Errorf(strings.TrimSpace(
   194  			errMigrateSingleLoadDefault), opts.OneType, err)
   195  	}
   196  	if err := stateOne.RefreshState(); err != nil {
   197  		return fmt.Errorf(strings.TrimSpace(
   198  			errMigrateSingleLoadDefault), opts.OneType, err)
   199  	}
   200  
   201  	stateTwo, err := opts.Two.State(opts.twoEnv)
   202  	if err != nil {
   203  		return fmt.Errorf(strings.TrimSpace(
   204  			errMigrateSingleLoadDefault), opts.TwoType, err)
   205  	}
   206  	if err := stateTwo.RefreshState(); err != nil {
   207  		return fmt.Errorf(strings.TrimSpace(
   208  			errMigrateSingleLoadDefault), opts.TwoType, err)
   209  	}
   210  
   211  	lockInfoOne := state.NewLockInfo()
   212  	lockInfoOne.Operation = "migration"
   213  	lockInfoOne.Info = "source state"
   214  
   215  	lockIDOne, err := clistate.Lock(stateOne, lockInfoOne, m.Ui, m.Colorize())
   216  	if err != nil {
   217  		return fmt.Errorf("Error locking source state: %s", err)
   218  	}
   219  	defer clistate.Unlock(stateOne, lockIDOne, m.Ui, m.Colorize())
   220  
   221  	lockInfoTwo := state.NewLockInfo()
   222  	lockInfoTwo.Operation = "migration"
   223  	lockInfoTwo.Info = "destination state"
   224  
   225  	lockIDTwo, err := clistate.Lock(stateTwo, lockInfoTwo, m.Ui, m.Colorize())
   226  	if err != nil {
   227  		return fmt.Errorf("Error locking destination state: %s", err)
   228  	}
   229  	defer clistate.Unlock(stateTwo, lockIDTwo, m.Ui, m.Colorize())
   230  
   231  	one := stateOne.State()
   232  	two := stateTwo.State()
   233  
   234  	var confirmFunc func(state.State, state.State, *backendMigrateOpts) (bool, error)
   235  	switch {
   236  	// No migration necessary
   237  	case one.Empty() && two.Empty():
   238  		return nil
   239  
   240  	// No migration necessary if we're inheriting state.
   241  	case one.Empty() && !two.Empty():
   242  		return nil
   243  
   244  	// We have existing state moving into no state. Ask the user if
   245  	// they'd like to do this.
   246  	case !one.Empty() && two.Empty():
   247  		confirmFunc = m.backendMigrateEmptyConfirm
   248  
   249  	// Both states are non-empty, meaning we need to determine which
   250  	// state should be used and update accordingly.
   251  	case !one.Empty() && !two.Empty():
   252  		confirmFunc = m.backendMigrateNonEmptyConfirm
   253  	}
   254  
   255  	if confirmFunc == nil {
   256  		panic("confirmFunc must not be nil")
   257  	}
   258  
   259  	if !opts.force {
   260  		// Confirm with the user whether we want to copy state over
   261  		confirm, err := confirmFunc(stateOne, stateTwo, opts)
   262  		if err != nil {
   263  			return err
   264  		}
   265  		if !confirm {
   266  			return nil
   267  		}
   268  	}
   269  
   270  	// Confirmed! Write.
   271  	if err := stateTwo.WriteState(one); err != nil {
   272  		return fmt.Errorf(strings.TrimSpace(errBackendStateCopy),
   273  			opts.OneType, opts.TwoType, err)
   274  	}
   275  	if err := stateTwo.PersistState(); err != nil {
   276  		return fmt.Errorf(strings.TrimSpace(errBackendStateCopy),
   277  			opts.OneType, opts.TwoType, err)
   278  	}
   279  
   280  	// And we're done.
   281  	return nil
   282  }
   283  
   284  func (m *Meta) backendMigrateEmptyConfirm(one, two state.State, opts *backendMigrateOpts) (bool, error) {
   285  	inputOpts := &terraform.InputOpts{
   286  		Id: "backend-migrate-copy-to-empty",
   287  		Query: fmt.Sprintf(
   288  			"Do you want to copy state from %q to %q?",
   289  			opts.OneType, opts.TwoType),
   290  		Description: fmt.Sprintf(
   291  			strings.TrimSpace(inputBackendMigrateEmpty),
   292  			opts.OneType, opts.TwoType),
   293  	}
   294  
   295  	// Confirm with the user that the copy should occur
   296  	for {
   297  		v, err := m.UIInput().Input(inputOpts)
   298  		if err != nil {
   299  			return false, fmt.Errorf(
   300  				"Error asking for state copy action: %s", err)
   301  		}
   302  
   303  		switch strings.ToLower(v) {
   304  		case "no":
   305  			return false, nil
   306  
   307  		case "yes":
   308  			return true, nil
   309  		}
   310  	}
   311  }
   312  
   313  func (m *Meta) backendMigrateNonEmptyConfirm(
   314  	stateOne, stateTwo state.State, opts *backendMigrateOpts) (bool, error) {
   315  	// We need to grab both states so we can write them to a file
   316  	one := stateOne.State()
   317  	two := stateTwo.State()
   318  
   319  	// Save both to a temporary
   320  	td, err := ioutil.TempDir("", "terraform")
   321  	if err != nil {
   322  		return false, fmt.Errorf("Error creating temporary directory: %s", err)
   323  	}
   324  	defer os.RemoveAll(td)
   325  
   326  	// Helper to write the state
   327  	saveHelper := func(n, path string, s *terraform.State) error {
   328  		f, err := os.Create(path)
   329  		if err != nil {
   330  			return err
   331  		}
   332  		defer f.Close()
   333  
   334  		return terraform.WriteState(s, f)
   335  	}
   336  
   337  	// Write the states
   338  	onePath := filepath.Join(td, fmt.Sprintf("1-%s.tfstate", opts.OneType))
   339  	twoPath := filepath.Join(td, fmt.Sprintf("2-%s.tfstate", opts.TwoType))
   340  	if err := saveHelper(opts.OneType, onePath, one); err != nil {
   341  		return false, fmt.Errorf("Error saving temporary state: %s", err)
   342  	}
   343  	if err := saveHelper(opts.TwoType, twoPath, two); err != nil {
   344  		return false, fmt.Errorf("Error saving temporary state: %s", err)
   345  	}
   346  
   347  	// Ask for confirmation
   348  	inputOpts := &terraform.InputOpts{
   349  		Id: "backend-migrate-to-backend",
   350  		Query: fmt.Sprintf(
   351  			"Do you want to copy state from %q to %q?",
   352  			opts.OneType, opts.TwoType),
   353  		Description: fmt.Sprintf(
   354  			strings.TrimSpace(inputBackendMigrateNonEmpty),
   355  			opts.OneType, opts.TwoType, onePath, twoPath),
   356  	}
   357  
   358  	// Confirm with the user that the copy should occur
   359  	for {
   360  		v, err := m.UIInput().Input(inputOpts)
   361  		if err != nil {
   362  			return false, fmt.Errorf(
   363  				"Error asking for state copy action: %s", err)
   364  		}
   365  
   366  		switch strings.ToLower(v) {
   367  		case "no":
   368  			return false, nil
   369  
   370  		case "yes":
   371  			return true, nil
   372  		}
   373  	}
   374  }
   375  
   376  type backendMigrateOpts struct {
   377  	OneType, TwoType string
   378  	One, Two         backend.Backend
   379  
   380  	// Fields below are set internally when migrate is called
   381  
   382  	oneEnv string // source env
   383  	twoEnv string // dest env
   384  	force  bool   // if true, won't ask for confirmation
   385  }
   386  
   387  const errMigrateLoadStates = `
   388  Error inspecting state in %q: %s
   389  
   390  Prior to changing backends, Terraform inspects the source and destionation
   391  states to determine what kind of migration steps need to be taken, if any.
   392  Terraform failed to load the states. The data in both the source and the
   393  destination remain unmodified. Please resolve the above error and try again.
   394  `
   395  
   396  const errMigrateSingleLoadDefault = `
   397  Error loading state from %q: %s
   398  
   399  Terraform failed to load the default state from %[1]q.
   400  State migration cannot occur unless the state can be loaded. Backend
   401  modification and state migration has been aborted. The state in both the
   402  source and the destination remain unmodified. Please resolve the
   403  above error and try again.
   404  `
   405  
   406  const errMigrateMulti = `
   407  Error migrating the environment %q from %q to %q:
   408  
   409  %s
   410  
   411  Terraform copies environments in alphabetical order. Any environments
   412  alphabetically earlier than this one have been copied. Any environments
   413  later than this haven't been modified in the destination. No environments
   414  in the source state have been modified.
   415  
   416  Please resolve the error above and run the initialization command again.
   417  This will attempt to copy (with permission) all environments again.
   418  `
   419  
   420  const errBackendStateCopy = `
   421  Error copying state from %q to %q: %s
   422  
   423  The state in %[1]q remains intact and unmodified. Please resolve the
   424  error above and try again.
   425  `
   426  
   427  const inputBackendMigrateEmpty = `
   428  Pre-existing state was found in %q while migrating to %q. No existing
   429  state was found in %[2]q. Do you want to copy the state from %[1]q to
   430  %[2]q? Enter "yes" to copy and "no" to start with an empty state.
   431  `
   432  
   433  const inputBackendMigrateNonEmpty = `
   434  Pre-existing state was found in %q while migrating to %q. An existing
   435  non-empty state exists in %[2]q. The two states have been saved to temporary
   436  files that will be removed after responding to this query.
   437  
   438  One (%[1]q): %[3]s
   439  Two (%[2]q): %[4]s
   440  
   441  Do you want to copy the state from %[1]q to %[2]q? Enter "yes" to copy
   442  and "no" to start with the existing state in %[2]q.
   443  `
   444  
   445  const inputBackendMigrateMultiToSingle = `
   446  The existing backend %[1]q supports environments and you currently are
   447  using more than one. The target backend %[2]q doesn't support environments.
   448  If you continue, Terraform will offer to copy your current environment
   449  %[3]q to the default environment in the target. Your existing environments
   450  in the source backend won't be modified. If you want to switch environments,
   451  back them up, or cancel altogether, answer "no" and Terraform will abort.
   452  `
   453  
   454  const inputBackendMigrateMultiToMulti = `
   455  Both the existing backend %[1]q and the target backend %[2]q support
   456  environments. When migrating between backends, Terraform will copy all
   457  environments (with the same names). THIS WILL OVERWRITE any conflicting
   458  states in the destination.
   459  
   460  Terraform initialization doesn't currently migrate only select environments.
   461  If you want to migrate a select number of environments, you must manually
   462  pull and push those states.
   463  
   464  If you answer "yes", Terraform will migrate all states. If you answer
   465  "no", Terraform will abort.
   466  `