From ea394d78de63b8606fed72cbbf13d3c932fdc81b Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Tue, 8 Dec 2020 19:46:56 +0000 Subject: [PATCH] backport of commit 6e0d8cde91fc9e2576bf7c7d6377e8d3e051594b --- 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 34fa84bbf2..f2fefb5ba1 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()) } } @@ -894,6 +897,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 46dc5c64a0..139ddafd30 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)