github.com/tompao/terraform@v0.6.10-0.20180215233341-e41b29d0961b/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 workspaces 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.Workspace()
   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 workspaces.\n"+
   175  					"Do you want to copy only your current workspace?",
   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.SetWorkspace(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: "Do you want to copy existing state to the new backend?",
   342  		Description: fmt.Sprintf(
   343  			strings.TrimSpace(inputBackendMigrateEmpty),
   344  			opts.OneType, opts.TwoType),
   345  	}
   346  
   347  	return m.confirm(inputOpts)
   348  }
   349  
   350  func (m *Meta) backendMigrateNonEmptyConfirm(
   351  	stateOne, stateTwo state.State, opts *backendMigrateOpts) (bool, error) {
   352  	// We need to grab both states so we can write them to a file
   353  	one := stateOne.State()
   354  	two := stateTwo.State()
   355  
   356  	// Save both to a temporary
   357  	td, err := ioutil.TempDir("", "terraform")
   358  	if err != nil {
   359  		return false, fmt.Errorf("Error creating temporary directory: %s", err)
   360  	}
   361  	defer os.RemoveAll(td)
   362  
   363  	// Helper to write the state
   364  	saveHelper := func(n, path string, s *terraform.State) error {
   365  		f, err := os.Create(path)
   366  		if err != nil {
   367  			return err
   368  		}
   369  		defer f.Close()
   370  
   371  		return terraform.WriteState(s, f)
   372  	}
   373  
   374  	// Write the states
   375  	onePath := filepath.Join(td, fmt.Sprintf("1-%s.tfstate", opts.OneType))
   376  	twoPath := filepath.Join(td, fmt.Sprintf("2-%s.tfstate", opts.TwoType))
   377  	if err := saveHelper(opts.OneType, onePath, one); err != nil {
   378  		return false, fmt.Errorf("Error saving temporary state: %s", err)
   379  	}
   380  	if err := saveHelper(opts.TwoType, twoPath, two); err != nil {
   381  		return false, fmt.Errorf("Error saving temporary state: %s", err)
   382  	}
   383  
   384  	// Ask for confirmation
   385  	inputOpts := &terraform.InputOpts{
   386  		Id:    "backend-migrate-to-backend",
   387  		Query: "Do you want to copy existing state to the new backend?",
   388  		Description: fmt.Sprintf(
   389  			strings.TrimSpace(inputBackendMigrateNonEmpty),
   390  			opts.OneType, opts.TwoType, onePath, twoPath),
   391  	}
   392  
   393  	// Confirm with the user that the copy should occur
   394  	return m.confirm(inputOpts)
   395  }
   396  
   397  type backendMigrateOpts struct {
   398  	OneType, TwoType string
   399  	One, Two         backend.Backend
   400  
   401  	// Fields below are set internally when migrate is called
   402  
   403  	oneEnv string // source env
   404  	twoEnv string // dest env
   405  	force  bool   // if true, won't ask for confirmation
   406  }
   407  
   408  const errMigrateLoadStates = `
   409  Error inspecting states in the %q backend:
   410      %s
   411  
   412  Prior to changing backends, Terraform inspects the source and destination
   413  states to determine what kind of migration steps need to be taken, if any.
   414  Terraform failed to load the states. The data in both the source and the
   415  destination remain unmodified. Please resolve the above error and try again.
   416  `
   417  
   418  const errMigrateSingleLoadDefault = `
   419  Error loading state:
   420      %[2]s
   421  
   422  Terraform failed to load the default state from the %[1]q backend.
   423  State migration cannot occur unless the state can be loaded. Backend
   424  modification and state migration has been aborted. The state in both the
   425  source and the destination remain unmodified. Please resolve the
   426  above error and try again.
   427  `
   428  
   429  const errMigrateMulti = `
   430  Error migrating the workspace %q from the previous %q backend to the newly
   431  configured %q backend:
   432      %s
   433  
   434  Terraform copies workspaces in alphabetical order. Any workspaces
   435  alphabetically earlier than this one have been copied. Any workspaces
   436  later than this haven't been modified in the destination. No workspaces
   437  in the source state have been modified.
   438  
   439  Please resolve the error above and run the initialization command again.
   440  This will attempt to copy (with permission) all workspaces again.
   441  `
   442  
   443  const errBackendStateCopy = `
   444  Error copying state from the previous %q backend to the newly configured %q backend:
   445      %s
   446  
   447  The state in the previous backend remains intact and unmodified. Please resolve
   448  the error above and try again.
   449  `
   450  
   451  const inputBackendMigrateEmpty = `
   452  Pre-existing state was found while migrating the previous %q backend to the
   453  newly configured %q backend. No existing state was found in the newly
   454  configured %[2]q backend. Do you want to copy this state to the new %[2]q
   455  backend? Enter "yes" to copy and "no" to start with an empty state.
   456  `
   457  
   458  const inputBackendMigrateNonEmpty = `
   459  Pre-existing state was found while migrating the previous %q backend to the
   460  newly configured %q backend. An existing non-empty state already exists in
   461  the new backend. The two states have been saved to temporary files that will be
   462  removed after responding to this query.
   463  
   464  Previous (type %[1]q): %[3]s
   465  New      (type %[2]q): %[4]s
   466  
   467  Do you want to overwrite the state in the new backend with the previous state?
   468  Enter "yes" to copy and "no" to start with the existing state in the newly
   469  configured %[2]q backend.
   470  `
   471  
   472  const inputBackendMigrateMultiToSingle = `
   473  The existing %[1]q backend supports workspaces and you currently are
   474  using more than one. The newly configured %[2]q backend doesn't support
   475  workspaces. If you continue, Terraform will copy your current workspace %[3]q
   476  to the default workspace in the target backend. Your existing workspaces in the
   477  source backend won't be modified. If you want to switch workspaces, back them
   478  up, or cancel altogether, answer "no" and Terraform will abort.
   479  `
   480  
   481  const inputBackendMigrateMultiToMulti = `
   482  Both the existing %[1]q backend and the newly configured %[2]q backend support
   483  workspaces. When migrating between backends, Terraform will copy all
   484  workspaces (with the same names). THIS WILL OVERWRITE any conflicting
   485  states in the destination.
   486  
   487  Terraform initialization doesn't currently migrate only select workspaces.
   488  If you want to migrate a select number of workspaces, you must manually
   489  pull and push those states.
   490  
   491  If you answer "yes", Terraform will migrate all states. If you answer
   492  "no", Terraform will abort.
   493  `