github.com/ben-turner/terraform@v0.11.8-0.20180503104400-0cc9e050ecd4/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 := context.Background()
   237  
   238  		lockerOne := clistate.NewLocker(lockCtx, m.stateLockTimeout, m.Ui, m.Colorize())
   239  		if err := lockerOne.Lock(stateOne, "migration source state"); err != nil {
   240  			return fmt.Errorf("Error locking source state: %s", err)
   241  		}
   242  		defer lockerOne.Unlock(nil)
   243  
   244  		lockerTwo := clistate.NewLocker(lockCtx, m.stateLockTimeout, m.Ui, m.Colorize())
   245  		if err := lockerTwo.Lock(stateTwo, "migration destination state"); err != nil {
   246  			return fmt.Errorf("Error locking destination state: %s", err)
   247  		}
   248  		defer lockerTwo.Unlock(nil)
   249  
   250  		// We now own a lock, so double check that we have the version
   251  		// corresponding to the lock.
   252  		if err := stateOne.RefreshState(); err != nil {
   253  			return fmt.Errorf(strings.TrimSpace(
   254  				errMigrateSingleLoadDefault), opts.OneType, err)
   255  		}
   256  		if err := stateTwo.RefreshState(); err != nil {
   257  			return fmt.Errorf(strings.TrimSpace(
   258  				errMigrateSingleLoadDefault), opts.OneType, err)
   259  		}
   260  
   261  		one = stateOne.State()
   262  		two = stateTwo.State()
   263  	}
   264  
   265  	// Clear the legacy remote state in both cases. If we're at the migration
   266  	// step then this won't be used anymore.
   267  	if one != nil {
   268  		one.Remote = nil
   269  	}
   270  	if two != nil {
   271  		two.Remote = nil
   272  	}
   273  
   274  	var confirmFunc func(state.State, state.State, *backendMigrateOpts) (bool, error)
   275  	switch {
   276  	// No migration necessary
   277  	case one.Empty() && two.Empty():
   278  		return nil
   279  
   280  	// No migration necessary if we're inheriting state.
   281  	case one.Empty() && !two.Empty():
   282  		return nil
   283  
   284  	// We have existing state moving into no state. Ask the user if
   285  	// they'd like to do this.
   286  	case !one.Empty() && two.Empty():
   287  		confirmFunc = m.backendMigrateEmptyConfirm
   288  
   289  	// Both states are non-empty, meaning we need to determine which
   290  	// state should be used and update accordingly.
   291  	case !one.Empty() && !two.Empty():
   292  		confirmFunc = m.backendMigrateNonEmptyConfirm
   293  	}
   294  
   295  	if confirmFunc == nil {
   296  		panic("confirmFunc must not be nil")
   297  	}
   298  
   299  	if !opts.force {
   300  		// Abort if we can't ask for input.
   301  		if !m.input {
   302  			return errors.New("error asking for state migration action: input disabled")
   303  		}
   304  
   305  		// Confirm with the user whether we want to copy state over
   306  		confirm, err := confirmFunc(stateOne, stateTwo, opts)
   307  		if err != nil {
   308  			return err
   309  		}
   310  		if !confirm {
   311  			return nil
   312  		}
   313  	}
   314  
   315  	// Confirmed! Write.
   316  	if err := stateTwo.WriteState(one); err != nil {
   317  		return fmt.Errorf(strings.TrimSpace(errBackendStateCopy),
   318  			opts.OneType, opts.TwoType, err)
   319  	}
   320  	if err := stateTwo.PersistState(); err != nil {
   321  		return fmt.Errorf(strings.TrimSpace(errBackendStateCopy),
   322  			opts.OneType, opts.TwoType, err)
   323  	}
   324  
   325  	// And we're done.
   326  	return nil
   327  }
   328  
   329  func (m *Meta) backendMigrateEmptyConfirm(one, two state.State, opts *backendMigrateOpts) (bool, error) {
   330  	inputOpts := &terraform.InputOpts{
   331  		Id:    "backend-migrate-copy-to-empty",
   332  		Query: "Do you want to copy existing state to the new backend?",
   333  		Description: fmt.Sprintf(
   334  			strings.TrimSpace(inputBackendMigrateEmpty),
   335  			opts.OneType, opts.TwoType),
   336  	}
   337  
   338  	return m.confirm(inputOpts)
   339  }
   340  
   341  func (m *Meta) backendMigrateNonEmptyConfirm(
   342  	stateOne, stateTwo state.State, opts *backendMigrateOpts) (bool, error) {
   343  	// We need to grab both states so we can write them to a file
   344  	one := stateOne.State()
   345  	two := stateTwo.State()
   346  
   347  	// Save both to a temporary
   348  	td, err := ioutil.TempDir("", "terraform")
   349  	if err != nil {
   350  		return false, fmt.Errorf("Error creating temporary directory: %s", err)
   351  	}
   352  	defer os.RemoveAll(td)
   353  
   354  	// Helper to write the state
   355  	saveHelper := func(n, path string, s *terraform.State) error {
   356  		f, err := os.Create(path)
   357  		if err != nil {
   358  			return err
   359  		}
   360  		defer f.Close()
   361  
   362  		return terraform.WriteState(s, f)
   363  	}
   364  
   365  	// Write the states
   366  	onePath := filepath.Join(td, fmt.Sprintf("1-%s.tfstate", opts.OneType))
   367  	twoPath := filepath.Join(td, fmt.Sprintf("2-%s.tfstate", opts.TwoType))
   368  	if err := saveHelper(opts.OneType, onePath, one); err != nil {
   369  		return false, fmt.Errorf("Error saving temporary state: %s", err)
   370  	}
   371  	if err := saveHelper(opts.TwoType, twoPath, two); err != nil {
   372  		return false, fmt.Errorf("Error saving temporary state: %s", err)
   373  	}
   374  
   375  	// Ask for confirmation
   376  	inputOpts := &terraform.InputOpts{
   377  		Id:    "backend-migrate-to-backend",
   378  		Query: "Do you want to copy existing state to the new backend?",
   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  	return m.confirm(inputOpts)
   386  }
   387  
   388  type backendMigrateOpts struct {
   389  	OneType, TwoType string
   390  	One, Two         backend.Backend
   391  
   392  	// Fields below are set internally when migrate is called
   393  
   394  	oneEnv string // source env
   395  	twoEnv string // dest env
   396  	force  bool   // if true, won't ask for confirmation
   397  }
   398  
   399  const errMigrateLoadStates = `
   400  Error inspecting states in the %q backend:
   401      %s
   402  
   403  Prior to changing backends, Terraform inspects the source and destination
   404  states to determine what kind of migration steps need to be taken, if any.
   405  Terraform failed to load the states. The data in both the source and the
   406  destination remain unmodified. Please resolve the above error and try again.
   407  `
   408  
   409  const errMigrateSingleLoadDefault = `
   410  Error loading state:
   411      %[2]s
   412  
   413  Terraform failed to load the default state from the %[1]q backend.
   414  State migration cannot occur unless the state can be loaded. Backend
   415  modification and state migration has been aborted. The state in both the
   416  source and the destination remain unmodified. Please resolve the
   417  above error and try again.
   418  `
   419  
   420  const errMigrateMulti = `
   421  Error migrating the workspace %q from the previous %q backend to the newly
   422  configured %q backend:
   423      %s
   424  
   425  Terraform copies workspaces in alphabetical order. Any workspaces
   426  alphabetically earlier than this one have been copied. Any workspaces
   427  later than this haven't been modified in the destination. No workspaces
   428  in the source state have been modified.
   429  
   430  Please resolve the error above and run the initialization command again.
   431  This will attempt to copy (with permission) all workspaces again.
   432  `
   433  
   434  const errBackendStateCopy = `
   435  Error copying state from the previous %q backend to the newly configured %q backend:
   436      %s
   437  
   438  The state in the previous backend remains intact and unmodified. Please resolve
   439  the error above and try again.
   440  `
   441  
   442  const inputBackendMigrateEmpty = `
   443  Pre-existing state was found while migrating the previous %q backend to the
   444  newly configured %q backend. No existing state was found in the newly
   445  configured %[2]q backend. Do you want to copy this state to the new %[2]q
   446  backend? Enter "yes" to copy and "no" to start with an empty state.
   447  `
   448  
   449  const inputBackendMigrateNonEmpty = `
   450  Pre-existing state was found while migrating the previous %q backend to the
   451  newly configured %q backend. An existing non-empty state already exists in
   452  the new backend. The two states have been saved to temporary files that will be
   453  removed after responding to this query.
   454  
   455  Previous (type %[1]q): %[3]s
   456  New      (type %[2]q): %[4]s
   457  
   458  Do you want to overwrite the state in the new backend with the previous state?
   459  Enter "yes" to copy and "no" to start with the existing state in the newly
   460  configured %[2]q backend.
   461  `
   462  
   463  const inputBackendMigrateMultiToSingle = `
   464  The existing %[1]q backend supports workspaces and you currently are
   465  using more than one. The newly configured %[2]q backend doesn't support
   466  workspaces. If you continue, Terraform will copy your current workspace %[3]q
   467  to the default workspace in the target backend. Your existing workspaces in the
   468  source backend won't be modified. If you want to switch workspaces, back them
   469  up, or cancel altogether, answer "no" and Terraform will abort.
   470  `
   471  
   472  const inputBackendMigrateMultiToMulti = `
   473  Both the existing %[1]q backend and the newly configured %[2]q backend support
   474  workspaces. When migrating between backends, Terraform will copy all
   475  workspaces (with the same names). THIS WILL OVERWRITE any conflicting
   476  states in the destination.
   477  
   478  Terraform initialization doesn't currently migrate only select workspaces.
   479  If you want to migrate a select number of workspaces, you must manually
   480  pull and push those states.
   481  
   482  If you answer "yes", Terraform will migrate all states. If you answer
   483  "no", Terraform will abort.
   484  `