github.com/opentofu/opentofu@v1.7.1/internal/cloud/e2e/migrate_state_remote_backend_to_tfc_test.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package main 7 8 import ( 9 "context" 10 "testing" 11 12 tfe "github.com/hashicorp/go-tfe" 13 ) 14 15 func Test_migrate_remote_backend_single_org(t *testing.T) { 16 t.Parallel() 17 skipIfMissingEnvVar(t) 18 skipWithoutRemoteTerraformVersion(t) 19 20 ctx := context.Background() 21 cases := testCases{ 22 "migrate remote backend name to tfc name": { 23 operations: []operationSets{ 24 { 25 prep: func(t *testing.T, orgName, dir string) { 26 remoteWorkspace := "remote-workspace" 27 tfBlock := terraformConfigRemoteBackendName(orgName, remoteWorkspace) 28 writeMainTF(t, tfBlock, dir) 29 }, 30 commands: []tfCommand{ 31 { 32 command: []string{"init"}, 33 expectedCmdOutput: `Successfully configured the backend "remote"!`, 34 }, 35 { 36 command: []string{"apply", "-auto-approve"}, 37 expectedCmdOutput: `Apply complete!`, 38 }, 39 }, 40 }, 41 { 42 prep: func(t *testing.T, orgName, dir string) { 43 wsName := "cloud-workspace" 44 tfBlock := terraformConfigCloudBackendName(orgName, wsName) 45 writeMainTF(t, tfBlock, dir) 46 }, 47 commands: []tfCommand{ 48 { 49 command: []string{"init", "-ignore-remote-version"}, 50 expectedCmdOutput: `Migrating from backend "remote" to Terraform Cloud.`, 51 userInput: []string{"yes", "yes"}, 52 postInputOutput: []string{ 53 `Should Terraform migrate your existing state?`, 54 `Terraform Cloud has been successfully initialized!`}, 55 }, 56 { 57 command: []string{"workspace", "show"}, 58 expectedCmdOutput: `cloud-workspace`, 59 }, 60 }, 61 }, 62 }, 63 validations: func(t *testing.T, orgName string) { 64 expectedName := "cloud-workspace" 65 ws, err := tfeClient.Workspaces.Read(ctx, orgName, expectedName) 66 if err != nil { 67 t.Fatal(err) 68 } 69 if ws == nil { 70 t.Fatalf("Expected workspace %s to be present, but is not.", expectedName) 71 } 72 }, 73 }, 74 "migrate remote backend name to tfc same name": { 75 operations: []operationSets{ 76 { 77 prep: func(t *testing.T, orgName, dir string) { 78 remoteWorkspace := "remote-workspace" 79 tfBlock := terraformConfigRemoteBackendName(orgName, remoteWorkspace) 80 writeMainTF(t, tfBlock, dir) 81 }, 82 commands: []tfCommand{ 83 { 84 command: []string{"init"}, 85 expectedCmdOutput: `Successfully configured the backend "remote"!`, 86 }, 87 { 88 command: []string{"apply", "-auto-approve"}, 89 postInputOutput: []string{`Apply complete!`}, 90 }, 91 }, 92 }, 93 { 94 prep: func(t *testing.T, orgName, dir string) { 95 wsName := "remote-workspace" 96 tfBlock := terraformConfigCloudBackendName(orgName, wsName) 97 writeMainTF(t, tfBlock, dir) 98 }, 99 commands: []tfCommand{ 100 { 101 command: []string{"init", "-ignore-remote-version"}, 102 expectedCmdOutput: `Migrating from backend "remote" to Terraform Cloud.`, 103 userInput: []string{"yes", "yes"}, 104 postInputOutput: []string{ 105 `Should Terraform migrate your existing state?`, 106 `Terraform Cloud has been successfully initialized!`}, 107 }, 108 { 109 command: []string{"workspace", "show"}, 110 expectedCmdOutput: `remote-workspace`, 111 }, 112 }, 113 }, 114 }, 115 validations: func(t *testing.T, orgName string) { 116 expectedName := "remote-workspace" 117 ws, err := tfeClient.Workspaces.Read(ctx, orgName, expectedName) 118 if err != nil { 119 t.Fatal(err) 120 } 121 if ws == nil { 122 t.Fatalf("Expected workspace %s to be present, but is not.", expectedName) 123 } 124 }, 125 }, 126 "migrate remote backend name to tfc tags": { 127 operations: []operationSets{ 128 { 129 prep: func(t *testing.T, orgName, dir string) { 130 remoteWorkspace := "remote-workspace" 131 tfBlock := terraformConfigRemoteBackendName(orgName, remoteWorkspace) 132 writeMainTF(t, tfBlock, dir) 133 }, 134 commands: []tfCommand{ 135 { 136 command: []string{"init"}, 137 expectedCmdOutput: `Successfully configured the backend "remote"!`, 138 }, 139 { 140 command: []string{"apply", "-auto-approve"}, 141 postInputOutput: []string{`Apply complete!`}, 142 }, 143 { 144 command: []string{"workspace", "show"}, 145 expectedCmdOutput: `default`, 146 }, 147 }, 148 }, 149 { 150 prep: func(t *testing.T, orgName, dir string) { 151 tag := "app" 152 tfBlock := terraformConfigCloudBackendTags(orgName, tag) 153 writeMainTF(t, tfBlock, dir) 154 }, 155 commands: []tfCommand{ 156 { 157 command: []string{"init", "-ignore-remote-version"}, 158 expectedCmdOutput: `Migrating from backend "remote" to Terraform Cloud.`, 159 userInput: []string{"yes", "cloud-workspace", "yes"}, 160 postInputOutput: []string{ 161 `Should Terraform migrate your existing state?`, 162 `Terraform Cloud requires all workspaces to be given an explicit name.`, 163 `Terraform Cloud has been successfully initialized!`}, 164 }, 165 { 166 command: []string{"workspace", "show"}, 167 expectedCmdOutput: `cloud-workspace`, 168 }, 169 }, 170 }, 171 }, 172 validations: func(t *testing.T, orgName string) { 173 wsList, err := tfeClient.Workspaces.List(ctx, orgName, &tfe.WorkspaceListOptions{ 174 Tags: "app", 175 }) 176 if err != nil { 177 t.Fatal(err) 178 } 179 if len(wsList.Items) != 1 { 180 t.Fatalf("Expected number of workspaces to be 1, but got %d", len(wsList.Items)) 181 } 182 ws := wsList.Items[0] 183 if ws.Name != "cloud-workspace" { 184 t.Fatalf("Expected workspace to be `cloud-workspace`, but is %s", ws.Name) 185 } 186 }, 187 }, 188 "migrate remote backend prefix to tfc name strategy single workspace": { 189 operations: []operationSets{ 190 { 191 prep: func(t *testing.T, orgName, dir string) { 192 _ = createWorkspace(t, orgName, tfe.WorkspaceCreateOptions{Name: tfe.String("app-one")}) 193 prefix := "app-" 194 tfBlock := terraformConfigRemoteBackendPrefix(orgName, prefix) 195 writeMainTF(t, tfBlock, dir) 196 }, 197 commands: []tfCommand{ 198 { 199 command: []string{"init"}, 200 expectedCmdOutput: `Terraform has been successfully initialized!`, 201 }, 202 { 203 command: []string{"apply", "-auto-approve"}, 204 postInputOutput: []string{`Apply complete!`}, 205 }, 206 }, 207 }, 208 { 209 prep: func(t *testing.T, orgName, dir string) { 210 wsName := "cloud-workspace" 211 tfBlock := terraformConfigCloudBackendName(orgName, wsName) 212 writeMainTF(t, tfBlock, dir) 213 }, 214 commands: []tfCommand{ 215 { 216 command: []string{"init", "-ignore-remote-version"}, 217 expectedCmdOutput: `Migrating from backend "remote" to Terraform Cloud.`, 218 userInput: []string{"yes", "yes"}, 219 postInputOutput: []string{ 220 `Should Terraform migrate your existing state?`, 221 `Terraform Cloud has been successfully initialized!`}, 222 }, 223 { 224 command: []string{"workspace", "show"}, 225 expectedCmdOutput: `cloud-workspace`, 226 }, 227 }, 228 }, 229 }, 230 validations: func(t *testing.T, orgName string) { 231 expectedName := "cloud-workspace" 232 ws, err := tfeClient.Workspaces.Read(ctx, orgName, expectedName) 233 if err != nil { 234 t.Fatal(err) 235 } 236 if ws == nil { 237 t.Fatalf("Expected workspace %s to be present, but is not.", expectedName) 238 } 239 }, 240 }, 241 "migrate remote backend prefix to tfc name strategy multi workspace": { 242 operations: []operationSets{ 243 { 244 prep: func(t *testing.T, orgName, dir string) { 245 _ = createWorkspace(t, orgName, tfe.WorkspaceCreateOptions{Name: tfe.String("app-one")}) 246 _ = createWorkspace(t, orgName, tfe.WorkspaceCreateOptions{Name: tfe.String("app-two")}) 247 prefix := "app-" 248 tfBlock := terraformConfigRemoteBackendPrefix(orgName, prefix) 249 writeMainTF(t, tfBlock, dir) 250 }, 251 commands: []tfCommand{ 252 { 253 command: []string{"init"}, 254 expectedCmdOutput: `The currently selected workspace (default) does not exist.`, 255 userInput: []string{"1"}, 256 postInputOutput: []string{`Terraform has been successfully initialized!`}, 257 }, 258 { 259 command: []string{"apply", "-auto-approve"}, 260 postInputOutput: []string{`Apply complete!`}, 261 }, 262 { 263 command: []string{"workspace", "list"}, 264 expectedCmdOutput: "* one", // app name retrieved via prefix 265 }, 266 { 267 command: []string{"workspace", "select", "two"}, 268 expectedCmdOutput: `Switched to workspace "two".`, // app name retrieved via prefix 269 }, 270 }, 271 }, 272 { 273 prep: func(t *testing.T, orgName, dir string) { 274 wsName := "cloud-workspace" 275 tfBlock := terraformConfigCloudBackendName(orgName, wsName) 276 writeMainTF(t, tfBlock, dir) 277 }, 278 commands: []tfCommand{ 279 { 280 command: []string{"init", "-ignore-remote-version"}, 281 expectedCmdOutput: `Do you want to copy only your current workspace?`, 282 userInput: []string{"yes"}, 283 postInputOutput: []string{ 284 `Terraform Cloud has been successfully initialized!`}, 285 }, 286 { 287 command: []string{"workspace", "show"}, 288 expectedCmdOutput: `cloud-workspace`, 289 }, 290 }, 291 }, 292 }, 293 validations: func(t *testing.T, orgName string) { 294 expectedName := "cloud-workspace" 295 ws, err := tfeClient.Workspaces.Read(ctx, orgName, expectedName) 296 if err != nil { 297 t.Fatal(err) 298 } 299 if ws == nil { 300 t.Fatalf("Expected workspace %s to be present, but is not.", expectedName) 301 } 302 wsList, err := tfeClient.Workspaces.List(ctx, orgName, nil) 303 if err != nil { 304 t.Fatal(err) 305 } 306 if len(wsList.Items) != 3 { 307 t.Fatalf("expected number of workspaces in this org to be 3, but got %d", len(wsList.Items)) 308 } 309 _, empty := getWorkspace(wsList.Items, "cloud-workspace") 310 if empty { 311 t.Fatalf("expected workspaces to include 'cloud-workspace' but didn't.") 312 } 313 _, empty = getWorkspace(wsList.Items, "app-one") 314 if empty { 315 t.Fatalf("expected workspaces to include 'app-one' but didn't.") 316 } 317 _, empty = getWorkspace(wsList.Items, "app-two") 318 if empty { 319 t.Fatalf("expected workspaces to include 'app-two' but didn't.") 320 } 321 }, 322 }, 323 "migrate remote backend prefix to tfc tags strategy single workspace": { 324 operations: []operationSets{ 325 { 326 prep: func(t *testing.T, orgName, dir string) { 327 _ = createWorkspace(t, orgName, tfe.WorkspaceCreateOptions{Name: tfe.String("app-one")}) 328 prefix := "app-" 329 tfBlock := terraformConfigRemoteBackendPrefix(orgName, prefix) 330 writeMainTF(t, tfBlock, dir) 331 }, 332 commands: []tfCommand{ 333 { 334 command: []string{"init"}, 335 expectedCmdOutput: `Terraform has been successfully initialized!`, 336 }, 337 { 338 command: []string{"apply", "-auto-approve"}, 339 postInputOutput: []string{`Apply complete!`}, 340 }, 341 }, 342 }, 343 { 344 prep: func(t *testing.T, orgName, dir string) { 345 tag := "app" 346 tfBlock := terraformConfigCloudBackendTags(orgName, tag) 347 writeMainTF(t, tfBlock, dir) 348 }, 349 commands: []tfCommand{ 350 { 351 command: []string{"init", "-ignore-remote-version"}, 352 expectedCmdOutput: `Migrating from backend "remote" to Terraform Cloud.`, 353 userInput: []string{"yes", "cloud-workspace", "yes"}, 354 postInputOutput: []string{ 355 `Should Terraform migrate your existing state?`, 356 `Terraform Cloud requires all workspaces to be given an explicit name.`, 357 `Terraform Cloud has been successfully initialized!`}, 358 }, 359 { 360 command: []string{"workspace", "list"}, 361 expectedCmdOutput: `cloud-workspace`, 362 }, 363 }, 364 }, 365 }, 366 validations: func(t *testing.T, orgName string) { 367 expectedName := "cloud-workspace" 368 ws, err := tfeClient.Workspaces.Read(ctx, orgName, expectedName) 369 if err != nil { 370 t.Fatal(err) 371 } 372 if ws == nil { 373 t.Fatalf("Expected workspace %s to be present, but is not.", expectedName) 374 } 375 }, 376 }, 377 "migrate remote backend prefix to tfc tags strategy multi workspace": { 378 operations: []operationSets{ 379 { 380 prep: func(t *testing.T, orgName, dir string) { 381 _ = createWorkspace(t, orgName, tfe.WorkspaceCreateOptions{Name: tfe.String("app-one")}) 382 _ = createWorkspace(t, orgName, tfe.WorkspaceCreateOptions{Name: tfe.String("app-two")}) 383 prefix := "app-" 384 tfBlock := terraformConfigRemoteBackendPrefix(orgName, prefix) 385 writeMainTF(t, tfBlock, dir) 386 }, 387 commands: []tfCommand{ 388 { 389 command: []string{"init"}, 390 expectedCmdOutput: `The currently selected workspace (default) does not exist.`, 391 userInput: []string{"1"}, 392 postInputOutput: []string{`Terraform has been successfully initialized!`}, 393 }, 394 { 395 command: []string{"apply"}, 396 expectedCmdOutput: `Do you want to perform these actions in workspace "app-one"?`, 397 userInput: []string{"yes"}, 398 postInputOutput: []string{`Apply complete!`}, 399 }, 400 { 401 command: []string{"workspace", "select", "two"}, 402 }, 403 { 404 command: []string{"apply"}, 405 expectedCmdOutput: `Do you want to perform these actions in workspace "app-two"?`, 406 userInput: []string{"yes"}, 407 postInputOutput: []string{`Apply complete!`}, 408 }, 409 }, 410 }, 411 { 412 prep: func(t *testing.T, orgName, dir string) { 413 tag := "app" 414 tfBlock := terraformConfigCloudBackendTags(orgName, tag) 415 writeMainTF(t, tfBlock, dir) 416 }, 417 commands: []tfCommand{ 418 { 419 command: []string{"init", "-ignore-remote-version"}, 420 expectedCmdOutput: `Do you wish to proceed?`, 421 userInput: []string{"yes"}, 422 postInputOutput: []string{`Terraform Cloud has been successfully initialized!`}, 423 }, 424 { 425 command: []string{"workspace", "show"}, 426 expectedCmdOutput: "app-two", 427 }, 428 { 429 command: []string{"workspace", "select", "app-one"}, 430 expectedCmdOutput: `Switched to workspace "app-one".`, 431 }, 432 }, 433 }, 434 }, 435 validations: func(t *testing.T, orgName string) { 436 wsList, err := tfeClient.Workspaces.List(ctx, orgName, &tfe.WorkspaceListOptions{ 437 Tags: "app", 438 }) 439 if err != nil { 440 t.Fatal(err) 441 } 442 if len(wsList.Items) != 2 { 443 t.Logf("Expected the number of workspaces to be 2, but got %d", len(wsList.Items)) 444 } 445 ws, empty := getWorkspace(wsList.Items, "app-one") 446 if empty { 447 t.Fatalf("expected workspaces to include 'app-one' but didn't.") 448 } 449 if len(ws.TagNames) == 0 { 450 t.Fatalf("expected workspaces 'one' to have tags.") 451 } 452 ws, empty = getWorkspace(wsList.Items, "app-two") 453 if empty { 454 t.Fatalf("expected workspaces to include 'app-two' but didn't.") 455 } 456 if len(ws.TagNames) == 0 { 457 t.Fatalf("expected workspaces 'app-two' to have tags.") 458 } 459 }, 460 }, 461 } 462 463 testRunner(t, cases, 1) 464 } 465 466 func Test_migrate_remote_backend_multi_org(t *testing.T) { 467 t.Parallel() 468 skipIfMissingEnvVar(t) 469 skipWithoutRemoteTerraformVersion(t) 470 471 ctx := context.Background() 472 cases := testCases{ 473 "migrate remote backend name to tfc name": { 474 operations: []operationSets{ 475 { 476 prep: func(t *testing.T, orgName, dir string) { 477 remoteWorkspace := "remote-workspace" 478 tfBlock := terraformConfigRemoteBackendName(orgName, remoteWorkspace) 479 writeMainTF(t, tfBlock, dir) 480 }, 481 commands: []tfCommand{ 482 { 483 command: []string{"init"}, 484 expectedCmdOutput: `Successfully configured the backend "remote"!`, 485 }, 486 { 487 command: []string{"apply", "-auto-approve"}, 488 postInputOutput: []string{`Apply complete!`}, 489 }, 490 }, 491 }, 492 { 493 prep: func(t *testing.T, orgName, dir string) { 494 wsName := "remote-workspace" 495 tfBlock := terraformConfigCloudBackendName(orgName, wsName) 496 writeMainTF(t, tfBlock, dir) 497 }, 498 commands: []tfCommand{ 499 { 500 command: []string{"init", "-ignore-remote-version"}, 501 expectedCmdOutput: `Migrating from backend "remote" to Terraform Cloud.`, 502 userInput: []string{"yes", "yes"}, 503 postInputOutput: []string{ 504 `Should Terraform migrate your existing state?`, 505 `Terraform Cloud has been successfully initialized!`}, 506 }, 507 { 508 command: []string{"workspace", "show"}, 509 expectedCmdOutput: `remote-workspace`, 510 }, 511 }, 512 }, 513 }, 514 validations: func(t *testing.T, orgName string) { 515 expectedName := "remote-workspace" 516 ws, err := tfeClient.Workspaces.Read(ctx, orgName, expectedName) 517 if err != nil { 518 t.Fatal(err) 519 } 520 if ws == nil { 521 t.Fatalf("Expected workspace %s to be present, but is not.", expectedName) 522 } 523 }, 524 }, 525 } 526 527 testRunner(t, cases, 2) 528 }