github.com/hugorut/terraform@v1.1.3/src/cloud/e2e/migrate_state_multi_to_tfc_test.go (about) 1 package main 2 3 import ( 4 "context" 5 "io/ioutil" 6 "os" 7 "testing" 8 9 expect "github.com/Netflix/go-expect" 10 tfe "github.com/hashicorp/go-tfe" 11 "github.com/hugorut/terraform/src/e2e" 12 tfversion "github.com/hugorut/terraform/version" 13 ) 14 15 func Test_migrate_multi_to_tfc_cloud_name_strategy(t *testing.T) { 16 skipIfMissingEnvVar(t) 17 skipWithoutRemoteTerraformVersion(t) 18 19 ctx := context.Background() 20 21 cases := map[string]struct { 22 operations []operationSets 23 validations func(t *testing.T, orgName string) 24 }{ 25 "migrating multiple workspaces to cloud using name strategy; current workspace is 'default'": { 26 operations: []operationSets{ 27 { 28 prep: func(t *testing.T, orgName, dir string) { 29 tfBlock := terraformConfigLocalBackend() 30 writeMainTF(t, tfBlock, dir) 31 }, 32 commands: []tfCommand{ 33 { 34 command: []string{"init"}, 35 expectedCmdOutput: `Successfully configured the backend "local"!`, 36 }, 37 { 38 command: []string{"apply", "-auto-approve"}, 39 postInputOutput: []string{`Apply complete!`}, 40 }, 41 { 42 command: []string{"workspace", "new", "prod"}, 43 expectedCmdOutput: `Created and switched to workspace "prod"!`, 44 }, 45 { 46 command: []string{"apply", "-auto-approve"}, 47 postInputOutput: []string{`Apply complete!`}, 48 }, 49 { 50 command: []string{"workspace", "select", "default"}, 51 expectedCmdOutput: `Switched to workspace "default".`, 52 }, 53 }, 54 }, 55 { 56 prep: func(t *testing.T, orgName, dir string) { 57 wsName := "new-workspace" 58 tfBlock := terraformConfigCloudBackendName(orgName, wsName) 59 writeMainTF(t, tfBlock, dir) 60 }, 61 commands: []tfCommand{ 62 { 63 command: []string{"init"}, 64 expectedCmdOutput: `Do you want to copy only your current workspace?`, 65 userInput: []string{"yes"}, 66 postInputOutput: []string{`Terraform Cloud has been successfully initialized!`}, 67 }, 68 { 69 command: []string{"workspace", "show"}, 70 expectedCmdOutput: `new-workspace`, // this comes from the `prep` function 71 }, 72 { 73 command: []string{"output"}, 74 expectedCmdOutput: `val = "default"`, // this was the output of the current workspace selected before migration 75 }, 76 }, 77 }, 78 }, 79 validations: func(t *testing.T, orgName string) { 80 wsList, err := tfeClient.Workspaces.List(ctx, orgName, tfe.WorkspaceListOptions{}) 81 if err != nil { 82 t.Fatal(err) 83 } 84 if len(wsList.Items) != 1 { 85 t.Fatalf("Expected the number of workspaces to be 1, but got %d", len(wsList.Items)) 86 } 87 ws := wsList.Items[0] 88 // this workspace name is what exists in the cloud backend configuration block 89 if ws.Name != "new-workspace" { 90 t.Fatalf("Expected workspace to be `new-workspace`, but is %s", ws.Name) 91 } 92 }, 93 }, 94 "migrating multiple workspaces to cloud using name strategy; current workspace is 'prod'": { 95 operations: []operationSets{ 96 { 97 prep: func(t *testing.T, orgName, dir string) { 98 tfBlock := terraformConfigLocalBackend() 99 writeMainTF(t, tfBlock, dir) 100 }, 101 commands: []tfCommand{ 102 { 103 command: []string{"init"}, 104 expectedCmdOutput: `Successfully configured the backend "local"!`, 105 }, 106 { 107 command: []string{"apply", "-auto-approve"}, 108 postInputOutput: []string{`Apply complete!`}, 109 }, 110 { 111 command: []string{"workspace", "new", "prod"}, 112 expectedCmdOutput: `Created and switched to workspace "prod"!`, 113 }, 114 { 115 command: []string{"apply", "-auto-approve"}, 116 postInputOutput: []string{`Apply complete!`}, 117 }, 118 }, 119 }, 120 { 121 prep: func(t *testing.T, orgName, dir string) { 122 wsName := "new-workspace" 123 tfBlock := terraformConfigCloudBackendName(orgName, wsName) 124 writeMainTF(t, tfBlock, dir) 125 }, 126 commands: []tfCommand{ 127 { 128 command: []string{"init"}, 129 expectedCmdOutput: `Do you want to copy only your current workspace?`, 130 userInput: []string{"yes"}, 131 postInputOutput: []string{`Terraform Cloud has been successfully initialized!`}, 132 }, 133 { 134 command: []string{"workspace", "list"}, 135 expectedCmdOutput: `new-workspace`, // this comes from the `prep` function 136 }, 137 { 138 command: []string{"output"}, 139 expectedCmdOutput: `val = "prod"`, 140 }, 141 }, 142 }, 143 }, 144 validations: func(t *testing.T, orgName string) { 145 wsList, err := tfeClient.Workspaces.List(ctx, orgName, tfe.WorkspaceListOptions{}) 146 if err != nil { 147 t.Fatal(err) 148 } 149 ws := wsList.Items[0] 150 // this workspace name is what exists in the cloud backend configuration block 151 if ws.Name != "new-workspace" { 152 t.Fatalf("Expected workspace to be `new-workspace`, but is %s", ws.Name) 153 } 154 }, 155 }, 156 "migrating multiple workspaces to cloud using name strategy; 'default' workspace is empty": { 157 operations: []operationSets{ 158 { 159 prep: func(t *testing.T, orgName, dir string) { 160 tfBlock := terraformConfigLocalBackend() 161 writeMainTF(t, tfBlock, dir) 162 }, 163 commands: []tfCommand{ 164 { 165 command: []string{"init"}, 166 expectedCmdOutput: `Successfully configured the backend "local"!`, 167 }, 168 { 169 command: []string{"workspace", "new", "workspace1"}, 170 expectedCmdOutput: `Created and switched to workspace "workspace1"!`, 171 }, 172 { 173 command: []string{"apply", "-auto-approve"}, 174 postInputOutput: []string{`Apply complete!`}, 175 }, 176 { 177 command: []string{"workspace", "new", "workspace2"}, 178 expectedCmdOutput: `Created and switched to workspace "workspace2"!`, 179 }, 180 { 181 command: []string{"apply", "-auto-approve"}, 182 postInputOutput: []string{`Apply complete!`}, 183 }, 184 }, 185 }, 186 { 187 prep: func(t *testing.T, orgName, dir string) { 188 wsName := "new-workspace" 189 tfBlock := terraformConfigCloudBackendName(orgName, wsName) 190 writeMainTF(t, tfBlock, dir) 191 }, 192 commands: []tfCommand{ 193 { 194 command: []string{"init"}, 195 expectedCmdOutput: `Do you want to copy only your current workspace?`, 196 userInput: []string{"yes"}, 197 postInputOutput: []string{`Terraform Cloud has been successfully initialized!`}, 198 }, 199 { 200 command: []string{"workspace", "select", "default"}, 201 expectError: true, 202 }, 203 { 204 command: []string{"output"}, 205 expectedCmdOutput: `val = "workspace2"`, // this was the output of the current workspace selected before migration 206 }, 207 }, 208 }, 209 }, 210 validations: func(t *testing.T, orgName string) { 211 wsList, err := tfeClient.Workspaces.List(ctx, orgName, tfe.WorkspaceListOptions{}) 212 if err != nil { 213 t.Fatal(err) 214 } 215 if len(wsList.Items) != 1 { 216 t.Fatalf("Expected the number of workspaces to be 1, but got %d", len(wsList.Items)) 217 } 218 ws := wsList.Items[0] 219 // this workspace name is what exists in the cloud backend configuration block 220 if ws.Name != "new-workspace" { 221 t.Fatalf("Expected workspace to be `new-workspace`, but is %s", ws.Name) 222 } 223 }, 224 }, 225 } 226 227 for name, tc := range cases { 228 tc := tc 229 t.Run(name, func(t *testing.T) { 230 // t.Parallel() 231 organization, cleanup := createOrganization(t) 232 defer cleanup() 233 exp, err := expect.NewConsole(defaultOpts()...) 234 if err != nil { 235 t.Fatal(err) 236 } 237 defer exp.Close() 238 239 tmpDir, err := ioutil.TempDir("", "terraform-test") 240 if err != nil { 241 t.Fatal(err) 242 } 243 defer os.RemoveAll(tmpDir) 244 245 tf := e2e.NewBinary(terraformBin, tmpDir) 246 defer tf.Close() 247 tf.AddEnv(cliConfigFileEnv) 248 249 for _, op := range tc.operations { 250 op.prep(t, organization.Name, tf.WorkDir()) 251 for _, tfCmd := range op.commands { 252 cmd := tf.Cmd(tfCmd.command...) 253 cmd.Stdin = exp.Tty() 254 cmd.Stdout = exp.Tty() 255 cmd.Stderr = exp.Tty() 256 257 err = cmd.Start() 258 if err != nil { 259 t.Fatal(err) 260 } 261 262 if tfCmd.expectedCmdOutput != "" { 263 got, err := exp.ExpectString(tfCmd.expectedCmdOutput) 264 if err != nil { 265 t.Fatalf("error while waiting for output\nwant: %s\nerror: %s\noutput\n%s", tfCmd.expectedCmdOutput, err, got) 266 } 267 } 268 269 lenInput := len(tfCmd.userInput) 270 lenInputOutput := len(tfCmd.postInputOutput) 271 if lenInput > 0 { 272 for i := 0; i < lenInput; i++ { 273 input := tfCmd.userInput[i] 274 exp.SendLine(input) 275 // use the index to find the corresponding 276 // output that matches the input. 277 if lenInputOutput-1 >= i { 278 output := tfCmd.postInputOutput[i] 279 _, err := exp.ExpectString(output) 280 if err != nil { 281 t.Fatal(err) 282 } 283 } 284 } 285 } 286 287 err = cmd.Wait() 288 if err != nil && !tfCmd.expectError { 289 t.Fatal(err) 290 } 291 } 292 } 293 294 if tc.validations != nil { 295 tc.validations(t, organization.Name) 296 } 297 }) 298 } 299 } 300 301 func Test_migrate_multi_to_tfc_cloud_tags_strategy(t *testing.T) { 302 skipIfMissingEnvVar(t) 303 skipWithoutRemoteTerraformVersion(t) 304 305 ctx := context.Background() 306 307 cases := map[string]struct { 308 operations []operationSets 309 validations func(t *testing.T, orgName string) 310 }{ 311 "migrating multiple workspaces to cloud using tags strategy; pattern is using prefix `app-*`": { 312 operations: []operationSets{ 313 { 314 prep: func(t *testing.T, orgName, dir string) { 315 tfBlock := terraformConfigLocalBackend() 316 writeMainTF(t, tfBlock, dir) 317 }, 318 commands: []tfCommand{ 319 { 320 command: []string{"init"}, 321 expectedCmdOutput: `Successfully configured the backend "local"!`, 322 }, 323 { 324 command: []string{"apply", "-auto-approve"}, 325 postInputOutput: []string{`Apply complete!`}, 326 }, 327 { 328 command: []string{"workspace", "new", "prod"}, 329 expectedCmdOutput: `Created and switched to workspace "prod"!`, 330 }, 331 { 332 command: []string{"apply", "-auto-approve"}, 333 postInputOutput: []string{`Apply complete!`}, 334 }, 335 { 336 command: []string{"workspace", "select", "default"}, 337 expectedCmdOutput: `Switched to workspace "default".`, 338 }, 339 { 340 command: []string{"output"}, 341 expectedCmdOutput: `val = "default"`, 342 }, 343 { 344 command: []string{"workspace", "select", "prod"}, 345 expectedCmdOutput: `Switched to workspace "prod".`, 346 }, 347 { 348 command: []string{"output"}, 349 expectedCmdOutput: `val = "prod"`, 350 }, 351 }, 352 }, 353 { 354 prep: func(t *testing.T, orgName, dir string) { 355 tag := "app" 356 tfBlock := terraformConfigCloudBackendTags(orgName, tag) 357 writeMainTF(t, tfBlock, dir) 358 }, 359 commands: []tfCommand{ 360 { 361 command: []string{"init"}, 362 expectedCmdOutput: `Terraform Cloud requires all workspaces to be given an explicit name.`, 363 userInput: []string{"dev", "1", "app-*"}, 364 postInputOutput: []string{ 365 `Would you like to rename your workspaces?`, 366 "How would you like to rename your workspaces?", 367 "Terraform Cloud has been successfully initialized!"}, 368 }, 369 { 370 command: []string{"workspace", "select", "app-dev"}, 371 expectedCmdOutput: `Switched to workspace "app-dev".`, 372 }, 373 { 374 command: []string{"output"}, 375 expectedCmdOutput: `val = "default"`, 376 }, 377 { 378 command: []string{"workspace", "select", "app-prod"}, 379 expectedCmdOutput: `Switched to workspace "app-prod".`, 380 }, 381 { 382 command: []string{"output"}, 383 expectedCmdOutput: `val = "prod"`, 384 }, 385 }, 386 }, 387 }, 388 validations: func(t *testing.T, orgName string) { 389 wsList, err := tfeClient.Workspaces.List(ctx, orgName, tfe.WorkspaceListOptions{ 390 Tags: tfe.String("app"), 391 }) 392 if err != nil { 393 t.Fatal(err) 394 } 395 if len(wsList.Items) != 2 { 396 t.Fatalf("Expected the number of workspaecs to be 2, but got %d", len(wsList.Items)) 397 } 398 expectedWorkspaceNames := []string{"app-prod", "app-dev"} 399 for _, ws := range wsList.Items { 400 hasName := false 401 for _, expectedNames := range expectedWorkspaceNames { 402 if expectedNames == ws.Name { 403 hasName = true 404 } 405 } 406 if !hasName { 407 t.Fatalf("Worksapce %s is not in the expected list of workspaces", ws.Name) 408 } 409 } 410 }, 411 }, 412 "migrating multiple workspaces to cloud using tags strategy; existing workspaces": { 413 operations: []operationSets{ 414 { 415 prep: func(t *testing.T, orgName, dir string) { 416 tfBlock := terraformConfigLocalBackend() 417 writeMainTF(t, tfBlock, dir) 418 }, 419 commands: []tfCommand{ 420 { 421 command: []string{"init"}, 422 expectedCmdOutput: `Successfully configured the backend "local"!`, 423 }, 424 { 425 command: []string{"apply", "-auto-approve"}, 426 postInputOutput: []string{`Apply complete!`}, 427 }, 428 { 429 command: []string{"workspace", "new", "identity"}, 430 expectedCmdOutput: `Created and switched to workspace "identity"!`, 431 }, 432 { 433 command: []string{"apply", "-auto-approve"}, 434 postInputOutput: []string{`Apply complete!`}, 435 }, 436 { 437 command: []string{"workspace", "new", "billing"}, 438 expectedCmdOutput: `Created and switched to workspace "billing"!`, 439 }, 440 { 441 command: []string{"apply", "-auto-approve"}, 442 postInputOutput: []string{`Apply complete!`}, 443 }, 444 { 445 command: []string{"workspace", "select", "default"}, 446 expectedCmdOutput: `Switched to workspace "default".`, 447 }, 448 }, 449 }, 450 { 451 prep: func(t *testing.T, orgName, dir string) { 452 tag := "app" 453 _ = createWorkspace(t, orgName, tfe.WorkspaceCreateOptions{ 454 Name: tfe.String("identity"), 455 TerraformVersion: tfe.String(tfversion.String()), 456 }) 457 _ = createWorkspace(t, orgName, tfe.WorkspaceCreateOptions{ 458 Name: tfe.String("billing"), 459 TerraformVersion: tfe.String(tfversion.String()), 460 }) 461 tfBlock := terraformConfigCloudBackendTags(orgName, tag) 462 writeMainTF(t, tfBlock, dir) 463 }, 464 commands: []tfCommand{ 465 { 466 command: []string{"init"}, 467 expectedCmdOutput: `Terraform Cloud requires all workspaces to be given an explicit name.`, 468 userInput: []string{"dev", "1", "app-*"}, 469 postInputOutput: []string{ 470 `Would you like to rename your workspaces?`, 471 "How would you like to rename your workspaces?", 472 "Terraform Cloud has been successfully initialized!"}, 473 }, 474 { 475 command: []string{"workspace", "select", "app-billing"}, 476 expectedCmdOutput: `Switched to workspace "app-billing".`, 477 }, 478 { 479 command: []string{"workspace", "select", "app-identity"}, 480 expectedCmdOutput: `Switched to workspace "app-identity".`, 481 }, 482 { 483 command: []string{"workspace", "select", "app-dev"}, 484 expectedCmdOutput: `Switched to workspace "app-dev".`, 485 }, 486 }, 487 }, 488 }, 489 validations: func(t *testing.T, orgName string) { 490 wsList, err := tfeClient.Workspaces.List(ctx, orgName, tfe.WorkspaceListOptions{ 491 Tags: tfe.String("app"), 492 }) 493 if err != nil { 494 t.Fatal(err) 495 } 496 if len(wsList.Items) != 3 { 497 t.Fatalf("Expected the number of workspaecs to be 3, but got %d", len(wsList.Items)) 498 } 499 expectedWorkspaceNames := []string{"app-billing", "app-dev", "app-identity"} 500 for _, ws := range wsList.Items { 501 hasName := false 502 for _, expectedNames := range expectedWorkspaceNames { 503 if expectedNames == ws.Name { 504 hasName = true 505 } 506 } 507 if !hasName { 508 t.Fatalf("Worksapce %s is not in the expected list of workspaces", ws.Name) 509 } 510 } 511 }, 512 }, 513 } 514 515 for name, tc := range cases { 516 tc := tc 517 t.Run(name, func(t *testing.T) { 518 // t.Parallel() 519 organization, cleanup := createOrganization(t) 520 defer cleanup() 521 exp, err := expect.NewConsole(defaultOpts()...) 522 if err != nil { 523 t.Fatal(err) 524 } 525 defer exp.Close() 526 527 tmpDir, err := ioutil.TempDir("", "terraform-test") 528 if err != nil { 529 t.Fatal(err) 530 } 531 defer os.RemoveAll(tmpDir) 532 533 tf := e2e.NewBinary(terraformBin, tmpDir) 534 defer tf.Close() 535 tf.AddEnv(cliConfigFileEnv) 536 537 for _, op := range tc.operations { 538 op.prep(t, organization.Name, tf.WorkDir()) 539 for _, tfCmd := range op.commands { 540 cmd := tf.Cmd(tfCmd.command...) 541 cmd.Stdin = exp.Tty() 542 cmd.Stdout = exp.Tty() 543 cmd.Stderr = exp.Tty() 544 545 err = cmd.Start() 546 if err != nil { 547 t.Fatal(err) 548 } 549 550 if tfCmd.expectedCmdOutput != "" { 551 got, err := exp.ExpectString(tfCmd.expectedCmdOutput) 552 if err != nil { 553 t.Fatalf("error while waiting for output\nwant: %s\nerror: %s\noutput\n%s", tfCmd.expectedCmdOutput, err, got) 554 } 555 } 556 557 lenInput := len(tfCmd.userInput) 558 lenInputOutput := len(tfCmd.postInputOutput) 559 if lenInput > 0 { 560 for i := 0; i < lenInput; i++ { 561 input := tfCmd.userInput[i] 562 exp.SendLine(input) 563 // use the index to find the corresponding 564 // output that matches the input. 565 if lenInputOutput-1 >= i { 566 output := tfCmd.postInputOutput[i] 567 if output == "" { 568 continue 569 } 570 _, err := exp.ExpectString(output) 571 if err != nil { 572 t.Fatal(err) 573 } 574 } 575 } 576 } 577 578 err = cmd.Wait() 579 if err != nil { 580 t.Fatal(err) 581 } 582 } 583 } 584 585 if tc.validations != nil { 586 tc.validations(t, organization.Name) 587 } 588 }) 589 } 590 }