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).
This commit is contained in:
Alisdair McDiarmid 2020-12-08 14:46:56 -05:00
parent 93c36e67b1
commit 6e0d8cde91
2 changed files with 52 additions and 3 deletions

View File

@ -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(

View File

@ -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)