From 779c958fbf7609b20b2665551e1d2e98c3f932a8 Mon Sep 17 00:00:00 2001 From: Chris Arcand Date: Wed, 3 Nov 2021 14:41:55 -0500 Subject: [PATCH] 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. --- internal/backend/remote/backend.go | 11 ++++ internal/command/meta_backend.go | 6 +- internal/command/meta_backend_migrate.go | 76 +++++++++++++++++++++--- 3 files changed, 83 insertions(+), 10 deletions(-) diff --git a/internal/backend/remote/backend.go b/internal/backend/remote/backend.go index bc4c03175..4f51966c5 100644 --- a/internal/backend/remote/backend.go +++ b/internal/backend/remote/backend.go @@ -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 { diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index 2ebc61c6f..005237845 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -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 diff --git a/internal/command/meta_backend_migrate.go b/internal/command/meta_backend_migrate.go index 2999a8c59..508199ade 100644 --- a/internal/command/meta_backend_migrate.go +++ b/internal/command/meta_backend_migrate.go @@ -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 = `