From 419676cb69f4134a99124545afce12e742d506cf Mon Sep 17 00:00:00 2001 From: Barrett Clark Date: Mon, 22 Nov 2021 13:17:04 -0600 Subject: [PATCH] Cloud integration requires input for migrations We cannot programmatically migrate workspaces to Terraform Cloud without prompts, so `-input=false` should not be allowed in those cases. There are 4 scenarios where we need input from a user to complete migrating workspaces to Terraform Cloud. 1.) Migrate from a single local workspace to Terraform Cloud * Terraform config for a local backend. Implicit local (no backend specified) is fine. * `terraform init` and `terraform apply` * Change the Terraform config to use the cloud block * `terraform init -input=false` * You should now see an error message 2.) Migrate from a remote backend with a prefix to Terraform Cloud with tags * Create a workspace in Terraform Cloud manually. The name should include a prefix, like "app-one" * Have the terraform config use `backend "remote"` with a prefix set to "app-" * `terraform init` and `terraform apply` * Update the Terraform config to use a cloud block with `tags = ["app"]`. There should not be a prefix defined in the config now. * `terraform init -input=false` * You should now see an error message 3.) Migrate from multiple local workspaces to a single Terraform Cloud workspace * Create one or many local workspaces * `terraform init` and `terraform apply` in each * Change the Terraform config to use the cloud block * `terraform init -input=false` * You should now see an error message 4.) Migrate to Terraform Cloud and ask for a workspace name * Create several local workspaces * `terraform init` and `terraform apply` in each * Change the Terraform config to use the cloud block with tags * `terraform init -input=false` * You should now see an error message --- internal/command/init_test.go | 2 +- internal/command/meta_backend_migrate.go | 26 +++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/internal/command/init_test.go b/internal/command/init_test.go index 90da036f01..a96e0e9d66 100644 --- a/internal/command/init_test.go +++ b/internal/command/init_test.go @@ -1310,7 +1310,7 @@ func TestInit_inputFalse(t *testing.T) { } errMsg := ui.ErrorWriter.String() - if !strings.Contains(errMsg, "input disabled") { + if !strings.Contains(errMsg, "interactive input is disabled") { t.Fatal("expected input disabled error, got", errMsg) } diff --git a/internal/command/meta_backend_migrate.go b/internal/command/meta_backend_migrate.go index 6bdff56eb5..5b476ff2b0 100644 --- a/internal/command/meta_backend_migrate.go +++ b/internal/command/meta_backend_migrate.go @@ -415,7 +415,7 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error { // Abort if we can't ask for input. if !m.input { log.Print("[TRACE] backendMigrateState: can't prompt for input, so aborting migration") - return errors.New("error asking for state migration action: input disabled") + return errors.New(strings.TrimSpace(errInteractiveInputDisabled)) } // Confirm with the user whether we want to copy state over @@ -773,6 +773,10 @@ func (m *Meta) backendMigrateState_S_TFC(opts *backendMigrateOpts, sourceWorkspa } func (m *Meta) promptSingleToCloudSingleStateMigration(opts *backendMigrateOpts) (bool, error) { + if !m.input { + log.Print("[TRACE] backendMigrateState: can't prompt for input, so aborting migration") + return false, errors.New(strings.TrimSpace(errInteractiveInputDisabled)) + } migrate := opts.force if !migrate { var err error @@ -790,6 +794,10 @@ func (m *Meta) promptSingleToCloudSingleStateMigration(opts *backendMigrateOpts) } func (m *Meta) promptRemotePrefixToCloudTagsMigration(opts *backendMigrateOpts) error { + if !m.input { + log.Print("[TRACE] backendMigrateState: can't prompt for input, so aborting migration") + return errors.New(strings.TrimSpace(errInteractiveInputDisabled)) + } migrate := opts.force if !migrate { var err error @@ -812,6 +820,10 @@ func (m *Meta) promptRemotePrefixToCloudTagsMigration(opts *backendMigrateOpts) // Multi-state to single state. func (m *Meta) promptMultiToSingleCloudMigration(opts *backendMigrateOpts) error { + if !m.input { + log.Print("[TRACE] backendMigrateState: can't prompt for input, so aborting migration") + return errors.New(strings.TrimSpace(errInteractiveInputDisabled)) + } migrate := opts.force if !migrate { var err error @@ -839,6 +851,10 @@ func (m *Meta) promptNewWorkspaceName(destinationType string) (string, error) { message := fmt.Sprintf("[reset][bold][yellow]The %q backend configuration only allows "+ "named workspaces![reset]", destinationType) if destinationType == "cloud" { + if !m.input { + log.Print("[TRACE] backendMigrateState: can't prompt for input, so aborting migration") + return "", errors.New(strings.TrimSpace(errInteractiveInputDisabled)) + } message = `[reset][bold][yellow]Terraform Cloud requires all workspaces to be given an explicit name.[reset]` } name, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{ @@ -854,6 +870,8 @@ func (m *Meta) promptNewWorkspaceName(destinationType string) (string, error) { } func (m *Meta) promptMultiStateMigrationPattern(sourceType string) (string, error) { + // This is not the first prompt a user would be presented with in the migration to TFC, so no + // guard on m.input is needed here. renameWorkspaces, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{ Id: "backend-migrate-multistate-to-tfc", Query: fmt.Sprintf("[reset][bold][yellow]%s[reset]", "Would you like to rename your workspaces?"), @@ -941,6 +959,12 @@ Migrating state from Terraform Cloud to another backend is not yet implemented. Please use the API to do this: https://www.terraform.io/docs/cloud/api/state-versions.html ` +const errInteractiveInputDisabled = ` +Can't ask approval for state migration when interactive input is disabled. + +Please remove the "-input=false" option and try again. +` + const tfcInputBackendMigrateMultiToMultiPattern = ` Enter a pattern with an asterisk (*) to rename all workspaces based on their previous names. The asterisk represents the current workspace name.