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