cloud: Add streamlined 'remote' backend state migration path

For Terraform Cloud users using the 'remote' backend, the existing
'pattern' prompt should work just fine - but because their workspaces
are already present in TFC, the 'migration' here is really just
realigning their local workspaces with Terraform Cloud. Instead of
forcing users to do the mental gymnastics of what it means to migrate
from 'prefix' - and because their remote workspaces probably already exist and
already conform to Terraform Cloud's naming concerns - streamline the
process for them and calculate the necessary pattern to migrate as-is,
without any user intervention necessary.
This commit is contained in:
Chris Arcand 2021-11-03 14:41:55 -05:00
parent 2690f738aa
commit 779c958fbf
3 changed files with 83 additions and 10 deletions

View File

@ -569,6 +569,17 @@ func (b *Remote) workspaces() ([]string, error) {
return names, nil
}
// WorkspaceNamePattern provides an appropriate workspace renaming pattern for backend migration
// purposes (handled outside of this package), based on previous usage of this backend with the
// 'prefix' workspace functionality. As of this writing, see meta_backend.migrate.go
func (b *Remote) WorkspaceNamePattern() string {
if b.prefix != "" {
return b.prefix + "*"
}
return ""
}
// DeleteWorkspace implements backend.Enhanced.
func (b *Remote) DeleteWorkspace(name string) error {
if b.workspace == "" && name == backend.DefaultStateName {

View File

@ -1002,7 +1002,7 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clista
if output {
// Notify the user
m.Ui.Output(m.Colorize().Color(fmt.Sprintf(
"[reset]%s\n\n",
"[reset]%s\n",
strings.TrimSpace(outputBackendReconfigure))))
}
@ -1021,7 +1021,9 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clista
if c.Type == "cloud" {
output = fmt.Sprintf(outputBackendMigrateChangeCloud, s.Backend.Type)
}
m.Ui.Output(strings.TrimSpace(output))
m.Ui.Output(m.Colorize().Color(fmt.Sprintf(
"[reset]%s\n",
strings.TrimSpace(output))))
}
// Grab the existing backend

View File

@ -13,6 +13,7 @@ import (
"strings"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/backend/remote"
"github.com/hashicorp/terraform/internal/cloud"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/command/clistate"
@ -635,9 +636,32 @@ func (m *Meta) backendMigrateState_S_TFC(opts *backendMigrateOpts, sourceWorkspa
defaultNewName[sourceWorkspaces[i]] = newName
}
}
pattern, err := m.promptMultiStateMigrationPattern(opts.SourceType)
if err != nil {
return err
// Fetch the pattern that will be used to rename the workspaces for Terraform Cloud.
//
// * For the general case, this will be a pattern provided by the user.
//
// * Specifically for a migration from the "remote" backend using 'prefix', we will
// instead 'migrate' the workspaces using a pattern based on the old prefix+name,
// not allowing a user to accidentally input the wrong pattern to line up with
// what the the remote backend was already using before (which presumably already
// meets the naming considerations for Terraform Cloud).
// In other words, this is a fast-track migration path from the remote backend, retaining
// how things already are in Terraform Cloud with no user intervention needed.
pattern := ""
if remoteBackend, ok := opts.Source.(*remote.Remote); ok {
if err := m.promptRemotePrefixToCloudTagsMigration(opts); err != nil {
return err
}
pattern = remoteBackend.WorkspaceNamePattern()
log.Printf("[TRACE] backendMigrateTFC: Remote backend reports workspace name pattern as: %q", pattern)
}
if pattern == "" {
pattern, err = m.promptMultiStateMigrationPattern(opts.SourceType)
if err != nil {
return err
}
}
// Go through each and migrate
@ -712,6 +736,27 @@ func (m *Meta) backendMigrateState_S_TFC(opts *backendMigrateOpts, sourceWorkspa
return nil
}
func (m *Meta) promptRemotePrefixToCloudTagsMigration(opts *backendMigrateOpts) error {
migrate := opts.force
if !migrate {
var err error
migrate, err = m.confirm(&terraform.InputOpts{
Id: "backend-migrate-remote-multistate-to-cloud",
Query: "Do you wish to proceed?",
Description: strings.TrimSpace(tfcInputBackendMigrateRemoteMultiToCloud),
})
if err != nil {
return fmt.Errorf("Error asking for state migration action: %s", err)
}
}
if !migrate {
return fmt.Errorf("Migration aborted by user.")
}
return nil
}
// Multi-state to single state.
func (m *Meta) promptMultiToSingleCloudMigration(opts *backendMigrateOpts) error {
migrate := opts.force
@ -867,11 +912,26 @@ For more information on workspace naming, see https://www.terraform.io/docs/clou
`
const tfcInputBackendMigrateMultiToSingle = `
The previous backend %[1]q has multiple workspaces, but Terraform Cloud has been
configured to use a single workspace (%[2]q). By continuing, you will only
migrate your current workspace. If you wish to migrate all workspaces from the
previous backend, use the 'tags' strategy in your workspace configuration block
instead.
The previous backend %[1]q has multiple workspaces, but Terraform Cloud has
been configured to use a single workspace (%[2]q). By continuing, you will
only migrate your current workspace. If you wish to migrate all workspaces
from the previous backend, you may cancel this operation and use the 'tags'
strategy in your workspace configuration block instead.
Enter "yes" to proceed or "no" to cancel.
`
const tfcInputBackendMigrateRemoteMultiToCloud = `
When migrating from the 'remote' backend to Terraform's native integration
with Terraform Cloud, Terraform will automatically create or use existing
workspaces based on the previous backend configuration's 'prefix' value.
When the migration is complete, workspace names in Terraform will match the
fully qualified Terraform Cloud workspace name. If necessary, the workspace
tags configured in the 'cloud' option block will be added to the associated
Terraform Cloud workspaces.
Enter "yes" to proceed or "no" to cancel.
`
const inputBackendMigrateEmpty = `