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 }