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  }