github.com/skyscape-cloud-services/terraform@v0.9.2-0.20170609144644-7ece028a1747/command/meta_backend_migrate.go (about)

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