From 6e0d8cde91fc9e2576bf7c7d6377e8d3e051594b Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Tue, 8 Dec 2020 14:46:56 -0500 Subject: [PATCH] backend/remote: Fix for "latest" workspace version Terraform Cloud/Enterprise support a pseudo-version of "latest" for the configured workspace Terraform version. If this is chosen, we abandon the attempt to verify the versions are compatible, as the meaning of "latest" cannot be predicted. This affects both the StateMgr check (used for commands which execute remotely) and the full version check (for local commands). --- backend/remote/backend.go | 12 +++++++++- backend/remote/backend_test.go | 43 ++++++++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/backend/remote/backend.go b/backend/remote/backend.go index ce400d4d6..3e9173e40 100644 --- a/backend/remote/backend.go +++ b/backend/remote/backend.go @@ -641,7 +641,10 @@ func (b *Remote) StateMgr(name string) (statemgr.Full, error) { // accidentally upgrade state with a new code path, and the version check // logic is coarser and simpler. if !b.ignoreVersionConflict { - if workspace.TerraformVersion != tfversion.String() { + wsv := workspace.TerraformVersion + // Explicitly ignore the pseudo-version "latest" here, as it will cause + // plan and apply to always fail. + if wsv != tfversion.String() && wsv != "latest" { return nil, fmt.Errorf("Remote workspace Terraform version %q does not match local Terraform version %q", workspace.TerraformVersion, tfversion.String()) } } @@ -890,6 +893,13 @@ func (b *Remote) VerifyWorkspaceTerraformVersion(workspaceName string) tfdiags.D return diags } + // If the workspace has the pseudo-version "latest", all bets are off. We + // cannot reasonably determine what the intended Terraform version is, so + // we'll skip version verification. + if workspace.TerraformVersion == "latest" { + return nil + } + remoteVersion, err := version.NewSemver(workspace.TerraformVersion) if err != nil { diags = diags.Append(tfdiags.Sourceless( diff --git a/backend/remote/backend_test.go b/backend/remote/backend_test.go index 46dc5c64a..139ddafd3 100644 --- a/backend/remote/backend_test.go +++ b/backend/remote/backend_test.go @@ -515,6 +515,45 @@ func TestRemote_StateMgr_versionCheck(t *testing.T) { } } +func TestRemote_StateMgr_versionCheckLatest(t *testing.T) { + b, bCleanup := testBackendDefault(t) + defer bCleanup() + + v0140 := version.Must(version.NewSemver("0.14.0")) + + // Save original local version state and restore afterwards + p := tfversion.Prerelease + v := tfversion.Version + s := tfversion.SemVer + defer func() { + tfversion.Prerelease = p + tfversion.Version = v + tfversion.SemVer = s + }() + + // For this test, the local Terraform version is set to 0.14.0 + tfversion.Prerelease = "" + tfversion.Version = v0140.String() + tfversion.SemVer = v0140 + + // Update the remote workspace to the pseudo-version "latest" + if _, err := b.client.Workspaces.Update( + context.Background(), + b.organization, + b.workspace, + tfe.WorkspaceUpdateOptions{ + TerraformVersion: tfe.String("latest"), + }, + ); err != nil { + t.Fatalf("error: %v", err) + } + + // This should succeed despite not being a string match + if _, err := b.StateMgr(backend.DefaultStateName); err != nil { + t.Fatalf("expected no error, got %v", err) + } +} + func TestRemote_VerifyWorkspaceTerraformVersion(t *testing.T) { testCases := []struct { local string @@ -528,6 +567,7 @@ func TestRemote_VerifyWorkspaceTerraformVersion(t *testing.T) { {"0.14.0", "1.1.0", true}, {"1.2.0", "1.2.99", false}, {"1.2.0", "1.3.0", true}, + {"0.15.0", "latest", false}, } for _, tc := range testCases { t.Run(fmt.Sprintf("local %s, remote %s", tc.local, tc.remote), func(t *testing.T) { @@ -535,7 +575,6 @@ func TestRemote_VerifyWorkspaceTerraformVersion(t *testing.T) { defer bCleanup() local := version.Must(version.NewSemver(tc.local)) - remote := version.Must(version.NewSemver(tc.remote)) // Save original local version state and restore afterwards p := tfversion.Prerelease @@ -559,7 +598,7 @@ func TestRemote_VerifyWorkspaceTerraformVersion(t *testing.T) { b.organization, b.workspace, tfe.WorkspaceUpdateOptions{ - TerraformVersion: tfe.String(remote.String()), + TerraformVersion: tfe.String(tc.remote), }, ); err != nil { t.Fatalf("error: %v", err)