github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/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/cycloidio/terraform/backend"
    16  	"github.com/cycloidio/terraform/backend/remote"
    17  	"github.com/cycloidio/terraform/cloud"
    18  	"github.com/cycloidio/terraform/command/arguments"
    19  	"github.com/cycloidio/terraform/command/clistate"
    20  	"github.com/cycloidio/terraform/command/views"
    21  	"github.com/cycloidio/terraform/states"
    22  	"github.com/cycloidio/terraform/states/statemgr"
    23  	"github.com/cycloidio/terraform/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  
   583  		// If the current workspace is has no state we do not need to ask
   584  		// if they want to migrate the state.
   585  		sourceState, err := opts.Source.StateMgr(currentWorkspace)
   586  		if err != nil {
   587  			return err
   588  		}
   589  		if err := sourceState.RefreshState(); err != nil {
   590  			return err
   591  		}
   592  		if sourceState.State().Empty() {
   593  			log.Printf("[INFO] backendMigrateTFC: skipping migration because source %s is empty", opts.sourceWorkspace)
   594  			return nil
   595  		}
   596  
   597  		// Run normal single-to-single state migration.
   598  		// This will handle both situations where the new cloud backend
   599  		// configuration is using a workspace.name strategy or workspace.tags
   600  		// strategy.
   601  		//
   602  		// We do prompt first though, because state migration is mandatory
   603  		// for moving to Cloud and the user should get an opportunity to
   604  		// confirm that first.
   605  		if migrate, err := m.promptSingleToCloudSingleStateMigration(opts); err != nil {
   606  			return err
   607  		} else if !migrate {
   608  			return nil //skip migrating but return successfully
   609  		}
   610  
   611  		return m.backendMigrateState_s_s(opts)
   612  	}
   613  
   614  	destinationTagsStrategy := cloudBackendDestination.WorkspaceMapping.Strategy() == cloud.WorkspaceTagsStrategy
   615  	destinationNameStrategy := cloudBackendDestination.WorkspaceMapping.Strategy() == cloud.WorkspaceNameStrategy
   616  
   617  	multiSource := !sourceSingleState && len(sourceWorkspaces) > 1
   618  	if multiSource && destinationNameStrategy {
   619  		currentWorkspace, err := m.Workspace()
   620  		if err != nil {
   621  			return err
   622  		}
   623  
   624  		opts.sourceWorkspace = currentWorkspace
   625  		opts.destinationWorkspace = cloudBackendDestination.WorkspaceMapping.Name
   626  		if err := m.promptMultiToSingleCloudMigration(opts); err != nil {
   627  			return err
   628  		}
   629  
   630  		log.Printf("[INFO] backendMigrateTFC: multi-to-single migration from source %s to destination %q", opts.sourceWorkspace, opts.destinationWorkspace)
   631  
   632  		return m.backendMigrateState_s_s(opts)
   633  	}
   634  
   635  	// Multiple sources, and using tags strategy. So migrate every source
   636  	// workspace over to new one, prompt for workspace name pattern (*),
   637  	// and start migrating, and create tags for each workspace.
   638  	if multiSource && destinationTagsStrategy {
   639  		log.Printf("[INFO] backendMigrateTFC: multi-to-multi migration from source workspaces %q", sourceWorkspaces)
   640  		return m.backendMigrateState_S_TFC(opts, sourceWorkspaces)
   641  	}
   642  
   643  	// TODO(omar): after the check for sourceSingle is done, everything following
   644  	// it has to be multi. So rework the code to not need to check for multi, adn
   645  	// return m.backendMigrateState_S_TFC here.
   646  	return nil
   647  }
   648  
   649  // migrates a multi-state backend to Terraform Cloud
   650  func (m *Meta) backendMigrateState_S_TFC(opts *backendMigrateOpts, sourceWorkspaces []string) error {
   651  	log.Print("[TRACE] backendMigrateState: migrating all named workspaces")
   652  
   653  	currentWorkspace, err := m.Workspace()
   654  	if err != nil {
   655  		return err
   656  	}
   657  	newCurrentWorkspace := ""
   658  
   659  	// This map is used later when doing the migration per source/destination.
   660  	// If a source has 'default' and has state, then we ask what the new name should be.
   661  	// And further down when we actually run state migration for each
   662  	// source/destination workspace, we use this new name (where source is 'default')
   663  	// and set as destinationWorkspace. If the default workspace does not have
   664  	// state we will not prompt the user for a new name because empty workspaces
   665  	// do not get migrated.
   666  	defaultNewName := map[string]string{}
   667  	for i := 0; i < len(sourceWorkspaces); i++ {
   668  		if sourceWorkspaces[i] == backend.DefaultStateName {
   669  			// For the default workspace we want to look to see if there is any state
   670  			// before we ask for a workspace name to migrate the default workspace into.
   671  			sourceState, err := opts.Source.StateMgr(backend.DefaultStateName)
   672  			if err != nil {
   673  				return fmt.Errorf(strings.TrimSpace(
   674  					errMigrateSingleLoadDefault), opts.SourceType, err)
   675  			}
   676  			// RefreshState is what actually pulls the state to be evaluated.
   677  			if err := sourceState.RefreshState(); err != nil {
   678  				return fmt.Errorf(strings.TrimSpace(
   679  					errMigrateSingleLoadDefault), opts.SourceType, err)
   680  			}
   681  			if !sourceState.State().Empty() {
   682  				newName, err := m.promptNewWorkspaceName(opts.DestinationType)
   683  				if err != nil {
   684  					return err
   685  				}
   686  				defaultNewName[sourceWorkspaces[i]] = newName
   687  			}
   688  		}
   689  	}
   690  
   691  	// Fetch the pattern that will be used to rename the workspaces for Terraform Cloud.
   692  	//
   693  	// * For the general case, this will be a pattern provided by the user.
   694  	//
   695  	// * Specifically for a migration from the "remote" backend using 'prefix', we will
   696  	//   instead 'migrate' the workspaces using a pattern based on the old prefix+name,
   697  	//   not allowing a user to accidentally input the wrong pattern to line up with
   698  	//   what the the remote backend was already using before (which presumably already
   699  	//   meets the naming considerations for Terraform Cloud).
   700  	//   In other words, this is a fast-track migration path from the remote backend, retaining
   701  	//   how things already are in Terraform Cloud with no user intervention needed.
   702  	pattern := ""
   703  	if remoteBackend, ok := opts.Source.(*remote.Remote); ok {
   704  		if err := m.promptRemotePrefixToCloudTagsMigration(opts); err != nil {
   705  			return err
   706  		}
   707  		pattern = remoteBackend.WorkspaceNamePattern()
   708  		log.Printf("[TRACE] backendMigrateTFC: Remote backend reports workspace name pattern as: %q", pattern)
   709  	}
   710  
   711  	if pattern == "" {
   712  		pattern, err = m.promptMultiStateMigrationPattern(opts.SourceType)
   713  		if err != nil {
   714  			return err
   715  		}
   716  	}
   717  
   718  	// Go through each and migrate
   719  	for _, name := range sourceWorkspaces {
   720  
   721  		// Copy the same names
   722  		opts.sourceWorkspace = name
   723  		if newName, ok := defaultNewName[name]; ok {
   724  			// this has to be done before setting destinationWorkspace
   725  			name = newName
   726  		}
   727  		opts.destinationWorkspace = strings.Replace(pattern, "*", name, -1)
   728  
   729  		// Force it, we confirmed above
   730  		opts.force = true
   731  
   732  		// Perform the migration
   733  		log.Printf("[INFO] backendMigrateTFC: multi-to-multi migration, source workspace %q to destination workspace %q", opts.sourceWorkspace, opts.destinationWorkspace)
   734  		if err := m.backendMigrateState_s_s(opts); err != nil {
   735  			return fmt.Errorf(strings.TrimSpace(
   736  				errMigrateMulti), name, opts.SourceType, opts.DestinationType, err)
   737  		}
   738  
   739  		if currentWorkspace == opts.sourceWorkspace {
   740  			newCurrentWorkspace = opts.destinationWorkspace
   741  		}
   742  	}
   743  
   744  	// After migrating multiple workspaces, we need to reselect the current workspace as it may
   745  	// have been renamed. Query the backend first to be sure it now exists.
   746  	workspaces, err := opts.Destination.Workspaces()
   747  	if err != nil {
   748  		return err
   749  	}
   750  
   751  	var workspacePresent bool
   752  	for _, name := range workspaces {
   753  		if name == newCurrentWorkspace {
   754  			workspacePresent = true
   755  		}
   756  	}
   757  
   758  	// If we couldn't select the workspace automatically from the backend (maybe it was empty
   759  	// and wasn't migrated, for instance), ask the user to select one instead and be done.
   760  	if !workspacePresent {
   761  		if err = m.selectWorkspace(opts.Destination); err != nil {
   762  			return err
   763  		}
   764  		return nil
   765  	}
   766  
   767  	// The newly renamed current workspace is present, so we'll automatically select it for the
   768  	// user, as well as display the equivalent of 'workspace list' to show how the workspaces
   769  	// were changed (as well as the newly selected current workspace).
   770  	if err = m.SetWorkspace(newCurrentWorkspace); err != nil {
   771  		return err
   772  	}
   773  
   774  	m.Ui.Output(m.Colorize().Color("[reset][bold]Migration complete! Your workspaces are as follows:[reset]"))
   775  	var out bytes.Buffer
   776  	for _, name := range workspaces {
   777  		if name == newCurrentWorkspace {
   778  			out.WriteString("* ")
   779  		} else {
   780  			out.WriteString("  ")
   781  		}
   782  		out.WriteString(name + "\n")
   783  	}
   784  
   785  	m.Ui.Output(out.String())
   786  
   787  	return nil
   788  }
   789  
   790  func (m *Meta) promptSingleToCloudSingleStateMigration(opts *backendMigrateOpts) (bool, error) {
   791  	if !m.input {
   792  		log.Print("[TRACE] backendMigrateState: can't prompt for input, so aborting migration")
   793  		return false, errors.New(strings.TrimSpace(errInteractiveInputDisabled))
   794  	}
   795  	migrate := opts.force
   796  	if !migrate {
   797  		var err error
   798  		migrate, err = m.confirm(&terraform.InputOpts{
   799  			Id:          "backend-migrate-state-single-to-cloud-single",
   800  			Query:       "Do you wish to proceed?",
   801  			Description: strings.TrimSpace(tfcInputBackendMigrateStateSingleToCloudSingle),
   802  		})
   803  		if err != nil {
   804  			return false, fmt.Errorf("Error asking for state migration action: %s", err)
   805  		}
   806  	}
   807  
   808  	return migrate, nil
   809  }
   810  
   811  func (m *Meta) promptRemotePrefixToCloudTagsMigration(opts *backendMigrateOpts) error {
   812  	if !m.input {
   813  		log.Print("[TRACE] backendMigrateState: can't prompt for input, so aborting migration")
   814  		return errors.New(strings.TrimSpace(errInteractiveInputDisabled))
   815  	}
   816  	migrate := opts.force
   817  	if !migrate {
   818  		var err error
   819  		migrate, err = m.confirm(&terraform.InputOpts{
   820  			Id:          "backend-migrate-remote-multistate-to-cloud",
   821  			Query:       "Do you wish to proceed?",
   822  			Description: strings.TrimSpace(tfcInputBackendMigrateRemoteMultiToCloud),
   823  		})
   824  		if err != nil {
   825  			return fmt.Errorf("Error asking for state migration action: %s", err)
   826  		}
   827  	}
   828  
   829  	if !migrate {
   830  		return fmt.Errorf("Migration aborted by user.")
   831  	}
   832  
   833  	return nil
   834  }
   835  
   836  // Multi-state to single state.
   837  func (m *Meta) promptMultiToSingleCloudMigration(opts *backendMigrateOpts) error {
   838  	if !m.input {
   839  		log.Print("[TRACE] backendMigrateState: can't prompt for input, so aborting migration")
   840  		return errors.New(strings.TrimSpace(errInteractiveInputDisabled))
   841  	}
   842  	migrate := opts.force
   843  	if !migrate {
   844  		var err error
   845  		// Ask the user if they want to migrate their existing remote state
   846  		migrate, err = m.confirm(&terraform.InputOpts{
   847  			Id:    "backend-migrate-multistate-to-single",
   848  			Query: "Do you want to copy only your current workspace?",
   849  			Description: fmt.Sprintf(
   850  				strings.TrimSpace(tfcInputBackendMigrateMultiToSingle),
   851  				opts.SourceType, opts.destinationWorkspace),
   852  		})
   853  		if err != nil {
   854  			return fmt.Errorf("Error asking for state migration action: %s", err)
   855  		}
   856  	}
   857  
   858  	if !migrate {
   859  		return fmt.Errorf("Migration aborted by user.")
   860  	}
   861  
   862  	return nil
   863  }
   864  
   865  func (m *Meta) promptNewWorkspaceName(destinationType string) (string, error) {
   866  	message := fmt.Sprintf("[reset][bold][yellow]The %q backend configuration only allows "+
   867  		"named workspaces![reset]", destinationType)
   868  	if destinationType == "cloud" {
   869  		if !m.input {
   870  			log.Print("[TRACE] backendMigrateState: can't prompt for input, so aborting migration")
   871  			return "", errors.New(strings.TrimSpace(errInteractiveInputDisabled))
   872  		}
   873  		message = `[reset][bold][yellow]Terraform Cloud requires all workspaces to be given an explicit name.[reset]`
   874  	}
   875  	name, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{
   876  		Id:          "new-state-name",
   877  		Query:       message,
   878  		Description: strings.TrimSpace(inputBackendNewWorkspaceName),
   879  	})
   880  	if err != nil {
   881  		return "", fmt.Errorf("Error asking for new state name: %s", err)
   882  	}
   883  
   884  	return name, nil
   885  }
   886  
   887  func (m *Meta) promptMultiStateMigrationPattern(sourceType string) (string, error) {
   888  	// This is not the first prompt a user would be presented with in the migration to TFC, so no
   889  	// guard on m.input is needed here.
   890  	renameWorkspaces, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{
   891  		Id:          "backend-migrate-multistate-to-tfc",
   892  		Query:       fmt.Sprintf("[reset][bold][yellow]%s[reset]", "Would you like to rename your workspaces?"),
   893  		Description: fmt.Sprintf(strings.TrimSpace(tfcInputBackendMigrateMultiToMulti), sourceType),
   894  	})
   895  	if err != nil {
   896  		return "", fmt.Errorf("Error asking for state migration action: %s", err)
   897  	}
   898  	if renameWorkspaces != "2" && renameWorkspaces != "1" {
   899  		return "", fmt.Errorf("Please select 1 or 2 as part of this option.")
   900  	}
   901  	if renameWorkspaces == "2" {
   902  		// this means they did not want to rename their workspaces, and we are
   903  		// returning a generic '*' that means use the same workspace name during
   904  		// migration.
   905  		return "*", nil
   906  	}
   907  
   908  	pattern, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{
   909  		Id:          "backend-migrate-multistate-to-tfc-pattern",
   910  		Query:       fmt.Sprintf("[reset][bold][yellow]%s[reset]", "How would you like to rename your workspaces?"),
   911  		Description: strings.TrimSpace(tfcInputBackendMigrateMultiToMultiPattern),
   912  	})
   913  	if err != nil {
   914  		return "", fmt.Errorf("Error asking for state migration action: %s", err)
   915  	}
   916  	if !strings.Contains(pattern, "*") {
   917  		return "", fmt.Errorf("The pattern must have an '*'")
   918  	}
   919  
   920  	if count := strings.Count(pattern, "*"); count > 1 {
   921  		return "", fmt.Errorf("The pattern '*' cannot be used more than once.")
   922  	}
   923  
   924  	return pattern, nil
   925  }
   926  
   927  const errMigrateLoadStates = `
   928  Error inspecting states in the %q backend:
   929      %s
   930  
   931  Prior to changing backends, Terraform inspects the source and destination
   932  states to determine what kind of migration steps need to be taken, if any.
   933  Terraform failed to load the states. The data in both the source and the
   934  destination remain unmodified. Please resolve the above error and try again.
   935  `
   936  
   937  const errMigrateSingleLoadDefault = `
   938  Error loading state:
   939      %[2]s
   940  
   941  Terraform failed to load the default state from the %[1]q backend.
   942  State migration cannot occur unless the state can be loaded. Backend
   943  modification and state migration has been aborted. The state in both the
   944  source and the destination remain unmodified. Please resolve the
   945  above error and try again.
   946  `
   947  
   948  const errMigrateMulti = `
   949  Error migrating the workspace %q from the previous %q backend
   950  to the newly configured %q backend:
   951      %s
   952  
   953  Terraform copies workspaces in alphabetical order. Any workspaces
   954  alphabetically earlier than this one have been copied. Any workspaces
   955  later than this haven't been modified in the destination. No workspaces
   956  in the source state have been modified.
   957  
   958  Please resolve the error above and run the initialization command again.
   959  This will attempt to copy (with permission) all workspaces again.
   960  `
   961  
   962  const errBackendStateCopy = `
   963  Error copying state from the previous %q backend to the newly configured 
   964  %q backend:
   965      %s
   966  
   967  The state in the previous backend remains intact and unmodified. Please resolve
   968  the error above and try again.
   969  `
   970  
   971  const errTFCMigrateNotYetImplemented = `
   972  Migrating state from Terraform Cloud to another backend is not yet implemented.
   973  
   974  Please use the API to do this: https://www.terraform.io/docs/cloud/api/state-versions.html
   975  `
   976  
   977  const errInteractiveInputDisabled = `
   978  Can't ask approval for state migration when interactive input is disabled.
   979  
   980  Please remove the "-input=false" option and try again.
   981  `
   982  
   983  const tfcInputBackendMigrateMultiToMultiPattern = `
   984  Enter a pattern with an asterisk (*) to rename all workspaces based on their
   985  previous names. The asterisk represents the current workspace name.
   986  
   987  For example, if a workspace is currently named 'prod', the pattern 'app-*' would yield
   988  'app-prod' for a new workspace name; 'app-*-region1' would  yield 'app-prod-region1'.
   989  `
   990  
   991  const tfcInputBackendMigrateMultiToMulti = `
   992  Unlike typical Terraform workspaces representing an environment associated with a particular
   993  configuration (e.g. production, staging, development), Terraform Cloud workspaces are named uniquely
   994  across all configurations used within an organization. A typical strategy to start with is
   995  <COMPONENT>-<ENVIRONMENT>-<REGION> (e.g. networking-prod-us-east, networking-staging-us-east).
   996  
   997  For more information on workspace naming, see https://www.terraform.io/docs/cloud/workspaces/naming.html
   998  
   999  When migrating existing workspaces from the backend %[1]q to Terraform Cloud, would you like to
  1000  rename your workspaces? Enter 1 or 2.
  1001  
  1002  1. Yes, I'd like to rename all workspaces according to a pattern I will provide.
  1003  2. No, I would not like to rename my workspaces. Migrate them as currently named.
  1004  `
  1005  
  1006  const tfcInputBackendMigrateMultiToSingle = `
  1007  The previous backend %[1]q has multiple workspaces, but Terraform Cloud has
  1008  been configured to use a single workspace (%[2]q). By continuing, you will
  1009  only migrate your current workspace. If you wish to migrate all workspaces
  1010  from the previous backend, you may cancel this operation and use the 'tags'
  1011  strategy in your workspace configuration block instead.
  1012  
  1013  Enter "yes" to proceed or "no" to cancel.
  1014  `
  1015  
  1016  const tfcInputBackendMigrateStateSingleToCloudSingle = `
  1017  As part of migrating to Terraform Cloud, Terraform can optionally copy your
  1018  current workspace state to the configured Terraform Cloud workspace.
  1019  
  1020  Answer "yes" to copy the latest state snapshot to the configured
  1021  Terraform Cloud workspace.
  1022  
  1023  Answer "no" to ignore the existing state and just activate the configured
  1024  Terraform Cloud workspace with its existing state, if any.
  1025  
  1026  Should Terraform migrate your existing state?
  1027  `
  1028  
  1029  const tfcInputBackendMigrateRemoteMultiToCloud = `
  1030  When migrating from the 'remote' backend to Terraform's native integration
  1031  with Terraform Cloud, Terraform will automatically create or use existing
  1032  workspaces based on the previous backend configuration's 'prefix' value.
  1033  
  1034  When the migration is complete, workspace names in Terraform will match the
  1035  fully qualified Terraform Cloud workspace name. If necessary, the workspace
  1036  tags configured in the 'cloud' option block will be added to the associated
  1037  Terraform Cloud workspaces.
  1038  
  1039  Enter "yes" to proceed or "no" to cancel.
  1040  `
  1041  
  1042  const inputBackendMigrateEmpty = `
  1043  Pre-existing state was found while migrating the previous %q backend to the
  1044  newly configured %q backend. No existing state was found in the newly
  1045  configured %[2]q backend. Do you want to copy this state to the new %[2]q
  1046  backend? Enter "yes" to copy and "no" to start with an empty state.
  1047  `
  1048  
  1049  const inputBackendMigrateEmptyCloud = `
  1050  Pre-existing state was found while migrating the previous %q backend to Terraform Cloud.
  1051  No existing state was found in Terraform Cloud. Do you want to copy this state to Terraform Cloud?
  1052  Enter "yes" to copy and "no" to start with an empty state.
  1053  `
  1054  
  1055  const inputBackendMigrateNonEmpty = `
  1056  Pre-existing state was found while migrating the previous %q backend to the
  1057  newly configured %q backend. An existing non-empty state already exists in
  1058  the new backend. The two states have been saved to temporary files that will be
  1059  removed after responding to this query.
  1060  
  1061  Previous (type %[1]q): %[3]s
  1062  New      (type %[2]q): %[4]s
  1063  
  1064  Do you want to overwrite the state in the new backend with the previous state?
  1065  Enter "yes" to copy and "no" to start with the existing state in the newly
  1066  configured %[2]q backend.
  1067  `
  1068  
  1069  const inputBackendMigrateNonEmptyCloud = `
  1070  Pre-existing state was found while migrating the previous %q backend to
  1071  Terraform Cloud. An existing non-empty state already exists in Terraform Cloud.
  1072  The two states have been saved to temporary files that will be removed after
  1073  responding to this query.
  1074  
  1075  Previous (type %[1]q): %[2]s
  1076  New      (Terraform Cloud): %[3]s
  1077  
  1078  Do you want to overwrite the state in Terraform Cloud with the previous state?
  1079  Enter "yes" to copy and "no" to start with the existing state in Terraform Cloud.
  1080  `
  1081  
  1082  const inputBackendMigrateMultiToSingle = `
  1083  The existing %[1]q backend supports workspaces and you currently are
  1084  using more than one. The newly configured %[2]q backend doesn't support
  1085  workspaces. If you continue, Terraform will copy your current workspace %[3]q
  1086  to the default workspace in the new backend. Your existing workspaces in the
  1087  source backend won't be modified. If you want to switch workspaces, back them
  1088  up, or cancel altogether, answer "no" and Terraform will abort.
  1089  `
  1090  
  1091  const inputBackendMigrateMultiToMulti = `
  1092  Both the existing %[1]q backend and the newly configured %[2]q backend
  1093  support workspaces. When migrating between backends, Terraform will copy
  1094  all workspaces (with the same names). THIS WILL OVERWRITE any conflicting
  1095  states in the destination.
  1096  
  1097  Terraform initialization doesn't currently migrate only select workspaces.
  1098  If you want to migrate a select number of workspaces, you must manually
  1099  pull and push those states.
  1100  
  1101  If you answer "yes", Terraform will migrate all states. If you answer
  1102  "no", Terraform will abort.
  1103  `
  1104  
  1105  const inputBackendNewWorkspaceName = `
  1106  Please provide a new workspace name (e.g. dev, test) that will be used
  1107  to migrate the existing default workspace.
  1108  `
  1109  
  1110  const inputBackendSelectWorkspace = `
  1111  This is expected behavior when the selected workspace did not have an
  1112  existing non-empty state. Please enter a number to select a workspace:
  1113  
  1114  %s
  1115  `