github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/backend/remote/backend_test.go (about)

     1  package remote
     2  
     3  import (
     4  	"reflect"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/hashicorp/terraform-svchost/disco"
     9  	"github.com/hashicorp/terraform/backend"
    10  	"github.com/hashicorp/terraform/version"
    11  	"github.com/zclconf/go-cty/cty"
    12  
    13  	backendLocal "github.com/hashicorp/terraform/backend/local"
    14  )
    15  
    16  func TestRemote(t *testing.T) {
    17  	var _ backend.Enhanced = New(nil)
    18  	var _ backend.CLI = New(nil)
    19  }
    20  
    21  func TestRemote_backendDefault(t *testing.T) {
    22  	b, bCleanup := testBackendDefault(t)
    23  	defer bCleanup()
    24  
    25  	backend.TestBackendStates(t, b)
    26  	backend.TestBackendStateLocks(t, b, b)
    27  	backend.TestBackendStateForceUnlock(t, b, b)
    28  }
    29  
    30  func TestRemote_backendNoDefault(t *testing.T) {
    31  	b, bCleanup := testBackendNoDefault(t)
    32  	defer bCleanup()
    33  
    34  	backend.TestBackendStates(t, b)
    35  }
    36  
    37  func TestRemote_config(t *testing.T) {
    38  	cases := map[string]struct {
    39  		config  cty.Value
    40  		confErr string
    41  		valErr  string
    42  	}{
    43  		"with_a_nonexisting_organization": {
    44  			config: cty.ObjectVal(map[string]cty.Value{
    45  				"hostname":     cty.NullVal(cty.String),
    46  				"organization": cty.StringVal("nonexisting"),
    47  				"token":        cty.NullVal(cty.String),
    48  				"workspaces": cty.ObjectVal(map[string]cty.Value{
    49  					"name":   cty.StringVal("prod"),
    50  					"prefix": cty.NullVal(cty.String),
    51  				}),
    52  			}),
    53  			confErr: "organization nonexisting does not exist",
    54  		},
    55  		"with_an_unknown_host": {
    56  			config: cty.ObjectVal(map[string]cty.Value{
    57  				"hostname":     cty.StringVal("nonexisting.local"),
    58  				"organization": cty.StringVal("hashicorp"),
    59  				"token":        cty.NullVal(cty.String),
    60  				"workspaces": cty.ObjectVal(map[string]cty.Value{
    61  					"name":   cty.StringVal("prod"),
    62  					"prefix": cty.NullVal(cty.String),
    63  				}),
    64  			}),
    65  			confErr: "Failed to request discovery document",
    66  		},
    67  		// localhost advertises TFE services, but has no token in the credentials
    68  		"without_a_token": {
    69  			config: cty.ObjectVal(map[string]cty.Value{
    70  				"hostname":     cty.StringVal("localhost"),
    71  				"organization": cty.StringVal("hashicorp"),
    72  				"token":        cty.NullVal(cty.String),
    73  				"workspaces": cty.ObjectVal(map[string]cty.Value{
    74  					"name":   cty.StringVal("prod"),
    75  					"prefix": cty.NullVal(cty.String),
    76  				}),
    77  			}),
    78  			confErr: "terraform login localhost",
    79  		},
    80  		"with_a_name": {
    81  			config: cty.ObjectVal(map[string]cty.Value{
    82  				"hostname":     cty.NullVal(cty.String),
    83  				"organization": cty.StringVal("hashicorp"),
    84  				"token":        cty.NullVal(cty.String),
    85  				"workspaces": cty.ObjectVal(map[string]cty.Value{
    86  					"name":   cty.StringVal("prod"),
    87  					"prefix": cty.NullVal(cty.String),
    88  				}),
    89  			}),
    90  		},
    91  		"with_a_prefix": {
    92  			config: cty.ObjectVal(map[string]cty.Value{
    93  				"hostname":     cty.NullVal(cty.String),
    94  				"organization": cty.StringVal("hashicorp"),
    95  				"token":        cty.NullVal(cty.String),
    96  				"workspaces": cty.ObjectVal(map[string]cty.Value{
    97  					"name":   cty.NullVal(cty.String),
    98  					"prefix": cty.StringVal("my-app-"),
    99  				}),
   100  			}),
   101  		},
   102  		"without_either_a_name_and_a_prefix": {
   103  			config: cty.ObjectVal(map[string]cty.Value{
   104  				"hostname":     cty.NullVal(cty.String),
   105  				"organization": cty.StringVal("hashicorp"),
   106  				"token":        cty.NullVal(cty.String),
   107  				"workspaces": cty.ObjectVal(map[string]cty.Value{
   108  					"name":   cty.NullVal(cty.String),
   109  					"prefix": cty.NullVal(cty.String),
   110  				}),
   111  			}),
   112  			valErr: `Either workspace "name" or "prefix" is required`,
   113  		},
   114  		"with_both_a_name_and_a_prefix": {
   115  			config: cty.ObjectVal(map[string]cty.Value{
   116  				"hostname":     cty.NullVal(cty.String),
   117  				"organization": cty.StringVal("hashicorp"),
   118  				"token":        cty.NullVal(cty.String),
   119  				"workspaces": cty.ObjectVal(map[string]cty.Value{
   120  					"name":   cty.StringVal("prod"),
   121  					"prefix": cty.StringVal("my-app-"),
   122  				}),
   123  			}),
   124  			valErr: `Only one of workspace "name" or "prefix" is allowed`,
   125  		},
   126  	}
   127  
   128  	for name, tc := range cases {
   129  		s := testServer(t)
   130  		b := New(testDisco(s))
   131  
   132  		// Validate
   133  		_, valDiags := b.PrepareConfig(tc.config)
   134  		if (valDiags.Err() != nil || tc.valErr != "") &&
   135  			(valDiags.Err() == nil || !strings.Contains(valDiags.Err().Error(), tc.valErr)) {
   136  			t.Fatalf("%s: unexpected validation result: %v", name, valDiags.Err())
   137  		}
   138  
   139  		// Configure
   140  		confDiags := b.Configure(tc.config)
   141  		if (confDiags.Err() != nil || tc.confErr != "") &&
   142  			(confDiags.Err() == nil || !strings.Contains(confDiags.Err().Error(), tc.confErr)) {
   143  			t.Fatalf("%s: unexpected configure result: %v", name, confDiags.Err())
   144  		}
   145  	}
   146  }
   147  
   148  func TestRemote_versionConstraints(t *testing.T) {
   149  	cases := map[string]struct {
   150  		config     cty.Value
   151  		prerelease string
   152  		version    string
   153  		result     string
   154  	}{
   155  		"compatible version": {
   156  			config: cty.ObjectVal(map[string]cty.Value{
   157  				"hostname":     cty.NullVal(cty.String),
   158  				"organization": cty.StringVal("hashicorp"),
   159  				"token":        cty.NullVal(cty.String),
   160  				"workspaces": cty.ObjectVal(map[string]cty.Value{
   161  					"name":   cty.StringVal("prod"),
   162  					"prefix": cty.NullVal(cty.String),
   163  				}),
   164  			}),
   165  			version: "0.11.1",
   166  		},
   167  		"version too old": {
   168  			config: cty.ObjectVal(map[string]cty.Value{
   169  				"hostname":     cty.NullVal(cty.String),
   170  				"organization": cty.StringVal("hashicorp"),
   171  				"token":        cty.NullVal(cty.String),
   172  				"workspaces": cty.ObjectVal(map[string]cty.Value{
   173  					"name":   cty.StringVal("prod"),
   174  					"prefix": cty.NullVal(cty.String),
   175  				}),
   176  			}),
   177  			version: "0.0.1",
   178  			result:  "upgrade Terraform to >= 0.1.0",
   179  		},
   180  		"version too new": {
   181  			config: cty.ObjectVal(map[string]cty.Value{
   182  				"hostname":     cty.NullVal(cty.String),
   183  				"organization": cty.StringVal("hashicorp"),
   184  				"token":        cty.NullVal(cty.String),
   185  				"workspaces": cty.ObjectVal(map[string]cty.Value{
   186  					"name":   cty.StringVal("prod"),
   187  					"prefix": cty.NullVal(cty.String),
   188  				}),
   189  			}),
   190  			version: "10.0.1",
   191  			result:  "downgrade Terraform to <= 10.0.0",
   192  		},
   193  	}
   194  
   195  	// Save and restore the actual version.
   196  	p := version.Prerelease
   197  	v := version.Version
   198  	defer func() {
   199  		version.Prerelease = p
   200  		version.Version = v
   201  	}()
   202  
   203  	for name, tc := range cases {
   204  		s := testServer(t)
   205  		b := New(testDisco(s))
   206  
   207  		// Set the version for this test.
   208  		version.Prerelease = tc.prerelease
   209  		version.Version = tc.version
   210  
   211  		// Validate
   212  		_, valDiags := b.PrepareConfig(tc.config)
   213  		if valDiags.HasErrors() {
   214  			t.Fatalf("%s: unexpected validation result: %v", name, valDiags.Err())
   215  		}
   216  
   217  		// Configure
   218  		confDiags := b.Configure(tc.config)
   219  		if (confDiags.Err() != nil || tc.result != "") &&
   220  			(confDiags.Err() == nil || !strings.Contains(confDiags.Err().Error(), tc.result)) {
   221  			t.Fatalf("%s: unexpected configure result: %v", name, confDiags.Err())
   222  		}
   223  	}
   224  }
   225  
   226  func TestRemote_localBackend(t *testing.T) {
   227  	b, bCleanup := testBackendDefault(t)
   228  	defer bCleanup()
   229  
   230  	local, ok := b.local.(*backendLocal.Local)
   231  	if !ok {
   232  		t.Fatalf("expected b.local to be \"*local.Local\", got: %T", b.local)
   233  	}
   234  
   235  	remote, ok := local.Backend.(*Remote)
   236  	if !ok {
   237  		t.Fatalf("expected local.Backend to be *remote.Remote, got: %T", remote)
   238  	}
   239  }
   240  
   241  func TestRemote_addAndRemoveWorkspacesDefault(t *testing.T) {
   242  	b, bCleanup := testBackendDefault(t)
   243  	defer bCleanup()
   244  
   245  	if _, err := b.Workspaces(); err != backend.ErrWorkspacesNotSupported {
   246  		t.Fatalf("expected error %v, got %v", backend.ErrWorkspacesNotSupported, err)
   247  	}
   248  
   249  	if _, err := b.StateMgr(backend.DefaultStateName); err != nil {
   250  		t.Fatalf("expected no error, got %v", err)
   251  	}
   252  
   253  	if _, err := b.StateMgr("prod"); err != backend.ErrWorkspacesNotSupported {
   254  		t.Fatalf("expected error %v, got %v", backend.ErrWorkspacesNotSupported, err)
   255  	}
   256  
   257  	if err := b.DeleteWorkspace(backend.DefaultStateName); err != nil {
   258  		t.Fatalf("expected no error, got %v", err)
   259  	}
   260  
   261  	if err := b.DeleteWorkspace("prod"); err != backend.ErrWorkspacesNotSupported {
   262  		t.Fatalf("expected error %v, got %v", backend.ErrWorkspacesNotSupported, err)
   263  	}
   264  }
   265  
   266  func TestRemote_addAndRemoveWorkspacesNoDefault(t *testing.T) {
   267  	b, bCleanup := testBackendNoDefault(t)
   268  	defer bCleanup()
   269  
   270  	states, err := b.Workspaces()
   271  	if err != nil {
   272  		t.Fatal(err)
   273  	}
   274  
   275  	expectedWorkspaces := []string(nil)
   276  	if !reflect.DeepEqual(states, expectedWorkspaces) {
   277  		t.Fatalf("expected states %#+v, got %#+v", expectedWorkspaces, states)
   278  	}
   279  
   280  	if _, err := b.StateMgr(backend.DefaultStateName); err != backend.ErrDefaultWorkspaceNotSupported {
   281  		t.Fatalf("expected error %v, got %v", backend.ErrDefaultWorkspaceNotSupported, err)
   282  	}
   283  
   284  	expectedA := "test_A"
   285  	if _, err := b.StateMgr(expectedA); err != nil {
   286  		t.Fatal(err)
   287  	}
   288  
   289  	states, err = b.Workspaces()
   290  	if err != nil {
   291  		t.Fatal(err)
   292  	}
   293  
   294  	expectedWorkspaces = append(expectedWorkspaces, expectedA)
   295  	if !reflect.DeepEqual(states, expectedWorkspaces) {
   296  		t.Fatalf("expected %#+v, got %#+v", expectedWorkspaces, states)
   297  	}
   298  
   299  	expectedB := "test_B"
   300  	if _, err := b.StateMgr(expectedB); err != nil {
   301  		t.Fatal(err)
   302  	}
   303  
   304  	states, err = b.Workspaces()
   305  	if err != nil {
   306  		t.Fatal(err)
   307  	}
   308  
   309  	expectedWorkspaces = append(expectedWorkspaces, expectedB)
   310  	if !reflect.DeepEqual(states, expectedWorkspaces) {
   311  		t.Fatalf("expected %#+v, got %#+v", expectedWorkspaces, states)
   312  	}
   313  
   314  	if err := b.DeleteWorkspace(backend.DefaultStateName); err != backend.ErrDefaultWorkspaceNotSupported {
   315  		t.Fatalf("expected error %v, got %v", backend.ErrDefaultWorkspaceNotSupported, err)
   316  	}
   317  
   318  	if err := b.DeleteWorkspace(expectedA); err != nil {
   319  		t.Fatal(err)
   320  	}
   321  
   322  	states, err = b.Workspaces()
   323  	if err != nil {
   324  		t.Fatal(err)
   325  	}
   326  
   327  	expectedWorkspaces = []string{expectedB}
   328  	if !reflect.DeepEqual(states, expectedWorkspaces) {
   329  		t.Fatalf("expected %#+v got %#+v", expectedWorkspaces, states)
   330  	}
   331  
   332  	if err := b.DeleteWorkspace(expectedB); err != nil {
   333  		t.Fatal(err)
   334  	}
   335  
   336  	states, err = b.Workspaces()
   337  	if err != nil {
   338  		t.Fatal(err)
   339  	}
   340  
   341  	expectedWorkspaces = []string(nil)
   342  	if !reflect.DeepEqual(states, expectedWorkspaces) {
   343  		t.Fatalf("expected %#+v, got %#+v", expectedWorkspaces, states)
   344  	}
   345  }
   346  
   347  func TestRemote_checkConstraints(t *testing.T) {
   348  	b, bCleanup := testBackendDefault(t)
   349  	defer bCleanup()
   350  
   351  	cases := map[string]struct {
   352  		constraints *disco.Constraints
   353  		prerelease  string
   354  		version     string
   355  		result      string
   356  	}{
   357  		"compatible version": {
   358  			constraints: &disco.Constraints{
   359  				Minimum: "0.11.0",
   360  				Maximum: "0.11.11",
   361  			},
   362  			version: "0.11.1",
   363  			result:  "",
   364  		},
   365  		"version too old": {
   366  			constraints: &disco.Constraints{
   367  				Minimum: "0.11.0",
   368  				Maximum: "0.11.11",
   369  			},
   370  			version: "0.10.1",
   371  			result:  "upgrade Terraform to >= 0.11.0",
   372  		},
   373  		"version too new": {
   374  			constraints: &disco.Constraints{
   375  				Minimum: "0.11.0",
   376  				Maximum: "0.11.11",
   377  			},
   378  			version: "0.12.0",
   379  			result:  "downgrade Terraform to <= 0.11.11",
   380  		},
   381  		"version excluded - ordered": {
   382  			constraints: &disco.Constraints{
   383  				Minimum:   "0.11.0",
   384  				Excluding: []string{"0.11.7", "0.11.8"},
   385  				Maximum:   "0.11.11",
   386  			},
   387  			version: "0.11.7",
   388  			result:  "upgrade Terraform to > 0.11.8",
   389  		},
   390  		"version excluded - unordered": {
   391  			constraints: &disco.Constraints{
   392  				Minimum:   "0.11.0",
   393  				Excluding: []string{"0.11.8", "0.11.6"},
   394  				Maximum:   "0.11.11",
   395  			},
   396  			version: "0.11.6",
   397  			result:  "upgrade Terraform to > 0.11.8",
   398  		},
   399  		"list versions": {
   400  			constraints: &disco.Constraints{
   401  				Minimum: "0.11.0",
   402  				Maximum: "0.11.11",
   403  			},
   404  			version: "0.10.1",
   405  			result:  "versions >= 0.11.0, <= 0.11.11.",
   406  		},
   407  		"list exclusion": {
   408  			constraints: &disco.Constraints{
   409  				Minimum:   "0.11.0",
   410  				Excluding: []string{"0.11.6"},
   411  				Maximum:   "0.11.11",
   412  			},
   413  			version: "0.11.6",
   414  			result:  "excluding version 0.11.6.",
   415  		},
   416  		"list exclusions": {
   417  			constraints: &disco.Constraints{
   418  				Minimum:   "0.11.0",
   419  				Excluding: []string{"0.11.8", "0.11.6"},
   420  				Maximum:   "0.11.11",
   421  			},
   422  			version: "0.11.6",
   423  			result:  "excluding versions 0.11.6, 0.11.8.",
   424  		},
   425  	}
   426  
   427  	// Save and restore the actual version.
   428  	p := version.Prerelease
   429  	v := version.Version
   430  	defer func() {
   431  		version.Prerelease = p
   432  		version.Version = v
   433  	}()
   434  
   435  	for name, tc := range cases {
   436  		// Set the version for this test.
   437  		version.Prerelease = tc.prerelease
   438  		version.Version = tc.version
   439  
   440  		// Check the constraints.
   441  		diags := b.checkConstraints(tc.constraints)
   442  		if (diags.Err() != nil || tc.result != "") &&
   443  			(diags.Err() == nil || !strings.Contains(diags.Err().Error(), tc.result)) {
   444  			t.Fatalf("%s: unexpected constraints result: %v", name, diags.Err())
   445  		}
   446  	}
   447  }