blob: 4d7db857582d336e8c478292c3283c00e24fa2da [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package tfexec
import (
"context"
"errors"
"fmt"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/go-version"
"github.com/hashicorp/hc-install/product"
"github.com/hashicorp/hc-install/releases"
"github.com/hashicorp/terraform-exec/tfexec/internal/testutil"
)
func mustVersion(t *testing.T, s string) *version.Version {
v, err := version.NewVersion(s)
if err != nil {
t.Fatal(err)
}
return v
}
func TestParsePlaintextVersionOutput(t *testing.T) {
for i, c := range []struct {
expectedV *version.Version
expectedProviders map[string]*version.Version
stdout string
}{
// 0.13 tests
{
mustVersion(t, "0.13.0-dev"), nil, `
Terraform v0.13.0-dev`,
},
{
mustVersion(t, "0.13.0-dev"), map[string]*version.Version{
"registry.terraform.io/hashicorp/null": mustVersion(t, "2.1.2"),
"registry.terraform.io/paultyng/null": mustVersion(t, "0.1.0"),
}, `
Terraform v0.13.0-dev
+ provider registry.terraform.io/hashicorp/null v2.1.2
+ provider registry.terraform.io/paultyng/null v0.1.0`,
},
{
mustVersion(t, "0.13.0-dev"), nil, `
Terraform v0.13.0-dev
Your version of Terraform is out of date! The latest version
is 0.13.1. You can update by downloading from https://www.terraform.io/downloads.html`,
},
{
mustVersion(t, "0.13.0-dev"), map[string]*version.Version{
"registry.terraform.io/hashicorp/null": mustVersion(t, "2.1.2"),
"registry.terraform.io/paultyng/null": mustVersion(t, "0.1.0"),
}, `
Terraform v0.13.0-dev
+ provider registry.terraform.io/hashicorp/null v2.1.2
+ provider registry.terraform.io/paultyng/null v0.1.0
Your version of Terraform is out of date! The latest version
is 0.13.1. You can update by downloading from https://www.terraform.io/downloads.html`,
},
// 0.12 tests
{
mustVersion(t, "0.12.26"), nil, `
Terraform v0.12.26
`,
},
{
mustVersion(t, "0.12.26"), map[string]*version.Version{
"null": mustVersion(t, "2.1.2"),
}, `
Terraform v0.12.26
+ provider.null v2.1.2
`,
},
{
mustVersion(t, "0.12.18"), nil, `
Terraform v0.12.18
Your version of Terraform is out of date! The latest version
is 0.12.26. You can update by downloading from https://www.terraform.io/downloads.html
`,
},
{
mustVersion(t, "0.12.18"), map[string]*version.Version{
"null": mustVersion(t, "2.1.2"),
}, `
Terraform v0.12.18
+ provider.null v2.1.2
Your version of Terraform is out of date! The latest version
is 0.12.26. You can update by downloading from https://www.terraform.io/downloads.html
`,
},
} {
t.Run(fmt.Sprintf("%d %s", i, c.expectedV), func(t *testing.T) {
actualV, actualProv, err := parsePlaintextVersionOutput(c.stdout)
if err != nil {
t.Fatal(err)
}
if !c.expectedV.Equal(actualV) {
t.Fatalf("expected %s, got %s", c.expectedV, actualV)
}
for k, v := range c.expectedProviders {
if actual := actualProv[k]; actual == nil || !v.Equal(actual) {
t.Fatalf("expected %s for %s, got %s", v, k, actual)
}
}
if len(c.expectedProviders) != len(actualProv) {
t.Fatalf("expected %d providers, got %d", len(c.expectedProviders), len(actualProv))
}
})
}
}
func TestParseJsonVersionOutput(t *testing.T) {
testStdout := []byte(`{
"terraform_version": "0.15.0-beta1",
"platform": "darwin_amd64",
"provider_selections": {
"registry.terraform.io/hashicorp/aws": "3.31.0",
"registry.terraform.io/hashicorp/google": "3.58.0"
},
"terraform_outdated": false
}
`)
tfVersion, pvs, err := parseJsonVersionOutput(testStdout)
if err != nil {
t.Fatal(err)
}
expectedTfVer := mustVersion(t, "0.15.0-beta1")
if !expectedTfVer.Equal(tfVersion) {
t.Fatalf("version doesn't match (%q != %q)",
expectedTfVer.String(), tfVersion.String())
}
expectedPvs := map[string]*version.Version{
"registry.terraform.io/hashicorp/aws": mustVersion(t, "3.31.0"),
"registry.terraform.io/hashicorp/google": mustVersion(t, "3.58.0"),
}
if diff := cmp.Diff(expectedPvs, pvs); diff != "" {
t.Fatalf("provider versions don't match: %s", diff)
}
}
func TestVersionInRange(t *testing.T) {
for i, c := range []struct {
expected bool
min string
tfv string
max string
}{
{true, "", "0.12.26", ""},
{true, "", "0.13.0-beta3", ""},
{false, "", "0.12.26", "0.12.25"},
{false, "", "0.12.26", "0.12.26"},
{false, "0.12.27", "0.12.26", ""},
{true, "", "0.12.26", "0.13.0"},
{true, "0.12.25", "0.12.26", ""},
{true, "0.12.26", "0.12.26", ""},
{true, "0.12.26", "0.12.26", "0.12.27"},
{true, "0.12.26", "0.12.26", "0.13.0"},
{false, "0.12.26", "0.13.0-beta3", "0.13.0"},
{true, "0.12.26", "0.13.0-beta3", ""},
{true, "0.13.0", "0.13.0-beta3", ""},
{true, "0.13.0", "0.13.0-beta3", "0.14.0"},
{true, "", "0.13.0-beta3", "0.14.0"},
} {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
tfv, err := version.NewVersion(c.tfv)
if err != nil {
t.Fatal(err)
}
var min *version.Version
if c.min != "" {
min, err = version.NewVersion(c.min)
if err != nil {
t.Fatal(err)
}
}
var max *version.Version
if c.max != "" {
max, err = version.NewVersion(c.max)
if err != nil {
t.Fatal(err)
}
}
actual := versionInRange(tfv, min, max)
if actual != c.expected {
t.Fatalf("expected %v, got %v: %s <= %s < %s", c.expected, actual, min, tfv, max)
}
})
}
}
func TestCompatible(t *testing.T) {
if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
t.Skip("Terraform for darwin/arm64 is not available until v1")
}
ev := &releases.ExactVersion{
Product: product.Terraform,
Version: version.Must(version.NewVersion("0.12.26")),
}
ev.SetLogger(testutil.TestLogger())
ctx := context.Background()
t.Cleanup(func() { ev.Remove(ctx) })
tf01226, err := ev.Install(ctx)
if err != nil {
t.Fatal(err)
}
ev = &releases.ExactVersion{
Product: product.Terraform,
Version: version.Must(version.NewVersion("0.13.0-beta3")),
}
ev.SetLogger(testutil.TestLogger())
t.Cleanup(func() { ev.Remove(ctx) })
tf013beta3, err := ev.Install(ctx)
if err != nil {
t.Fatal(err)
}
for i, c := range []struct {
expected bool
min string
max string
binPath string
}{
{false, "0.12.27", "", tf01226},
{false, "0.12.26", "0.13.0", tf013beta3},
{true, "0.12.25", "", tf01226},
{true, "0.12.26", "0.13.0", tf01226},
{true, "", "0.12.27", tf01226},
{true, "0.12.26", "", tf013beta3},
{true, "0.13.0", "", tf013beta3},
{true, "0.13.0", "0.14.0", tf013beta3},
{true, "", "0.14.0", tf013beta3},
} {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
tf, err := NewTerraform(filepath.Dir(c.binPath), c.binPath)
if err != nil {
t.Fatal(err)
}
var min *version.Version
if c.min != "" {
min, err = version.NewVersion(c.min)
if err != nil {
t.Fatal(err)
}
}
var max *version.Version
if c.max != "" {
max, err = version.NewVersion(c.max)
if err != nil {
t.Fatal(err)
}
}
var mismatch *ErrVersionMismatch
err = tf.compatible(context.Background(), min, max)
switch {
case c.expected && err != nil:
t.Fatal(err)
case !c.expected && err == nil:
t.Fatal("expected version mismatch error, no error returned")
case !c.expected && !errors.As(err, &mismatch):
t.Fatal(err)
}
})
}
}
func TestExperimentsEnabled(t *testing.T) {
testCases := map[string]struct {
tfVersion *version.Version
expectedError error
}{
"experiments-enabled-in-1.9.0-alpha20240404": {
tfVersion: version.Must(version.NewVersion(testutil.Alpha_v1_9)),
},
"experiments-disabled-in-1.8.0-beta1": {
tfVersion: version.Must(version.NewVersion(testutil.Beta_v1_8)),
expectedError: errors.New("experiments are not enabled in version 1.8.0-beta1, as it's not an alpha or dev build"),
},
"experiments-disabled-in-1.5.3": {
tfVersion: version.Must(version.NewVersion(testutil.Latest_v1_5)),
expectedError: errors.New("experiments are not enabled in version 1.5.3, as it's not an alpha or dev build"),
},
}
for name, testCase := range testCases {
name, testCase := name, testCase
t.Run(name, func(t *testing.T) {
ev := &releases.ExactVersion{
Product: product.Terraform,
Version: testCase.tfVersion,
}
ev.SetLogger(testutil.TestLogger())
ctx := context.Background()
t.Cleanup(func() { ev.Remove(ctx) })
tfBinPath, err := ev.Install(ctx)
if err != nil {
t.Fatal(err)
}
tf, err := NewTerraform(filepath.Dir(tfBinPath), tfBinPath)
if err != nil {
t.Fatal(err)
}
err = tf.experimentsEnabled(context.Background())
if err != nil {
if testCase.expectedError == nil {
t.Fatalf("expected no error, got: %s", err)
}
if !strings.Contains(err.Error(), testCase.expectedError.Error()) {
t.Fatalf("expected error %q, got: %s", testCase.expectedError, err)
}
}
if err == nil && testCase.expectedError != nil {
t.Fatalf("got no error, expected: %s", testCase.expectedError)
}
})
}
}