github.com/dougneal/terraform@v0.6.15-0.20170330092735-b6a3840768a4/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  	migrate := m.forceInitCopy
   166  	if !migrate {
   167  		var err error
   168  		// Ask the user if they want to migrate their existing remote state
   169  		migrate, err = m.confirm(&terraform.InputOpts{
   170  			Id: "backend-migrate-multistate-to-single",
   171  			Query: fmt.Sprintf(
   172  				"Destination state %q doesn't support environments (named states).\n"+
   173  					"Do you want to copy only your current environment?",
   174  				opts.TwoType),
   175  			Description: fmt.Sprintf(
   176  				strings.TrimSpace(inputBackendMigrateMultiToSingle),
   177  				opts.OneType, opts.TwoType, currentEnv),
   178  		})
   179  		if err != nil {
   180  			return fmt.Errorf(
   181  				"Error asking for state migration action: %s", err)
   182  		}
   183  	}
   184  
   185  	if !migrate {
   186  		return fmt.Errorf("Migration aborted by user.")
   187  	}
   188  
   189  	// Copy the default state
   190  	opts.oneEnv = currentEnv
   191  
   192  	// now switch back to the default env so we can acccess the new backend
   193  	m.SetEnv(backend.DefaultStateName)
   194  
   195  	return m.backendMigrateState_s_s(opts)
   196  }
   197  
   198  // Single state to single state, assumed default state name.
   199  func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
   200  	stateOne, err := opts.One.State(opts.oneEnv)
   201  	if err != nil {
   202  		return fmt.Errorf(strings.TrimSpace(
   203  			errMigrateSingleLoadDefault), opts.OneType, err)
   204  	}
   205  	if err := stateOne.RefreshState(); err != nil {
   206  		return fmt.Errorf(strings.TrimSpace(
   207  			errMigrateSingleLoadDefault), opts.OneType, err)
   208  	}
   209  
   210  	stateTwo, err := opts.Two.State(opts.twoEnv)
   211  	if err != nil {
   212  		return fmt.Errorf(strings.TrimSpace(
   213  			errMigrateSingleLoadDefault), opts.TwoType, err)
   214  	}
   215  	if err := stateTwo.RefreshState(); err != nil {
   216  		return fmt.Errorf(strings.TrimSpace(
   217  			errMigrateSingleLoadDefault), opts.TwoType, err)
   218  	}
   219  
   220  	lockInfoOne := state.NewLockInfo()
   221  	lockInfoOne.Operation = "migration"
   222  	lockInfoOne.Info = "source state"
   223  
   224  	lockIDOne, err := clistate.Lock(stateOne, lockInfoOne, m.Ui, m.Colorize())
   225  	if err != nil {
   226  		return fmt.Errorf("Error locking source state: %s", err)
   227  	}
   228  	defer clistate.Unlock(stateOne, lockIDOne, m.Ui, m.Colorize())
   229  
   230  	lockInfoTwo := state.NewLockInfo()
   231  	lockInfoTwo.Operation = "migration"
   232  	lockInfoTwo.Info = "destination state"
   233  
   234  	lockIDTwo, err := clistate.Lock(stateTwo, lockInfoTwo, m.Ui, m.Colorize())
   235  	if err != nil {
   236  		return fmt.Errorf("Error locking destination state: %s", err)
   237  	}
   238  	defer clistate.Unlock(stateTwo, lockIDTwo, m.Ui, m.Colorize())
   239  
   240  	one := stateOne.State()
   241  	two := stateTwo.State()
   242  
   243  	// Clear the legacy remote state in both cases. If we're at the migration
   244  	// step then this won't be used anymore.
   245  	if one != nil {
   246  		one.Remote = nil
   247  	}
   248  	if two != nil {
   249  		two.Remote = nil
   250  	}
   251  
   252  	var confirmFunc func(state.State, state.State, *backendMigrateOpts) (bool, error)
   253  	switch {
   254  	// No migration necessary
   255  	case one.Empty() && two.Empty():
   256  		return nil
   257  
   258  	// No migration necessary if we're inheriting state.
   259  	case one.Empty() && !two.Empty():
   260  		return nil
   261  
   262  	// We have existing state moving into no state. Ask the user if
   263  	// they'd like to do this.
   264  	case !one.Empty() && two.Empty():
   265  		confirmFunc = m.backendMigrateEmptyConfirm
   266  
   267  	// Both states are non-empty, meaning we need to determine which
   268  	// state should be used and update accordingly.
   269  	case !one.Empty() && !two.Empty():
   270  		confirmFunc = m.backendMigrateNonEmptyConfirm
   271  	}
   272  
   273  	if confirmFunc == nil {
   274  		panic("confirmFunc must not be nil")
   275  	}
   276  
   277  	if !opts.force {
   278  		// Confirm with the user whether we want to copy state over
   279  		confirm, err := confirmFunc(stateOne, stateTwo, opts)
   280  		if err != nil {
   281  			return err
   282  		}
   283  		if !confirm {
   284  			return nil
   285  		}
   286  	}
   287  
   288  	// Confirmed! Write.
   289  	if err := stateTwo.WriteState(one); err != nil {
   290  		return fmt.Errorf(strings.TrimSpace(errBackendStateCopy),
   291  			opts.OneType, opts.TwoType, err)
   292  	}
   293  	if err := stateTwo.PersistState(); err != nil {
   294  		return fmt.Errorf(strings.TrimSpace(errBackendStateCopy),
   295  			opts.OneType, opts.TwoType, err)
   296  	}
   297  
   298  	// And we're done.
   299  	return nil
   300  }
   301  
   302  func (m *Meta) backendMigrateEmptyConfirm(one, two state.State, opts *backendMigrateOpts) (bool, error) {
   303  	if m.forceInitCopy {
   304  		return true, nil
   305  	}
   306  
   307  	inputOpts := &terraform.InputOpts{
   308  		Id: "backend-migrate-copy-to-empty",
   309  		Query: fmt.Sprintf(
   310  			"Do you want to copy state from %q to %q?",
   311  			opts.OneType, opts.TwoType),
   312  		Description: fmt.Sprintf(
   313  			strings.TrimSpace(inputBackendMigrateEmpty),
   314  			opts.OneType, opts.TwoType),
   315  	}
   316  
   317  	// Confirm with the user that the copy should occur
   318  	for {
   319  		v, err := m.UIInput().Input(inputOpts)
   320  		if err != nil {
   321  			return false, fmt.Errorf(
   322  				"Error asking for state copy action: %s", err)
   323  		}
   324  
   325  		switch strings.ToLower(v) {
   326  		case "no":
   327  			return false, nil
   328  
   329  		case "yes":
   330  			return true, nil
   331  		}
   332  	}
   333  }
   334  
   335  func (m *Meta) backendMigrateNonEmptyConfirm(
   336  	stateOne, stateTwo state.State, opts *backendMigrateOpts) (bool, error) {
   337  	// We need to grab both states so we can write them to a file
   338  	one := stateOne.State()
   339  	two := stateTwo.State()
   340  
   341  	// Save both to a temporary
   342  	td, err := ioutil.TempDir("", "terraform")
   343  	if err != nil {
   344  		return false, fmt.Errorf("Error creating temporary directory: %s", err)
   345  	}
   346  	defer os.RemoveAll(td)
   347  
   348  	// Helper to write the state
   349  	saveHelper := func(n, path string, s *terraform.State) error {
   350  		f, err := os.Create(path)
   351  		if err != nil {
   352  			return err
   353  		}
   354  		defer f.Close()
   355  
   356  		return terraform.WriteState(s, f)
   357  	}
   358  
   359  	// Write the states
   360  	onePath := filepath.Join(td, fmt.Sprintf("1-%s.tfstate", opts.OneType))
   361  	twoPath := filepath.Join(td, fmt.Sprintf("2-%s.tfstate", opts.TwoType))
   362  	if err := saveHelper(opts.OneType, onePath, one); err != nil {
   363  		return false, fmt.Errorf("Error saving temporary state: %s", err)
   364  	}
   365  	if err := saveHelper(opts.TwoType, twoPath, two); err != nil {
   366  		return false, fmt.Errorf("Error saving temporary state: %s", err)
   367  	}
   368  
   369  	if m.forceInitCopy {
   370  		return true, nil
   371  	}
   372  
   373  	// Ask for confirmation
   374  	inputOpts := &terraform.InputOpts{
   375  		Id: "backend-migrate-to-backend",
   376  		Query: fmt.Sprintf(
   377  			"Do you want to copy state from %q to %q?",
   378  			opts.OneType, opts.TwoType),
   379  		Description: fmt.Sprintf(
   380  			strings.TrimSpace(inputBackendMigrateNonEmpty),
   381  			opts.OneType, opts.TwoType, onePath, twoPath),
   382  	}
   383  
   384  	// Confirm with the user that the copy should occur
   385  	for {
   386  		v, err := m.UIInput().Input(inputOpts)
   387  		if err != nil {
   388  			return false, fmt.Errorf(
   389  				"Error asking for state copy action: %s", err)
   390  		}
   391  
   392  		switch strings.ToLower(v) {
   393  		case "no":
   394  			return false, nil
   395  
   396  		case "yes":
   397  			return true, nil
   398  		}
   399  	}
   400  }
   401  
   402  type backendMigrateOpts struct {
   403  	OneType, TwoType string
   404  	One, Two         backend.Backend
   405  
   406  	// Fields below are set internally when migrate is called
   407  
   408  	oneEnv string // source env
   409  	twoEnv string // dest env
   410  	force  bool   // if true, won't ask for confirmation
   411  }
   412  
   413  const errMigrateLoadStates = `
   414  Error inspecting state in %q: %s
   415  
   416  Prior to changing backends, Terraform inspects the source and destionation
   417  states to determine what kind of migration steps need to be taken, if any.
   418  Terraform failed to load the states. The data in both the source and the
   419  destination remain unmodified. Please resolve the above error and try again.
   420  `
   421  
   422  const errMigrateSingleLoadDefault = `
   423  Error loading state from %q: %s
   424  
   425  Terraform failed to load the default state from %[1]q.
   426  State migration cannot occur unless the state can be loaded. Backend
   427  modification and state migration has been aborted. The state in both the
   428  source and the destination remain unmodified. Please resolve the
   429  above error and try again.
   430  `
   431  
   432  const errMigrateMulti = `
   433  Error migrating the environment %q from %q to %q:
   434  
   435  %s
   436  
   437  Terraform copies environments in alphabetical order. Any environments
   438  alphabetically earlier than this one have been copied. Any environments
   439  later than this haven't been modified in the destination. No environments
   440  in the source state have been modified.
   441  
   442  Please resolve the error above and run the initialization command again.
   443  This will attempt to copy (with permission) all environments again.
   444  `
   445  
   446  const errBackendStateCopy = `
   447  Error copying state from %q to %q: %s
   448  
   449  The state in %[1]q remains intact and unmodified. Please resolve the
   450  error above and try again.
   451  `
   452  
   453  const inputBackendMigrateEmpty = `
   454  Pre-existing state was found in %q while migrating to %q. No existing
   455  state was found in %[2]q. Do you want to copy the state from %[1]q to
   456  %[2]q? Enter "yes" to copy and "no" to start with an empty state.
   457  `
   458  
   459  const inputBackendMigrateNonEmpty = `
   460  Pre-existing state was found in %q while migrating to %q. An existing
   461  non-empty state exists in %[2]q. The two states have been saved to temporary
   462  files that will be removed after responding to this query.
   463  
   464  One (%[1]q): %[3]s
   465  Two (%[2]q): %[4]s
   466  
   467  Do you want to copy the state from %[1]q to %[2]q? Enter "yes" to copy
   468  and "no" to start with the existing state in %[2]q.
   469  `
   470  
   471  const inputBackendMigrateMultiToSingle = `
   472  The existing backend %[1]q supports environments and you currently are
   473  using more than one. The target backend %[2]q doesn't support environments.
   474  If you continue, Terraform will offer to copy your current environment
   475  %[3]q to the default environment in the target. Your existing environments
   476  in the source backend won't be modified. If you want to switch environments,
   477  back them up, or cancel altogether, answer "no" and Terraform will abort.
   478  `
   479  
   480  const inputBackendMigrateMultiToMulti = `
   481  Both the existing backend %[1]q and the target backend %[2]q support
   482  environments. When migrating between backends, Terraform will copy all
   483  environments (with the same names). THIS WILL OVERWRITE any conflicting
   484  states in the destination.
   485  
   486  Terraform initialization doesn't currently migrate only select environments.
   487  If you want to migrate a select number of environments, you must manually
   488  pull and push those states.
   489  
   490  If you answer "yes", Terraform will migrate all states. If you answer
   491  "no", Terraform will abort.
   492  `