2018-10-31 16:45:03 +01:00
|
|
|
package remote
|
|
|
|
|
|
|
|
import (
|
|
|
|
"reflect"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/backend"
|
2018-12-14 19:31:42 +01:00
|
|
|
"github.com/hashicorp/terraform/svchost/disco"
|
|
|
|
"github.com/hashicorp/terraform/version"
|
2018-10-31 16:45:03 +01:00
|
|
|
"github.com/zclconf/go-cty/cty"
|
2018-11-15 20:26:46 +01:00
|
|
|
|
|
|
|
backendLocal "github.com/hashicorp/terraform/backend/local"
|
2018-10-31 16:45:03 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestRemote(t *testing.T) {
|
|
|
|
var _ backend.Enhanced = New(nil)
|
|
|
|
var _ backend.CLI = New(nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_backendDefault(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
|
|
|
|
2018-10-31 16:45:03 +01:00
|
|
|
backend.TestBackendStates(t, b)
|
|
|
|
backend.TestBackendStateLocks(t, b, b)
|
|
|
|
backend.TestBackendStateForceUnlock(t, b, b)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_backendNoDefault(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendNoDefault(t)
|
|
|
|
defer bCleanup()
|
|
|
|
|
2018-10-31 16:45:03 +01:00
|
|
|
backend.TestBackendStates(t, b)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_config(t *testing.T) {
|
|
|
|
cases := map[string]struct {
|
|
|
|
config cty.Value
|
|
|
|
confErr string
|
|
|
|
valErr string
|
|
|
|
}{
|
2018-11-15 20:26:46 +01:00
|
|
|
"with_a_nonexisting_organization": {
|
|
|
|
config: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hostname": cty.NullVal(cty.String),
|
|
|
|
"organization": cty.StringVal("nonexisting"),
|
|
|
|
"token": cty.NullVal(cty.String),
|
|
|
|
"workspaces": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"name": cty.StringVal("prod"),
|
|
|
|
"prefix": cty.NullVal(cty.String),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
confErr: "organization nonexisting does not exist",
|
|
|
|
},
|
|
|
|
"with_an_unknown_host": {
|
|
|
|
config: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hostname": cty.StringVal("nonexisting.local"),
|
|
|
|
"organization": cty.StringVal("hashicorp"),
|
|
|
|
"token": cty.NullVal(cty.String),
|
|
|
|
"workspaces": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"name": cty.StringVal("prod"),
|
|
|
|
"prefix": cty.NullVal(cty.String),
|
|
|
|
}),
|
|
|
|
}),
|
2018-12-10 11:06:05 +01:00
|
|
|
confErr: "Failed to request discovery document",
|
2018-11-15 20:26:46 +01:00
|
|
|
},
|
2018-10-31 16:45:03 +01:00
|
|
|
"with_a_name": {
|
|
|
|
config: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hostname": cty.NullVal(cty.String),
|
|
|
|
"organization": cty.StringVal("hashicorp"),
|
|
|
|
"token": cty.NullVal(cty.String),
|
|
|
|
"workspaces": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"name": cty.StringVal("prod"),
|
|
|
|
"prefix": cty.NullVal(cty.String),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
"with_a_prefix": {
|
|
|
|
config: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hostname": cty.NullVal(cty.String),
|
|
|
|
"organization": cty.StringVal("hashicorp"),
|
|
|
|
"token": cty.NullVal(cty.String),
|
|
|
|
"workspaces": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"name": cty.NullVal(cty.String),
|
|
|
|
"prefix": cty.StringVal("my-app-"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
"without_either_a_name_and_a_prefix": {
|
|
|
|
config: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hostname": cty.NullVal(cty.String),
|
|
|
|
"organization": cty.StringVal("hashicorp"),
|
|
|
|
"token": cty.NullVal(cty.String),
|
|
|
|
"workspaces": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"name": cty.NullVal(cty.String),
|
|
|
|
"prefix": cty.NullVal(cty.String),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
valErr: `Either workspace "name" or "prefix" is required`,
|
|
|
|
},
|
|
|
|
"with_both_a_name_and_a_prefix": {
|
|
|
|
config: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hostname": cty.NullVal(cty.String),
|
|
|
|
"organization": cty.StringVal("hashicorp"),
|
|
|
|
"token": cty.NullVal(cty.String),
|
|
|
|
"workspaces": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"name": cty.StringVal("prod"),
|
|
|
|
"prefix": cty.StringVal("my-app-"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
valErr: `Only one of workspace "name" or "prefix" is allowed`,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, tc := range cases {
|
|
|
|
s := testServer(t)
|
|
|
|
b := New(testDisco(s))
|
|
|
|
|
|
|
|
// Validate
|
|
|
|
valDiags := b.ValidateConfig(tc.config)
|
2018-12-10 11:06:05 +01:00
|
|
|
if (valDiags.Err() != nil || tc.valErr != "") &&
|
|
|
|
(valDiags.Err() == nil || !strings.Contains(valDiags.Err().Error(), tc.valErr)) {
|
2018-10-31 16:45:03 +01:00
|
|
|
t.Fatalf("%s: unexpected validation result: %v", name, valDiags.Err())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Configure
|
|
|
|
confDiags := b.Configure(tc.config)
|
2018-12-13 23:12:36 +01:00
|
|
|
if (confDiags.Err() != nil || tc.confErr != "") &&
|
|
|
|
(confDiags.Err() == nil || !strings.Contains(confDiags.Err().Error(), tc.confErr)) {
|
2018-11-15 20:26:46 +01:00
|
|
|
t.Fatalf("%s: unexpected configure result: %v", name, confDiags.Err())
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-15 21:28:35 +01:00
|
|
|
func TestRemote_versionConstraints(t *testing.T) {
|
|
|
|
cases := map[string]struct {
|
|
|
|
config cty.Value
|
|
|
|
prerelease string
|
|
|
|
version string
|
|
|
|
result string
|
|
|
|
}{
|
|
|
|
"compatible version": {
|
|
|
|
config: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hostname": cty.NullVal(cty.String),
|
|
|
|
"organization": cty.StringVal("hashicorp"),
|
|
|
|
"token": cty.NullVal(cty.String),
|
|
|
|
"workspaces": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"name": cty.StringVal("prod"),
|
|
|
|
"prefix": cty.NullVal(cty.String),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
version: "0.11.1",
|
|
|
|
},
|
|
|
|
"version too old": {
|
|
|
|
config: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hostname": cty.NullVal(cty.String),
|
|
|
|
"organization": cty.StringVal("hashicorp"),
|
|
|
|
"token": cty.NullVal(cty.String),
|
|
|
|
"workspaces": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"name": cty.StringVal("prod"),
|
|
|
|
"prefix": cty.NullVal(cty.String),
|
|
|
|
}),
|
|
|
|
}),
|
2019-02-06 09:36:42 +01:00
|
|
|
version: "0.0.1",
|
|
|
|
result: "upgrade Terraform to >= 0.1.0",
|
2018-12-15 21:28:35 +01:00
|
|
|
},
|
|
|
|
"version too new": {
|
|
|
|
config: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hostname": cty.NullVal(cty.String),
|
|
|
|
"organization": cty.StringVal("hashicorp"),
|
|
|
|
"token": cty.NullVal(cty.String),
|
|
|
|
"workspaces": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"name": cty.StringVal("prod"),
|
|
|
|
"prefix": cty.NullVal(cty.String),
|
|
|
|
}),
|
|
|
|
}),
|
2019-02-06 09:36:42 +01:00
|
|
|
version: "10.0.1",
|
|
|
|
result: "downgrade Terraform to <= 10.0.0",
|
2018-12-15 21:28:35 +01:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save and restore the actual version.
|
|
|
|
p := version.Prerelease
|
|
|
|
v := version.Version
|
|
|
|
defer func() {
|
|
|
|
version.Prerelease = p
|
|
|
|
version.Version = v
|
|
|
|
}()
|
|
|
|
|
|
|
|
for name, tc := range cases {
|
|
|
|
s := testServer(t)
|
|
|
|
b := New(testDisco(s))
|
|
|
|
|
|
|
|
// Set the version for this test.
|
|
|
|
version.Prerelease = tc.prerelease
|
|
|
|
version.Version = tc.version
|
|
|
|
|
|
|
|
// Validate
|
|
|
|
valDiags := b.ValidateConfig(tc.config)
|
|
|
|
if valDiags.HasErrors() {
|
|
|
|
t.Fatalf("%s: unexpected validation result: %v", name, valDiags.Err())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Configure
|
|
|
|
confDiags := b.Configure(tc.config)
|
|
|
|
if (confDiags.Err() != nil || tc.result != "") &&
|
|
|
|
(confDiags.Err() == nil || !strings.Contains(confDiags.Err().Error(), tc.result)) {
|
|
|
|
t.Fatalf("%s: unexpected configure result: %v", name, confDiags.Err())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-15 20:26:46 +01:00
|
|
|
func TestRemote_localBackend(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
2018-10-31 16:45:03 +01:00
|
|
|
|
2018-11-15 20:26:46 +01:00
|
|
|
local, ok := b.local.(*backendLocal.Local)
|
|
|
|
if !ok {
|
|
|
|
t.Fatalf("expected b.local to be \"*local.Local\", got: %T", b.local)
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
|
|
|
|
2018-11-15 20:26:46 +01:00
|
|
|
remote, ok := local.Backend.(*Remote)
|
|
|
|
if !ok {
|
|
|
|
t.Fatalf("expected local.Backend to be *remote.Remote, got: %T", remote)
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_addAndRemoveWorkspacesDefault(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
|
|
|
|
2018-10-31 16:45:03 +01:00
|
|
|
if _, err := b.Workspaces(); err != backend.ErrWorkspacesNotSupported {
|
|
|
|
t.Fatalf("expected error %v, got %v", backend.ErrWorkspacesNotSupported, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := b.StateMgr(backend.DefaultStateName); err != nil {
|
|
|
|
t.Fatalf("expected no error, got %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := b.StateMgr("prod"); err != backend.ErrWorkspacesNotSupported {
|
|
|
|
t.Fatalf("expected error %v, got %v", backend.ErrWorkspacesNotSupported, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := b.DeleteWorkspace(backend.DefaultStateName); err != nil {
|
|
|
|
t.Fatalf("expected no error, got %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := b.DeleteWorkspace("prod"); err != backend.ErrWorkspacesNotSupported {
|
|
|
|
t.Fatalf("expected error %v, got %v", backend.ErrWorkspacesNotSupported, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_addAndRemoveWorkspacesNoDefault(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendNoDefault(t)
|
|
|
|
defer bCleanup()
|
|
|
|
|
2018-10-31 16:45:03 +01:00
|
|
|
states, err := b.Workspaces()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expectedWorkspaces := []string(nil)
|
|
|
|
if !reflect.DeepEqual(states, expectedWorkspaces) {
|
|
|
|
t.Fatalf("expected states %#+v, got %#+v", expectedWorkspaces, states)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := b.StateMgr(backend.DefaultStateName); err != backend.ErrDefaultWorkspaceNotSupported {
|
|
|
|
t.Fatalf("expected error %v, got %v", backend.ErrDefaultWorkspaceNotSupported, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expectedA := "test_A"
|
|
|
|
if _, err := b.StateMgr(expectedA); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
states, err = b.Workspaces()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expectedWorkspaces = append(expectedWorkspaces, expectedA)
|
|
|
|
if !reflect.DeepEqual(states, expectedWorkspaces) {
|
|
|
|
t.Fatalf("expected %#+v, got %#+v", expectedWorkspaces, states)
|
|
|
|
}
|
|
|
|
|
|
|
|
expectedB := "test_B"
|
|
|
|
if _, err := b.StateMgr(expectedB); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
states, err = b.Workspaces()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expectedWorkspaces = append(expectedWorkspaces, expectedB)
|
|
|
|
if !reflect.DeepEqual(states, expectedWorkspaces) {
|
|
|
|
t.Fatalf("expected %#+v, got %#+v", expectedWorkspaces, states)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := b.DeleteWorkspace(backend.DefaultStateName); err != backend.ErrDefaultWorkspaceNotSupported {
|
|
|
|
t.Fatalf("expected error %v, got %v", backend.ErrDefaultWorkspaceNotSupported, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := b.DeleteWorkspace(expectedA); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
states, err = b.Workspaces()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expectedWorkspaces = []string{expectedB}
|
|
|
|
if !reflect.DeepEqual(states, expectedWorkspaces) {
|
|
|
|
t.Fatalf("expected %#+v got %#+v", expectedWorkspaces, states)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := b.DeleteWorkspace(expectedB); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
states, err = b.Workspaces()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expectedWorkspaces = []string(nil)
|
|
|
|
if !reflect.DeepEqual(states, expectedWorkspaces) {
|
|
|
|
t.Fatalf("expected %#+v, got %#+v", expectedWorkspaces, states)
|
|
|
|
}
|
|
|
|
}
|
2018-12-14 19:31:42 +01:00
|
|
|
|
|
|
|
func TestRemote_checkConstraints(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
2018-12-14 19:31:42 +01:00
|
|
|
|
|
|
|
cases := map[string]struct {
|
|
|
|
constraints *disco.Constraints
|
|
|
|
prerelease string
|
|
|
|
version string
|
|
|
|
result string
|
|
|
|
}{
|
|
|
|
"compatible version": {
|
|
|
|
constraints: &disco.Constraints{
|
|
|
|
Minimum: "0.11.0",
|
|
|
|
Maximum: "0.11.11",
|
|
|
|
},
|
|
|
|
version: "0.11.1",
|
|
|
|
result: "",
|
|
|
|
},
|
|
|
|
"version too old": {
|
|
|
|
constraints: &disco.Constraints{
|
|
|
|
Minimum: "0.11.0",
|
|
|
|
Maximum: "0.11.11",
|
|
|
|
},
|
|
|
|
version: "0.10.1",
|
|
|
|
result: "upgrade Terraform to >= 0.11.0",
|
|
|
|
},
|
|
|
|
"version too new": {
|
|
|
|
constraints: &disco.Constraints{
|
|
|
|
Minimum: "0.11.0",
|
|
|
|
Maximum: "0.11.11",
|
|
|
|
},
|
|
|
|
version: "0.12.0",
|
|
|
|
result: "downgrade Terraform to <= 0.11.11",
|
|
|
|
},
|
|
|
|
"version excluded - ordered": {
|
|
|
|
constraints: &disco.Constraints{
|
|
|
|
Minimum: "0.11.0",
|
|
|
|
Excluding: []string{"0.11.7", "0.11.8"},
|
|
|
|
Maximum: "0.11.11",
|
|
|
|
},
|
|
|
|
version: "0.11.7",
|
|
|
|
result: "upgrade Terraform to > 0.11.8",
|
|
|
|
},
|
|
|
|
"version excluded - unordered": {
|
|
|
|
constraints: &disco.Constraints{
|
|
|
|
Minimum: "0.11.0",
|
|
|
|
Excluding: []string{"0.11.8", "0.11.6"},
|
|
|
|
Maximum: "0.11.11",
|
|
|
|
},
|
|
|
|
version: "0.11.6",
|
|
|
|
result: "upgrade Terraform to > 0.11.8",
|
|
|
|
},
|
|
|
|
"list versions": {
|
|
|
|
constraints: &disco.Constraints{
|
|
|
|
Minimum: "0.11.0",
|
|
|
|
Maximum: "0.11.11",
|
|
|
|
},
|
|
|
|
version: "0.10.1",
|
2018-12-19 17:28:32 +01:00
|
|
|
result: "versions >= 0.11.0, <= 0.11.11.",
|
2018-12-14 19:31:42 +01:00
|
|
|
},
|
|
|
|
"list exclusion": {
|
|
|
|
constraints: &disco.Constraints{
|
|
|
|
Minimum: "0.11.0",
|
|
|
|
Excluding: []string{"0.11.6"},
|
|
|
|
Maximum: "0.11.11",
|
|
|
|
},
|
|
|
|
version: "0.11.6",
|
|
|
|
result: "excluding version 0.11.6.",
|
|
|
|
},
|
|
|
|
"list exclusions": {
|
|
|
|
constraints: &disco.Constraints{
|
|
|
|
Minimum: "0.11.0",
|
|
|
|
Excluding: []string{"0.11.8", "0.11.6"},
|
|
|
|
Maximum: "0.11.11",
|
|
|
|
},
|
|
|
|
version: "0.11.6",
|
|
|
|
result: "excluding versions 0.11.6, 0.11.8.",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2018-12-15 21:28:35 +01:00
|
|
|
// Save and restore the actual version.
|
|
|
|
p := version.Prerelease
|
|
|
|
v := version.Version
|
|
|
|
defer func() {
|
|
|
|
version.Prerelease = p
|
|
|
|
version.Version = v
|
|
|
|
}()
|
|
|
|
|
2018-12-14 19:31:42 +01:00
|
|
|
for name, tc := range cases {
|
2018-12-15 21:28:35 +01:00
|
|
|
// Set the version for this test.
|
2018-12-14 19:31:42 +01:00
|
|
|
version.Prerelease = tc.prerelease
|
|
|
|
version.Version = tc.version
|
|
|
|
|
|
|
|
// Check the constraints.
|
|
|
|
diags := b.checkConstraints(tc.constraints)
|
|
|
|
if (diags.Err() != nil || tc.result != "") &&
|
|
|
|
(diags.Err() == nil || !strings.Contains(diags.Err().Error(), tc.result)) {
|
|
|
|
t.Fatalf("%s: unexpected constraints result: %v", name, diags.Err())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|