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