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  }