From c99f5972dee6f62eaaf96ba74ab3cd4e71d19caa Mon Sep 17 00:00:00 2001 From: Nick Fagerlund Date: Fri, 22 Oct 2021 14:50:16 -0700 Subject: [PATCH] cloud.StateMgr(): Set terraform version AFTER creating workspace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, if the remote TFC/TFE instance doesn't happen to have a tool_version record whose name exactly matches the value of `tfversion.String()`, Terraform would be completely blocked from using the `terraform workspace new` command (when configured with the tags strategy) — the API would give a 422 to the whole create request. This commit changes the StateMgr() function to do the work in two passes; first create the workspace (which should work fine regardless), THEN update the Terraform version and print a warning to the terminal if it fails (which 99% of the time is a benign failure with little impact on your future CLI usage). --- internal/cloud/backend.go | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/internal/cloud/backend.go b/internal/cloud/backend.go index e6b0ba98a1..f043378f68 100644 --- a/internal/cloud/backend.go +++ b/internal/cloud/backend.go @@ -506,6 +506,7 @@ func (b *Cloud) StateMgr(name string) (statemgr.Full, error) { } if err == tfe.ErrResourceNotFound { + // Create a workspace options := tfe.WorkspaceCreateOptions{ Name: tfe.String(name), } @@ -517,12 +518,27 @@ func (b *Cloud) StateMgr(name string) (statemgr.Full, error) { } options.Tags = tags - options.TerraformVersion = tfe.String(tfversion.String()) - workspace, err = b.client.Workspaces.Create(context.Background(), b.organization, options) if err != nil { return nil, fmt.Errorf("Error creating workspace %s: %v", name, err) } + + // Attempt to set the new workspace to use this version of Terraform. This + // can fail if there's no enabled tool_version whose name matches our + // version string, but that's expected sometimes -- just warn and continue. + versionOptions := tfe.WorkspaceUpdateOptions{ + TerraformVersion: tfe.String(tfversion.String()), + } + _, err := b.client.Workspaces.UpdateByID(context.Background(), workspace.ID, versionOptions) + if err != nil { + // TODO: Ideally we could rely on the client to tell us what the actual + // problem was, but we currently can't get enough context from the error + // object to do a nicely formatted message, so we're just assuming the + // issue was that the version wasn't available since that's probably what + // happened. + versionUnavailable := fmt.Sprintf(unavailableTerraformVersion, tfversion.String(), workspace.TerraformVersion) + b.CLI.Output(b.Colorize().Color(versionUnavailable)) + } } // This is a fallback error check. Most code paths should use other @@ -948,6 +964,12 @@ const operationNotCanceled = ` const refreshToApplyRefresh = `[bold][yellow]Proceeding with 'terraform apply -refresh-only -auto-approve'.[reset]` +const unavailableTerraformVersion = ` +[reset][yellow]The local Terraform version (%s) is not available in Terraform Cloud, or your +organization does not have access to it. The new workspace will use %s. You can +change this later in the workspace settings.[reset] +` + var ( workspaceConfigurationHelp = fmt.Sprintf( `The 'workspaces' block configures how Terraform CLI maps its workspaces for this single