github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/command/meta_backend_migrate.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"log"
    10  	"os"
    11  	"path/filepath"
    12  	"sort"
    13  	"strings"
    14  
    15  	"github.com/muratcelep/terraform/not-internal/backend"
    16  	"github.com/muratcelep/terraform/not-internal/backend/remote"
    17  	"github.com/muratcelep/terraform/not-internal/cloud"
    18  	"github.com/muratcelep/terraform/not-internal/command/arguments"
    19  	"github.com/muratcelep/terraform/not-internal/command/clistate"
    20  	"github.com/muratcelep/terraform/not-internal/command/views"
    21  	"github.com/muratcelep/terraform/not-internal/states"
    22  	"github.com/muratcelep/terraform/not-internal/states/statemgr"
    23  	"github.com/muratcelep/terraform/not-internal/terraform"
    24  )
    25  
    26  type backendMigrateOpts struct {
    27  	SourceType, DestinationType string
    28  	Source, Destination         backend.Backend
    29  
    30  	// Fields below are set internally when migrate is called
    31  
    32  	sourceWorkspace      string
    33  	destinationWorkspace string
    34  	force                bool // if true, won't ask for confirmation
    35  }
    36  
    37  // backendMigrateState handles migrating (copying) state from one backend
    38  // to another. This function handles asking the user for confirmation
    39  // as well as the copy itself.
    40  //
    41  // This function can handle all scenarios of state migration regardless
    42  // of the existence of state in either backend.
    43  //
    44  // After migrating the state, the existing state in the first backend
    45  // remains untouched.
    46  //
    47  // This will attempt to lock both states for the migration.
    48  func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
    49  	log.Printf("[INFO] backendMigrateState: need to migrate from %q to %q backend config", opts.SourceType, opts.DestinationType)
    50  	// We need to check what the named state status is. If we're converting
    51  	// from multi-state to single-state for example, we need to handle that.
    52  	var sourceSingleState, destinationSingleState, sourceTFC, destinationTFC bool
    53  
    54  	_, sourceTFC = opts.Source.(*cloud.Cloud)
    55  	_, destinationTFC = opts.Destination.(*cloud.Cloud)
    56  
    57  	sourceWorkspaces, sourceSingleState, err := retrieveWorkspaces(opts.Source, opts.SourceType)
    58  	if err != nil {
    59  		return err
    60  	}
    61  	destinationWorkspaces, destinationSingleState, err := retrieveWorkspaces(opts.Destination, opts.SourceType)
    62  	if err != nil {
    63  		return err
    64  	}
    65  
    66  	// Set up defaults
    67  	opts.sourceWorkspace = backend.DefaultStateName
    68  	opts.destinationWorkspace = backend.DefaultStateName
    69  	opts.force = m.forceInitCopy
    70  
    71  	// Disregard remote Terraform version for the state source backend. If it's a
    72  	// Terraform Cloud remote backend, we don't care about the remote version,
    73  	// as we are migrating away and will not break a remote workspace.
    74  	m.ignoreRemoteVersionConflict(opts.Source)
    75  
    76  	// Disregard remote Terraform version if instructed to do so via CLI flag.
    77  	if m.ignoreRemoteVersion {
    78  		m.ignoreRemoteVersionConflict(opts.Destination)
    79  	} else {
    80  		// Check the remote Terraform version for the state destination backend. If
    81  		// it's a Terraform Cloud remote backend, we want to ensure that we don't
    82  		// break the workspace by uploading an incompatible state file.
    83  		for _, workspace := range destinationWorkspaces {
    84  			diags := m.remoteVersionCheck(opts.Destination, workspace)
    85  			if diags.HasErrors() {
    86  				return diags.Err()
    87  			}
    88  		}
    89  		// If there are no specified destination workspaces, perform a remote
    90  		// backend version check with the default workspace.
    91  		// Ensure that we are not dealing with Terraform Cloud migrations, as it
    92  		// does not support the default name.
    93  		if len(destinationWorkspaces) == 0 && !destinationTFC {
    94  			diags := m.remoteVersionCheck(opts.Destination, backend.DefaultStateName)
    95  			if diags.HasErrors() {
    96  				return diags.Err()
    97  			}
    98  		}
    99  	}
   100  
   101  	// Determine migration behavior based on whether the source/destination
   102  	// supports multi-state.
   103  	switch {
   104  	case sourceTFC || destinationTFC:
   105  		return m.backendMigrateTFC(opts)
   106  
   107  	// Single-state to single-state. This is the easiest case: we just
   108  	// copy the default state directly.
   109  	case sourceSingleState && destinationSingleState:
   110  		return m.backendMigrateState_s_s(opts)
   111  
   112  	// Single-state to multi-state. This is easy since we just copy
   113  	// the default state and ignore the rest in the destination.
   114  	case sourceSingleState && !destinationSingleState:
   115  		return m.backendMigrateState_s_s(opts)
   116  
   117  	// Multi-state to single-state. If the source has more than the default
   118  	// state this is complicated since we have to ask the user what to do.
   119  	case !sourceSingleState && destinationSingleState:
   120  		// If the source only has one state and it is the default,
   121  		// treat it as if it doesn't support multi-state.
   122  		if len(sourceWorkspaces) == 1 && sourceWorkspaces[0] == backend.DefaultStateName {
   123  			return m.backendMigrateState_s_s(opts)
   124  		}
   125  
   126  		return m.backendMigrateState_S_s(opts)
   127  
   128  	// Multi-state to multi-state. We merge the states together (migrating
   129  	// each from the source to the destination one by one).
   130  	case !sourceSingleState && !destinationSingleState:
   131  		// If the source only has one state and it is the default,
   132  		// treat it as if it doesn't support multi-state.
   133  		if len(sourceWorkspaces) == 1 && sourceWorkspaces[0] == backend.DefaultStateName {
   134  			return m.backendMigrateState_s_s(opts)
   135  		}
   136  
   137  		return m.backendMigrateState_S_S(opts)
   138  	}
   139  
   140  	return nil
   141  }
   142  
   143  //-------------------------------------------------------------------
   144  // State Migration Scenarios
   145  //
   146  // The functions below cover handling all the various scenarios that
   147  // can exist when migrating state. They are named in an immediately not
   148  // obvious format but is simple:
   149  //
   150  // Format: backendMigrateState_s1_s2[_suffix]
   151  //
   152  // When s1 or s2 is lower case, it means that it is a single state backend.
   153  // When either is uppercase, it means that state is a multi-state backend.
   154  // The suffix is used to disambiguate multiple cases with the same type of
   155  // states.
   156  //
   157  //-------------------------------------------------------------------
   158  
   159  // Multi-state to multi-state.
   160  func (m *Meta) backendMigrateState_S_S(opts *backendMigrateOpts) error {
   161  	log.Print("[INFO] backendMigrateState: migrating all named workspaces")
   162  
   163  	migrate := opts.force
   164  	if !migrate {
   165  		var err error
   166  		// Ask the user if they want to migrate their existing remote state
   167  		migrate, err = m.confirm(&terraform.InputOpts{
   168  			Id: "backend-migrate-multistate-to-multistate",
   169  			Query: fmt.Sprintf(
   170  				"Do you want to migrate all workspaces to %q?",
   171  				opts.DestinationType),
   172  			Description: fmt.Sprintf(
   173  				strings.TrimSpace(inputBackendMigrateMultiToMulti),
   174  				opts.SourceType, opts.DestinationType),
   175  		})
   176  		if err != nil {
   177  			return fmt.Errorf(
   178  				"Error asking for state migration action: %s", err)
   179  		}
   180  	}
   181  	if !migrate {
   182  		return fmt.Errorf("Migration aborted by user.")
   183  	}
   184  
   185  	// Read all the states
   186  	sourceWorkspaces, err := opts.Source.Workspaces()
   187  	if err != nil {
   188  		return fmt.Errorf(strings.TrimSpace(
   189  			errMigrateLoadStates), opts.SourceType, err)
   190  	}
   191  
   192  	// Sort the states so they're always copied alphabetically
   193  	sort.Strings(sourceWorkspaces)
   194  
   195  	// Go through each and migrate
   196  	for _, name := range sourceWorkspaces {
   197  		// Copy the same names
   198  		opts.sourceWorkspace = name
   199  		opts.destinationWorkspace = name
   200  
   201  		// Force it, we confirmed above
   202  		opts.force = true
   203  
   204  		// Perform the migration
   205  		if err := m.backendMigrateState_s_s(opts); err != nil {
   206  			return fmt.Errorf(strings.TrimSpace(
   207  				errMigrateMulti), name, opts.SourceType, opts.DestinationType, err)
   208  		}
   209  	}
   210  
   211  	return nil
   212  }
   213  
   214  // Multi-state to single state.
   215  func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
   216  	log.Printf("[INFO] backendMigrateState: destination backend type %q does not support named workspaces", opts.DestinationType)
   217  
   218  	currentWorkspace, err := m.Workspace()
   219  	if err != nil {
   220  		return err
   221  	}
   222  
   223  	migrate := opts.force
   224  	if !migrate {
   225  		var err error
   226  		// Ask the user if they want to migrate their existing remote state
   227  		migrate, err = m.confirm(&terraform.InputOpts{
   228  			Id: "backend-migrate-multistate-to-single",
   229  			Query: fmt.Sprintf(
   230  				"Destination state %q doesn't support workspaces.\n"+
   231  					"Do you want to copy only your current workspace?",
   232  				opts.DestinationType),
   233  			Description: fmt.Sprintf(
   234  				strings.TrimSpace(inputBackendMigrateMultiToSingle),
   235  				opts.SourceType, opts.DestinationType, currentWorkspace),
   236  		})
   237  		if err != nil {
   238  			return fmt.Errorf(
   239  				"Error asking for state migration action: %s", err)
   240  		}
   241  	}
   242  
   243  	if !migrate {
   244  		return fmt.Errorf("Migration aborted by user.")
   245  	}
   246  
   247  	// Copy the default state
   248  	opts.sourceWorkspace = currentWorkspace
   249  
   250  	// now switch back to the default env so we can acccess the new backend
   251  	m.SetWorkspace(backend.DefaultStateName)
   252  
   253  	return m.backendMigrateState_s_s(opts)
   254  }
   255  
   256  // Single state to single state, assumed default state name.
   257  func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
   258  	log.Printf("[INFO] backendMigrateState: single-to-single migrating %q workspace to %q workspace", opts.sourceWorkspace, opts.destinationWorkspace)
   259  
   260  	sourceState, err := opts.Source.StateMgr(opts.sourceWorkspace)
   261  	if err != nil {
   262  		return fmt.Errorf(strings.TrimSpace(
   263  			errMigrateSingleLoadDefault), opts.SourceType, err)
   264  	}
   265  	if err := sourceState.RefreshState(); err != nil {
   266  		return fmt.Errorf(strings.TrimSpace(
   267  			errMigrateSingleLoadDefault), opts.SourceType, err)
   268  	}
   269  
   270  	// Do not migrate workspaces without state.
   271  	if sourceState.State().Empty() {
   272  		log.Print("[TRACE] backendMigrateState: source workspace has empty state, so nothing to migrate")
   273  		return nil
   274  	}
   275  
   276  	destinationState, err := opts.Destination.StateMgr(opts.destinationWorkspace)
   277  	if err == backend.ErrDefaultWorkspaceNotSupported {
   278  		// If the backend doesn't support using the default state, we ask the user
   279  		// for a new name and migrate the default state to the given named state.
   280  		destinationState, err = func() (statemgr.Full, error) {
   281  			log.Print("[TRACE] backendMigrateState: destination doesn't support a default workspace, so we must prompt for a new name")
   282  			name, err := m.promptNewWorkspaceName(opts.DestinationType)
   283  			if err != nil {
   284  				return nil, err
   285  			}
   286  
   287  			// Update the name of the destination state.
   288  			opts.destinationWorkspace = name
   289  
   290  			destinationState, err := opts.Destination.StateMgr(opts.destinationWorkspace)
   291  			if err != nil {
   292  				return nil, err
   293  			}
   294  
   295  			// Ignore invalid workspace name as it is irrelevant in this context.
   296  			workspace, _ := m.Workspace()
   297  
   298  			// If the currently selected workspace is the default workspace, then set
   299  			// the named workspace as the new selected workspace.
   300  			if workspace == backend.DefaultStateName {
   301  				if err := m.SetWorkspace(opts.destinationWorkspace); err != nil {
   302  					return nil, fmt.Errorf("Failed to set new workspace: %s", err)
   303  				}
   304  			}
   305  
   306  			return destinationState, nil
   307  		}()
   308  	}
   309  	if err != nil {
   310  		return fmt.Errorf(strings.TrimSpace(
   311  			errMigrateSingleLoadDefault), opts.DestinationType, err)
   312  	}
   313  	if err := destinationState.RefreshState(); err != nil {
   314  		return fmt.Errorf(strings.TrimSpace(
   315  			errMigrateSingleLoadDefault), opts.DestinationType, err)
   316  	}
   317  
   318  	// Check if we need migration at all.
   319  	// This is before taking a lock, because they may also correspond to the same lock.
   320  	source := sourceState.State()
   321  	destination := destinationState.State()
   322  
   323  	// no reason to migrate if the state is already there
   324  	if source.Equal(destination) {
   325  		// Equal isn't identical; it doesn't check lineage.
   326  		sm1, _ := sourceState.(statemgr.PersistentMeta)
   327  		sm2, _ := destinationState.(statemgr.PersistentMeta)
   328  		if source != nil && destination != nil {
   329  			if sm1 == nil || sm2 == nil {
   330  				log.Print("[TRACE] backendMigrateState: both source and destination workspaces have no state, so no migration is needed")
   331  				return nil
   332  			}
   333  			if sm1.StateSnapshotMeta().Lineage == sm2.StateSnapshotMeta().Lineage {
   334  				log.Printf("[TRACE] backendMigrateState: both source and destination workspaces have equal state with lineage %q, so no migration is needed", sm1.StateSnapshotMeta().Lineage)
   335  				return nil
   336  			}
   337  		}
   338  	}
   339  
   340  	if m.stateLock {
   341  		lockCtx := context.Background()
   342  
   343  		view := views.NewStateLocker(arguments.ViewHuman, m.View)
   344  		locker := clistate.NewLocker(m.stateLockTimeout, view)
   345  
   346  		lockerSource := locker.WithContext(lockCtx)
   347  		if diags := lockerSource.Lock(sourceState, "migration source state"); diags.HasErrors() {
   348  			return diags.Err()
   349  		}
   350  		defer lockerSource.Unlock()
   351  
   352  		lockerDestination := locker.WithContext(lockCtx)
   353  		if diags := lockerDestination.Lock(destinationState, "migration destination state"); diags.HasErrors() {
   354  			return diags.Err()
   355  		}
   356  		defer lockerDestination.Unlock()
   357  
   358  		// We now own a lock, so double check that we have the version
   359  		// corresponding to the lock.
   360  		log.Print("[TRACE] backendMigrateState: refreshing source workspace state")
   361  		if err := sourceState.RefreshState(); err != nil {
   362  			return fmt.Errorf(strings.TrimSpace(
   363  				errMigrateSingleLoadDefault), opts.SourceType, err)
   364  		}
   365  		log.Print("[TRACE] backendMigrateState: refreshing destination workspace state")
   366  		if err := destinationState.RefreshState(); err != nil {
   367  			return fmt.Errorf(strings.TrimSpace(
   368  				errMigrateSingleLoadDefault), opts.SourceType, err)
   369  		}
   370  
   371  		source = sourceState.State()
   372  		destination = destinationState.State()
   373  	}
   374  
   375  	var confirmFunc func(statemgr.Full, statemgr.Full, *backendMigrateOpts) (bool, error)
   376  	switch {
   377  	// No migration necessary
   378  	case source.Empty() && destination.Empty():
   379  		log.Print("[TRACE] backendMigrateState: both source and destination workspaces have empty state, so no migration is required")
   380  		return nil
   381  
   382  	// No migration necessary if we're inheriting state.
   383  	case source.Empty() && !destination.Empty():
   384  		log.Print("[TRACE] backendMigrateState: source workspace has empty state, so no migration is required")
   385  		return nil
   386  
   387  	// We have existing state moving into no state. Ask the user if
   388  	// they'd like to do this.
   389  	case !source.Empty() && destination.Empty():
   390  		if opts.SourceType == "cloud" || opts.DestinationType == "cloud" {
   391  			// HACK: backendMigrateTFC has its own earlier prompt for
   392  			// whether to migrate state in the cloud case, so we'll skip
   393  			// this later prompt for Cloud, even though we do still need it
   394  			// for state backends.
   395  			confirmFunc = func(statemgr.Full, statemgr.Full, *backendMigrateOpts) (bool, error) {
   396  				return true, nil // the answer is implied to be "yes" if we reached this point
   397  			}
   398  		} else {
   399  			log.Print("[TRACE] backendMigrateState: destination workspace has empty state, so might copy source workspace state")
   400  			confirmFunc = m.backendMigrateEmptyConfirm
   401  		}
   402  
   403  	// Both states are non-empty, meaning we need to determine which
   404  	// state should be used and update accordingly.
   405  	case !source.Empty() && !destination.Empty():
   406  		log.Print("[TRACE] backendMigrateState: both source and destination workspaces have states, so might overwrite destination with source")
   407  		confirmFunc = m.backendMigrateNonEmptyConfirm
   408  	}
   409  
   410  	if confirmFunc == nil {
   411  		panic("confirmFunc must not be nil")
   412  	}
   413  
   414  	if !opts.force {
   415  		// Abort if we can't ask for input.
   416  		if !m.input {
   417  			log.Print("[TRACE] backendMigrateState: can't prompt for input, so aborting migration")
   418  			return errors.New(strings.TrimSpace(errInteractiveInputDisabled))
   419  		}
   420  
   421  		// Confirm with the user whether we want to copy state over
   422  		confirm, err := confirmFunc(sourceState, destinationState, opts)
   423  		if err != nil {
   424  			log.Print("[TRACE] backendMigrateState: error reading input, so aborting migration")
   425  			return err
   426  		}
   427  		if !confirm {
   428  			log.Print("[TRACE] backendMigrateState: user cancelled at confirmation prompt, so aborting migration")
   429  			return nil
   430  		}
   431  	}
   432  
   433  	// Confirmed! We'll have the statemgr package handle the migration, which
   434  	// includes preserving any lineage/serial information where possible, if
   435  	// both managers support such metadata.
   436  	log.Print("[TRACE] backendMigrateState: migration confirmed, so migrating")
   437  	if err := statemgr.Migrate(destinationState, sourceState); err != nil {
   438  		return fmt.Errorf(strings.TrimSpace(errBackendStateCopy),
   439  			opts.SourceType, opts.DestinationType, err)
   440  	}
   441  	if err := destinationState.PersistState(); err != nil {
   442  		return fmt.Errorf(strings.TrimSpace(errBackendStateCopy),
   443  			opts.SourceType, opts.DestinationType, err)
   444  	}
   445  
   446  	// And we're done.
   447  	return nil
   448  }
   449  
   450  func (m *Meta) backendMigrateEmptyConfirm(source, destination statemgr.Full, opts *backendMigrateOpts) (bool, error) {
   451  	var inputOpts *terraform.InputOpts
   452  	if opts.DestinationType == "cloud" {
   453  		inputOpts = &terraform.InputOpts{
   454  			Id:          "backend-migrate-copy-to-empty-cloud",
   455  			Query:       "Do you want to copy existing state to Terraform Cloud?",
   456  			Description: fmt.Sprintf(strings.TrimSpace(inputBackendMigrateEmptyCloud), opts.SourceType),
   457  		}
   458  	} else {
   459  		inputOpts = &terraform.InputOpts{
   460  			Id:    "backend-migrate-copy-to-empty",
   461  			Query: "Do you want to copy existing state to the new backend?",
   462  			Description: fmt.Sprintf(
   463  				strings.TrimSpace(inputBackendMigrateEmpty),
   464  				opts.SourceType, opts.DestinationType),
   465  		}
   466  	}
   467  
   468  	return m.confirm(inputOpts)
   469  }
   470  
   471  func (m *Meta) backendMigrateNonEmptyConfirm(
   472  	sourceState, destinationState statemgr.Full, opts *backendMigrateOpts) (bool, error) {
   473  	// We need to grab both states so we can write them to a file
   474  	source := sourceState.State()
   475  	destination := destinationState.State()
   476  
   477  	// Save both to a temporary
   478  	td, err := ioutil.TempDir("", "terraform")
   479  	if err != nil {
   480  		return false, fmt.Errorf("Error creating temporary directory: %s", err)
   481  	}
   482  	defer os.RemoveAll(td)
   483  
   484  	// Helper to write the state
   485  	saveHelper := func(n, path string, s *states.State) error {
   486  		mgr := statemgr.NewFilesystem(path)
   487  		return mgr.WriteState(s)
   488  	}
   489  
   490  	// Write the states
   491  	sourcePath := filepath.Join(td, fmt.Sprintf("1-%s.tfstate", opts.SourceType))
   492  	destinationPath := filepath.Join(td, fmt.Sprintf("2-%s.tfstate", opts.DestinationType))
   493  	if err := saveHelper(opts.SourceType, sourcePath, source); err != nil {
   494  		return false, fmt.Errorf("Error saving temporary state: %s", err)
   495  	}
   496  	if err := saveHelper(opts.DestinationType, destinationPath, destination); err != nil {
   497  		return false, fmt.Errorf("Error saving temporary state: %s", err)
   498  	}
   499  
   500  	// Ask for confirmation
   501  	var inputOpts *terraform.InputOpts
   502  	if opts.DestinationType == "cloud" {
   503  		inputOpts = &terraform.InputOpts{
   504  			Id:    "backend-migrate-to-tfc",
   505  			Query: "Do you want to copy existing state to Terraform Cloud?",
   506  			Description: fmt.Sprintf(
   507  				strings.TrimSpace(inputBackendMigrateNonEmptyCloud),
   508  				opts.SourceType, sourcePath, destinationPath),
   509  		}
   510  	} else {
   511  		inputOpts = &terraform.InputOpts{
   512  			Id:    "backend-migrate-to-backend",
   513  			Query: "Do you want to copy existing state to the new backend?",
   514  			Description: fmt.Sprintf(
   515  				strings.TrimSpace(inputBackendMigrateNonEmpty),
   516  				opts.SourceType, opts.DestinationType, sourcePath, destinationPath),
   517  		}
   518  	}
   519  
   520  	// Confirm with the user that the copy should occur
   521  	return m.confirm(inputOpts)
   522  }
   523  
   524  func retrieveWorkspaces(back backend.Backend, sourceType string) ([]string, bool, error) {
   525  	var singleState bool
   526  	var err error
   527  	workspaces, err := back.Workspaces()
   528  	if err == backend.ErrWorkspacesNotSupported {
   529  		singleState = true
   530  		err = nil
   531  	}
   532  	if err != nil {
   533  		return nil, singleState, fmt.Errorf(strings.TrimSpace(
   534  			errMigrateLoadStates), sourceType, err)
   535  	}
   536  
   537  	return workspaces, singleState, err
   538  }
   539  
   540  func (m *Meta) backendMigrateTFC(opts *backendMigrateOpts) error {
   541  	_, sourceTFC := opts.Source.(*cloud.Cloud)
   542  	cloudBackendDestination, destinationTFC := opts.Destination.(*cloud.Cloud)
   543  
   544  	sourceWorkspaces, sourceSingleState, err := retrieveWorkspaces(opts.Source, opts.SourceType)
   545  	if err != nil {
   546  		return err
   547  	}
   548  	//to be used below, not yet implamented
   549  	// destinationWorkspaces, destinationSingleState
   550  	_, _, err = retrieveWorkspaces(opts.Destination, opts.SourceType)
   551  	if err != nil {
   552  		return err
   553  	}
   554  
   555  	// from TFC to non-TFC backend
   556  	if sourceTFC && !destinationTFC {
   557  		// From Terraform Cloud to another backend. This is not yet implemented, and
   558  		// we recommend people to use the TFC API.
   559  		return fmt.Errorf(strings.TrimSpace(errTFCMigrateNotYetImplemented))
   560  	}
   561  
   562  	// Everything below, by the above two conditionals, now assumes that the
   563  	// destination is always Terraform Cloud (TFC).
   564  
   565  	sourceSingle := sourceSingleState || (len(sourceWorkspaces) == 1)
   566  	if sourceSingle {
   567  		if cloudBackendDestination.WorkspaceMapping.Strategy() == cloud.WorkspaceNameStrategy {
   568  			// If we know the name via WorkspaceNameStrategy, then set the
   569  			// destinationWorkspace to the new Name and skip the user prompt. Here the
   570  			// destinationWorkspace is not set to `default` thereby we will create it
   571  			// in TFC if it does not exist.
   572  			opts.destinationWorkspace = cloudBackendDestination.WorkspaceMapping.Name
   573  		}
   574  
   575  		currentWorkspace, err := m.Workspace()
   576  		if err != nil {
   577  			return err
   578  		}
   579  		opts.sourceWorkspace = currentWorkspace
   580  
   581  		log.Printf("[INFO] backendMigrateTFC: single-to-single migration from source %s to destination %q", opts.sourceWorkspace, opts.destinationWorkspace)
   582  		// Run normal single-to-single state migration.
   583  		// This will handle both situations where the new cloud backend
   584  		// configuration is using a workspace.name strategy or workspace.tags
   585  		// strategy.
   586  		//
   587  		// We do prompt first though, because state migration is mandatory
   588  		// for moving to Cloud and the user should get an opportunity to
   589  		// confirm that first.
   590  		if migrate, err := m.promptSingleToCloudSingleStateMigration(opts); err != nil {
   591  			return err
   592  		} else if !migrate {
   593  			return nil //skip migrating but return successfully
   594  		}
   595  
   596  		return m.backendMigrateState_s_s(opts)
   597  	}
   598  
   599  	destinationTagsStrategy := cloudBackendDestination.WorkspaceMapping.Strategy() == cloud.WorkspaceTagsStrategy
   600  	destinationNameStrategy := cloudBackendDestination.WorkspaceMapping.Strategy() == cloud.WorkspaceNameStrategy
   601  
   602  	multiSource := !sourceSingleState && len(sourceWorkspaces) > 1
   603  	if multiSource && destinationNameStrategy {
   604  		currentWorkspace, err := m.Workspace()
   605  		if err != nil {
   606  			return err
   607  		}
   608  
   609  		opts.sourceWorkspace = currentWorkspace
   610  		opts.destinationWorkspace = cloudBackendDestination.WorkspaceMapping.Name
   611  		if err := m.promptMultiToSingleCloudMigration(opts); err != nil {
   612  			return err
   613  		}
   614  
   615  		log.Printf("[INFO] backendMigrateTFC: multi-to-single migration from source %s to destination %q", opts.sourceWorkspace, opts.destinationWorkspace)
   616  
   617  		return m.backendMigrateState_s_s(opts)
   618  	}
   619  
   620  	// Multiple sources, and using tags strategy. So migrate every source
   621  	// workspace over to new one, prompt for workspace name pattern (*),
   622  	// and start migrating, and create tags for each workspace.
   623  	if multiSource && destinationTagsStrategy {
   624  		log.Printf("[INFO] backendMigrateTFC: multi-to-multi migration from source workspaces %q", sourceWorkspaces)
   625  		return m.backendMigrateState_S_TFC(opts, sourceWorkspaces)
   626  	}
   627  
   628  	// TODO(omar): after the check for sourceSingle is done, everything following
   629  	// it has to be multi. So rework the code to not need to check for multi, adn
   630  	// return m.backendMigrateState_S_TFC here.
   631  	return nil
   632  }
   633  
   634  // migrates a multi-state backend to Terraform Cloud
   635  func (m *Meta) backendMigrateState_S_TFC(opts *backendMigrateOpts, sourceWorkspaces []string) error {
   636  	log.Print("[TRACE] backendMigrateState: migrating all named workspaces")
   637  
   638  	currentWorkspace, err := m.Workspace()
   639  	if err != nil {
   640  		return err
   641  	}
   642  	newCurrentWorkspace := ""
   643  
   644  	// This map is used later when doing the migration per source/destination.
   645  	// If a source has 'default' and has state, then we ask what the new name should be.
   646  	// And further down when we actually run state migration for each
   647  	// source/destination workspace, we use this new name (where source is 'default')
   648  	// and set as destinationWorkspace. If the default workspace does not have
   649  	// state we will not prompt the user for a new name because empty workspaces
   650  	// do not get migrated.
   651  	defaultNewName := map[string]string{}
   652  	for i := 0; i < len(sourceWorkspaces); i++ {
   653  		if sourceWorkspaces[i] == backend.DefaultStateName {
   654  			// For the default workspace we want to look to see if there is any state
   655  			// before we ask for a workspace name to migrate the default workspace into.
   656  			sourceState, err := opts.Source.StateMgr(backend.DefaultStateName)
   657  			if err != nil {
   658  				return fmt.Errorf(strings.TrimSpace(
   659  					errMigrateSingleLoadDefault), opts.SourceType, err)
   660  			}
   661  			// RefreshState is what actually pulls the state to be evaluated.
   662  			if err := sourceState.RefreshState(); err != nil {
   663  				return fmt.Errorf(strings.TrimSpace(
   664  					errMigrateSingleLoadDefault), opts.SourceType, err)
   665  			}
   666  			if !sourceState.State().Empty() {
   667  				newName, err := m.promptNewWorkspaceName(opts.DestinationType)
   668  				if err != nil {
   669  					return err
   670  				}
   671  				defaultNewName[sourceWorkspaces[i]] = newName
   672  			}
   673  		}
   674  	}
   675  
   676  	// Fetch the pattern that will be used to rename the workspaces for Terraform Cloud.
   677  	//
   678  	// * For the general case, this will be a pattern provided by the user.
   679  	//
   680  	// * Specifically for a migration from the "remote" backend using 'prefix', we will
   681  	//   instead 'migrate' the workspaces using a pattern based on the old prefix+name,
   682  	//   not allowing a user to accidentally input the wrong pattern to line up with
   683  	//   what the the remote backend was already using before (which presumably already
   684  	//   meets the naming considerations for Terraform Cloud).
   685  	//   In other words, this is a fast-track migration path from the remote backend, retaining
   686  	//   how things already are in Terraform Cloud with no user intervention needed.
   687  	pattern := ""
   688  	if remoteBackend, ok := opts.Source.(*remote.Remote); ok {
   689  		if err := m.promptRemotePrefixToCloudTagsMigration(opts); err != nil {
   690  			return err
   691  		}
   692  		pattern = remoteBackend.WorkspaceNamePattern()
   693  		log.Printf("[TRACE] backendMigrateTFC: Remote backend reports workspace name pattern as: %q", pattern)
   694  	}
   695  
   696  	if pattern == "" {
   697  		pattern, err = m.promptMultiStateMigrationPattern(opts.SourceType)
   698  		if err != nil {
   699  			return err
   700  		}
   701  	}
   702  
   703  	// Go through each and migrate
   704  	for _, name := range sourceWorkspaces {
   705  
   706  		// Copy the same names
   707  		opts.sourceWorkspace = name
   708  		if newName, ok := defaultNewName[name]; ok {
   709  			// this has to be done before setting destinationWorkspace
   710  			name = newName
   711  		}
   712  		opts.destinationWorkspace = strings.Replace(pattern, "*", name, -1)
   713  
   714  		// Force it, we confirmed above
   715  		opts.force = true
   716  
   717  		// Perform the migration
   718  		log.Printf("[INFO] backendMigrateTFC: multi-to-multi migration, source workspace %q to destination workspace %q", opts.sourceWorkspace, opts.destinationWorkspace)
   719  		if err := m.backendMigrateState_s_s(opts); err != nil {
   720  			return fmt.Errorf(strings.TrimSpace(
   721  				errMigrateMulti), name, opts.SourceType, opts.DestinationType, err)
   722  		}
   723  
   724  		if currentWorkspace == opts.sourceWorkspace {
   725  			newCurrentWorkspace = opts.destinationWorkspace
   726  		}
   727  	}
   728  
   729  	// After migrating multiple workspaces, we need to reselect the current workspace as it may
   730  	// have been renamed. Query the backend first to be sure it now exists.
   731  	workspaces, err := opts.Destination.Workspaces()
   732  	if err != nil {
   733  		return err
   734  	}
   735  
   736  	var workspacePresent bool
   737  	for _, name := range workspaces {
   738  		if name == newCurrentWorkspace {
   739  			workspacePresent = true
   740  		}
   741  	}
   742  
   743  	// If we couldn't select the workspace automatically from the backend (maybe it was empty
   744  	// and wasn't migrated, for instance), ask the user to select one instead and be done.
   745  	if !workspacePresent {
   746  		if err = m.selectWorkspace(opts.Destination); err != nil {
   747  			return err
   748  		}
   749  		return nil
   750  	}
   751  
   752  	// The newly renamed current workspace is present, so we'll automatically select it for the
   753  	// user, as well as display the equivalent of 'workspace list' to show how the workspaces
   754  	// were changed (as well as the newly selected current workspace).
   755  	if err = m.SetWorkspace(newCurrentWorkspace); err != nil {
   756  		return err
   757  	}
   758  
   759  	m.Ui.Output(m.Colorize().Color("[reset][bold]Migration complete! Your workspaces are as follows:[reset]"))
   760  	var out bytes.Buffer
   761  	for _, name := range workspaces {
   762  		if name == newCurrentWorkspace {
   763  			out.WriteString("* ")
   764  		} else {
   765  			out.WriteString("  ")
   766  		}
   767  		out.WriteString(name + "\n")
   768  	}
   769  
   770  	m.Ui.Output(out.String())
   771  
   772  	return nil
   773  }
   774  
   775  func (m *Meta) promptSingleToCloudSingleStateMigration(opts *backendMigrateOpts) (bool, error) {
   776  	if !m.input {
   777  		log.Print("[TRACE] backendMigrateState: can't prompt for input, so aborting migration")
   778  		return false, errors.New(strings.TrimSpace(errInteractiveInputDisabled))
   779  	}
   780  	migrate := opts.force
   781  	if !migrate {
   782  		var err error
   783  		migrate, err = m.confirm(&terraform.InputOpts{
   784  			Id:          "backend-migrate-state-single-to-cloud-single",
   785  			Query:       "Do you wish to proceed?",
   786  			Description: strings.TrimSpace(tfcInputBackendMigrateStateSingleToCloudSingle),
   787  		})
   788  		if err != nil {
   789  			return false, fmt.Errorf("Error asking for state migration action: %s", err)
   790  		}
   791  	}
   792  
   793  	return migrate, nil
   794  }
   795  
   796  func (m *Meta) promptRemotePrefixToCloudTagsMigration(opts *backendMigrateOpts) error {
   797  	if !m.input {
   798  		log.Print("[TRACE] backendMigrateState: can't prompt for input, so aborting migration")
   799  		return errors.New(strings.TrimSpace(errInteractiveInputDisabled))
   800  	}
   801  	migrate := opts.force
   802  	if !migrate {
   803  		var err error
   804  		migrate, err = m.confirm(&terraform.InputOpts{
   805  			Id:          "backend-migrate-remote-multistate-to-cloud",
   806  			Query:       "Do you wish to proceed?",
   807  			Description: strings.TrimSpace(tfcInputBackendMigrateRemoteMultiToCloud),
   808  		})
   809  		if err != nil {
   810  			return fmt.Errorf("Error asking for state migration action: %s", err)
   811  		}
   812  	}
   813  
   814  	if !migrate {
   815  		return fmt.Errorf("Migration aborted by user.")
   816  	}
   817  
   818  	return nil
   819  }
   820  
   821  // Multi-state to single state.
   822  func (m *Meta) promptMultiToSingleCloudMigration(opts *backendMigrateOpts) error {
   823  	if !m.input {
   824  		log.Print("[TRACE] backendMigrateState: can't prompt for input, so aborting migration")
   825  		return errors.New(strings.TrimSpace(errInteractiveInputDisabled))
   826  	}
   827  	migrate := opts.force
   828  	if !migrate {
   829  		var err error
   830  		// Ask the user if they want to migrate their existing remote state
   831  		migrate, err = m.confirm(&terraform.InputOpts{
   832  			Id:    "backend-migrate-multistate-to-single",
   833  			Query: "Do you want to copy only your current workspace?",
   834  			Description: fmt.Sprintf(
   835  				strings.TrimSpace(tfcInputBackendMigrateMultiToSingle),
   836  				opts.SourceType, opts.destinationWorkspace),
   837  		})
   838  		if err != nil {
   839  			return fmt.Errorf("Error asking for state migration action: %s", err)
   840  		}
   841  	}
   842  
   843  	if !migrate {
   844  		return fmt.Errorf("Migration aborted by user.")
   845  	}
   846  
   847  	return nil
   848  }
   849  
   850  func (m *Meta) promptNewWorkspaceName(destinationType string) (string, error) {
   851  	message := fmt.Sprintf("[reset][bold][yellow]The %q backend configuration only allows "+
   852  		"named workspaces![reset]", destinationType)
   853  	if destinationType == "cloud" {
   854  		if !m.input {
   855  			log.Print("[TRACE] backendMigrateState: can't prompt for input, so aborting migration")
   856  			return "", errors.New(strings.TrimSpace(errInteractiveInputDisabled))
   857  		}
   858  		message = `[reset][bold][yellow]Terraform Cloud requires all workspaces to be given an explicit name.[reset]`
   859  	}
   860  	name, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{
   861  		Id:          "new-state-name",
   862  		Query:       message,
   863  		Description: strings.TrimSpace(inputBackendNewWorkspaceName),
   864  	})
   865  	if err != nil {
   866  		return "", fmt.Errorf("Error asking for new state name: %s", err)
   867  	}
   868  
   869  	return name, nil
   870  }
   871  
   872  func (m *Meta) promptMultiStateMigrationPattern(sourceType string) (string, error) {
   873  	// This is not the first prompt a user would be presented with in the migration to TFC, so no
   874  	// guard on m.input is needed here.
   875  	renameWorkspaces, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{
   876  		Id:          "backend-migrate-multistate-to-tfc",
   877  		Query:       fmt.Sprintf("[reset][bold][yellow]%s[reset]", "Would you like to rename your workspaces?"),
   878  		Description: fmt.Sprintf(strings.TrimSpace(tfcInputBackendMigrateMultiToMulti), sourceType),
   879  	})
   880  	if err != nil {
   881  		return "", fmt.Errorf("Error asking for state migration action: %s", err)
   882  	}
   883  	if renameWorkspaces != "2" && renameWorkspaces != "1" {
   884  		return "", fmt.Errorf("Please select 1 or 2 as part of this option.")
   885  	}
   886  	if renameWorkspaces == "2" {
   887  		// this means they did not want to rename their workspaces, and we are
   888  		// returning a generic '*' that means use the same workspace name during
   889  		// migration.
   890  		return "*", nil
   891  	}
   892  
   893  	pattern, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{
   894  		Id:          "backend-migrate-multistate-to-tfc-pattern",
   895  		Query:       fmt.Sprintf("[reset][bold][yellow]%s[reset]", "How would you like to rename your workspaces?"),
   896  		Description: strings.TrimSpace(tfcInputBackendMigrateMultiToMultiPattern),
   897  	})
   898  	if err != nil {
   899  		return "", fmt.Errorf("Error asking for state migration action: %s", err)
   900  	}
   901  	if !strings.Contains(pattern, "*") {
   902  		return "", fmt.Errorf("The pattern must have an '*'")
   903  	}
   904  
   905  	if count := strings.Count(pattern, "*"); count > 1 {
   906  		return "", fmt.Errorf("The pattern '*' cannot be used more than once.")
   907  	}
   908  
   909  	return pattern, nil
   910  }
   911  
   912  const errMigrateLoadStates = `
   913  Error inspecting states in the %q backend:
   914      %s
   915  
   916  Prior to changing backends, Terraform inspects the source and destination
   917  states to determine what kind of migration steps need to be taken, if any.
   918  Terraform failed to load the states. The data in both the source and the
   919  destination remain unmodified. Please resolve the above error and try again.
   920  `
   921  
   922  const errMigrateSingleLoadDefault = `
   923  Error loading state:
   924      %[2]s
   925  
   926  Terraform failed to load the default state from the %[1]q backend.
   927  State migration cannot occur unless the state can be loaded. Backend
   928  modification and state migration has been aborted. The state in both the
   929  source and the destination remain unmodified. Please resolve the
   930  above error and try again.
   931  `
   932  
   933  const errMigrateMulti = `
   934  Error migrating the workspace %q from the previous %q backend
   935  to the newly configured %q backend:
   936      %s
   937  
   938  Terraform copies workspaces in alphabetical order. Any workspaces
   939  alphabetically earlier than this one have been copied. Any workspaces
   940  later than this haven't been modified in the destination. No workspaces
   941  in the source state have been modified.
   942  
   943  Please resolve the error above and run the initialization command again.
   944  This will attempt to copy (with permission) all workspaces again.
   945  `
   946  
   947  const errBackendStateCopy = `
   948  Error copying state from the previous %q backend to the newly configured 
   949  %q backend:
   950      %s
   951  
   952  The state in the previous backend remains intact and unmodified. Please resolve
   953  the error above and try again.
   954  `
   955  
   956  const errTFCMigrateNotYetImplemented = `
   957  Migrating state from Terraform Cloud to another backend is not yet implemented.
   958  
   959  Please use the API to do this: https://www.terraform.io/docs/cloud/api/state-versions.html
   960  `
   961  
   962  const errInteractiveInputDisabled = `
   963  Can't ask approval for state migration when interactive input is disabled.
   964  
   965  Please remove the "-input=false" option and try again.
   966  `
   967  
   968  const tfcInputBackendMigrateMultiToMultiPattern = `
   969  Enter a pattern with an asterisk (*) to rename all workspaces based on their
   970  previous names. The asterisk represents the current workspace name.
   971  
   972  For example, if a workspace is currently named 'prod', the pattern 'app-*' would yield
   973  'app-prod' for a new workspace name; 'app-*-region1' would  yield 'app-prod-region1'.
   974  `
   975  
   976  const tfcInputBackendMigrateMultiToMulti = `
   977  Unlike typical Terraform workspaces representing an environment associated with a particular
   978  configuration (e.g. production, staging, development), Terraform Cloud workspaces are named uniquely
   979  across all configurations used within an organization. A typical strategy to start with is
   980  <COMPONENT>-<ENVIRONMENT>-<REGION> (e.g. networking-prod-us-east, networking-staging-us-east).
   981  
   982  For more information on workspace naming, see https://www.terraform.io/docs/cloud/workspaces/naming.html
   983  
   984  When migrating existing workspaces from the backend %[1]q to Terraform Cloud, would you like to
   985  rename your workspaces? Enter 1 or 2.
   986  
   987  1. Yes, I'd like to rename all workspaces according to a pattern I will provide.
   988  2. No, I would not like to rename my workspaces. Migrate them as currently named.
   989  `
   990  
   991  const tfcInputBackendMigrateMultiToSingle = `
   992  The previous backend %[1]q has multiple workspaces, but Terraform Cloud has
   993  been configured to use a single workspace (%[2]q). By continuing, you will
   994  only migrate your current workspace. If you wish to migrate all workspaces
   995  from the previous backend, you may cancel this operation and use the 'tags'
   996  strategy in your workspace configuration block instead.
   997  
   998  Enter "yes" to proceed or "no" to cancel.
   999  `
  1000  
  1001  const tfcInputBackendMigrateStateSingleToCloudSingle = `
  1002  As part of migrating to Terraform Cloud, Terraform can optionally copy your
  1003  current workspace state to the configured Terraform Cloud workspace.
  1004  
  1005  Answer "yes" to copy the latest state snapshot to the configured
  1006  Terraform Cloud workspace.
  1007  
  1008  Answer "no" to ignore the existing state and just activate the configured
  1009  Terraform Cloud workspace with its existing state, if any.
  1010  
  1011  Should Terraform migrate your existing state?
  1012  `
  1013  
  1014  const tfcInputBackendMigrateRemoteMultiToCloud = `
  1015  When migrating from the 'remote' backend to Terraform's native integration
  1016  with Terraform Cloud, Terraform will automatically create or use existing
  1017  workspaces based on the previous backend configuration's 'prefix' value.
  1018  
  1019  When the migration is complete, workspace names in Terraform will match the
  1020  fully qualified Terraform Cloud workspace name. If necessary, the workspace
  1021  tags configured in the 'cloud' option block will be added to the associated
  1022  Terraform Cloud workspaces.
  1023  
  1024  Enter "yes" to proceed or "no" to cancel.
  1025  `
  1026  
  1027  const inputBackendMigrateEmpty = `
  1028  Pre-existing state was found while migrating the previous %q backend to the
  1029  newly configured %q backend. No existing state was found in the newly
  1030  configured %[2]q backend. Do you want to copy this state to the new %[2]q
  1031  backend? Enter "yes" to copy and "no" to start with an empty state.
  1032  `
  1033  
  1034  const inputBackendMigrateEmptyCloud = `
  1035  Pre-existing state was found while migrating the previous %q backend to Terraform Cloud.
  1036  No existing state was found in Terraform Cloud. Do you want to copy this state to Terraform Cloud?
  1037  Enter "yes" to copy and "no" to start with an empty state.
  1038  `
  1039  
  1040  const inputBackendMigrateNonEmpty = `
  1041  Pre-existing state was found while migrating the previous %q backend to the
  1042  newly configured %q backend. An existing non-empty state already exists in
  1043  the new backend. The two states have been saved to temporary files that will be
  1044  removed after responding to this query.
  1045  
  1046  Previous (type %[1]q): %[3]s
  1047  New      (type %[2]q): %[4]s
  1048  
  1049  Do you want to overwrite the state in the new backend with the previous state?
  1050  Enter "yes" to copy and "no" to start with the existing state in the newly
  1051  configured %[2]q backend.
  1052  `
  1053  
  1054  const inputBackendMigrateNonEmptyCloud = `
  1055  Pre-existing state was found while migrating the previous %q backend to
  1056  Terraform Cloud. An existing non-empty state already exists in Terraform Cloud.
  1057  The two states have been saved to temporary files that will be removed after
  1058  responding to this query.
  1059  
  1060  Previous (type %[1]q): %[2]s
  1061  New      (Terraform Cloud): %[3]s
  1062  
  1063  Do you want to overwrite the state in Terraform Cloud with the previous state?
  1064  Enter "yes" to copy and "no" to start with the existing state in Terraform Cloud.
  1065  `
  1066  
  1067  const inputBackendMigrateMultiToSingle = `
  1068  The existing %[1]q backend supports workspaces and you currently are
  1069  using more than one. The newly configured %[2]q backend doesn't support
  1070  workspaces. If you continue, Terraform will copy your current workspace %[3]q
  1071  to the default workspace in the new backend. Your existing workspaces in the
  1072  source backend won't be modified. If you want to switch workspaces, back them
  1073  up, or cancel altogether, answer "no" and Terraform will abort.
  1074  `
  1075  
  1076  const inputBackendMigrateMultiToMulti = `
  1077  Both the existing %[1]q backend and the newly configured %[2]q backend
  1078  support workspaces. When migrating between backends, Terraform will copy
  1079  all workspaces (with the same names). THIS WILL OVERWRITE any conflicting
  1080  states in the destination.
  1081  
  1082  Terraform initialization doesn't currently migrate only select workspaces.
  1083  If you want to migrate a select number of workspaces, you must manually
  1084  pull and push those states.
  1085  
  1086  If you answer "yes", Terraform will migrate all states. If you answer
  1087  "no", Terraform will abort.
  1088  `
  1089  
  1090  const inputBackendNewWorkspaceName = `
  1091  Please provide a new workspace name (e.g. dev, test) that will be used
  1092  to migrate the existing default workspace.
  1093  `
  1094  
  1095  const inputBackendSelectWorkspace = `
  1096  This is expected behavior when the selected workspace did not have an
  1097  existing non-empty state. Please enter a number to select a workspace:
  1098  
  1099  %s
  1100  `