Backend State Migration to `cloud`: Multiple Workspaces
* Handle when there are multiple workspaces migrating to cloud, using both the cloud name strategy and cloud tags strategy. * Add e2e tests
This commit is contained in:
parent
baa72ce235
commit
3fedd6898c
|
@ -43,12 +43,12 @@ func Test_terraform_apply_autoApprove(t *testing.T) {
|
||||||
commands: []tfCommand{
|
commands: []tfCommand{
|
||||||
{
|
{
|
||||||
command: []string{"init"},
|
command: []string{"init"},
|
||||||
expectedOutput: "Terraform has been successfully initialized",
|
expectedCmdOutput: "Terraform has been successfully initialized",
|
||||||
expectedErr: "",
|
expectedErr: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: []string{"apply"},
|
command: []string{"apply"},
|
||||||
expectedOutput: "Do you want to perform these actions in workspace",
|
expectedCmdOutput: "Do you want to perform these actions in workspace",
|
||||||
expectedErr: "Error asking approve",
|
expectedErr: "Error asking approve",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -87,12 +87,12 @@ func Test_terraform_apply_autoApprove(t *testing.T) {
|
||||||
commands: []tfCommand{
|
commands: []tfCommand{
|
||||||
{
|
{
|
||||||
command: []string{"init"},
|
command: []string{"init"},
|
||||||
expectedOutput: "Terraform has been successfully initialized",
|
expectedCmdOutput: "Terraform has been successfully initialized",
|
||||||
expectedErr: "",
|
expectedErr: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: []string{"apply"},
|
command: []string{"apply"},
|
||||||
expectedOutput: "Do you want to perform these actions in workspace",
|
expectedCmdOutput: "Do you want to perform these actions in workspace",
|
||||||
expectedErr: "Error asking approve",
|
expectedErr: "Error asking approve",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -131,12 +131,12 @@ func Test_terraform_apply_autoApprove(t *testing.T) {
|
||||||
commands: []tfCommand{
|
commands: []tfCommand{
|
||||||
{
|
{
|
||||||
command: []string{"init"},
|
command: []string{"init"},
|
||||||
expectedOutput: "Terraform has been successfully initialized",
|
expectedCmdOutput: "Terraform has been successfully initialized",
|
||||||
expectedErr: "",
|
expectedErr: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: []string{"apply", "-auto-approve"},
|
command: []string{"apply", "-auto-approve"},
|
||||||
expectedOutput: "Apply complete! Resources: 1 added, 0 changed, 0 destroyed.",
|
expectedCmdOutput: "Apply complete! Resources: 1 added, 0 changed, 0 destroyed.",
|
||||||
expectedErr: "",
|
expectedErr: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -176,12 +176,12 @@ func Test_terraform_apply_autoApprove(t *testing.T) {
|
||||||
commands: []tfCommand{
|
commands: []tfCommand{
|
||||||
{
|
{
|
||||||
command: []string{"init"},
|
command: []string{"init"},
|
||||||
expectedOutput: "Terraform has been successfully initialized",
|
expectedCmdOutput: "Terraform has been successfully initialized",
|
||||||
expectedErr: "",
|
expectedErr: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: []string{"apply", "-auto-approve"},
|
command: []string{"apply", "-auto-approve"},
|
||||||
expectedOutput: "Apply complete! Resources: 1 added, 0 changed, 0 destroyed.",
|
expectedCmdOutput: "Apply complete! Resources: 1 added, 0 changed, 0 destroyed.",
|
||||||
expectedErr: "",
|
expectedErr: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -228,8 +228,8 @@ func Test_terraform_apply_autoApprove(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.expectedOutput != "" && !strings.Contains(stdout, cmd.expectedOutput) {
|
if cmd.expectedCmdOutput != "" && !strings.Contains(stdout, cmd.expectedCmdOutput) {
|
||||||
t.Fatalf("Expected to find output %s, but did not find in\n%s", cmd.expectedOutput, stdout)
|
t.Fatalf("Expected to find output %s, but did not find in\n%s", cmd.expectedCmdOutput, stdout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,11 +20,11 @@ const (
|
||||||
|
|
||||||
type tfCommand struct {
|
type tfCommand struct {
|
||||||
command []string
|
command []string
|
||||||
expectedOutput string
|
expectedCmdOutput string
|
||||||
expectedErr string
|
expectedErr string
|
||||||
expectError bool
|
expectError bool
|
||||||
userInput []string
|
userInput []string
|
||||||
postInputOutput string
|
postInputOutput []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type operationSets struct {
|
type operationSets struct {
|
||||||
|
|
|
@ -1,18 +1,434 @@
|
||||||
|
//go:build e2e
|
||||||
|
// +build e2e
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
expect "github.com/Netflix/go-expect"
|
||||||
|
tfe "github.com/hashicorp/go-tfe"
|
||||||
|
"github.com/hashicorp/terraform/internal/e2e"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
func Test_migrate_multi_to_tfc_cloud_name_strategy(t *testing.T) {
|
||||||
"multi" == multi-backend, multiple workspaces
|
ctx := context.Background()
|
||||||
-- when cloud config == name ->
|
|
||||||
---- prompt -> do you want to ONLY migrate the current workspace
|
|
||||||
|
|
||||||
-- when cloud config == tags
|
cases := map[string]struct {
|
||||||
-- If Default present, prompt to rename default.
|
operations []operationSets
|
||||||
-- Then -> Prompt with *
|
validations func(t *testing.T, orgName string)
|
||||||
*/
|
}{
|
||||||
func Test_migrate_multi_to_tfc(t *testing.T) {
|
"migrating multiple workspaces to cloud using name strategy; current workspace is 'default'": {
|
||||||
t.Skip("todo: see comments")
|
operations: []operationSets{
|
||||||
|
{
|
||||||
|
prep: func(t *testing.T, orgName, dir string) {
|
||||||
|
tfBlock := terraformConfigLocalBackend()
|
||||||
|
writeMainTF(t, tfBlock, dir)
|
||||||
|
},
|
||||||
|
commands: []tfCommand{
|
||||||
|
{
|
||||||
|
command: []string{"init"},
|
||||||
|
expectedCmdOutput: `Successfully configured the backend "local"!`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"apply"},
|
||||||
|
expectedCmdOutput: `Do you want to perform these actions?`,
|
||||||
|
userInput: []string{"yes"},
|
||||||
|
postInputOutput: []string{`Apply complete!`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "new", "prod"},
|
||||||
|
expectedCmdOutput: `Created and switched to workspace "prod"!`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"apply"},
|
||||||
|
expectedCmdOutput: `Do you want to perform these actions`,
|
||||||
|
userInput: []string{"yes"},
|
||||||
|
postInputOutput: []string{`Apply complete!`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "select", "default"},
|
||||||
|
expectedCmdOutput: `Switched to workspace "default".`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prep: func(t *testing.T, orgName, dir string) {
|
||||||
|
wsName := "new-workspace"
|
||||||
|
tfBlock := terraformConfigCloudBackendName(orgName, wsName)
|
||||||
|
writeMainTF(t, tfBlock, dir)
|
||||||
|
},
|
||||||
|
commands: []tfCommand{
|
||||||
|
{
|
||||||
|
command: []string{"init", "-migrate-state"},
|
||||||
|
expectedCmdOutput: `Do you want to copy only your current workspace?`,
|
||||||
|
userInput: []string{"yes", "yes"},
|
||||||
|
postInputOutput: []string{
|
||||||
|
`Do you want to copy existing state to the new backend?`,
|
||||||
|
`Successfully configured the backend "cloud"!`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "show"},
|
||||||
|
expectedCmdOutput: `new-workspace`, // this comes from the `prep` function
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"output"},
|
||||||
|
expectedCmdOutput: `val = "default"`, // this was the output of the current workspace selected before migration
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validations: func(t *testing.T, orgName string) {
|
||||||
|
wsList, err := tfeClient.Workspaces.List(ctx, orgName, tfe.WorkspaceListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(wsList.Items) != 1 {
|
||||||
|
t.Fatalf("Expected the number of workspaces to be 1, but got %d", len(wsList.Items))
|
||||||
|
}
|
||||||
|
ws := wsList.Items[0]
|
||||||
|
// this workspace name is what exists in the cloud backend configuration block
|
||||||
|
if ws.Name != "new-workspace" {
|
||||||
|
t.Fatalf("Expected workspace to be `new-workspace`, but is %s", ws.Name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"migrating multiple workspaces to cloud using name strategy; current workspace is 'prod'": {
|
||||||
|
operations: []operationSets{
|
||||||
|
{
|
||||||
|
prep: func(t *testing.T, orgName, dir string) {
|
||||||
|
tfBlock := terraformConfigLocalBackend()
|
||||||
|
writeMainTF(t, tfBlock, dir)
|
||||||
|
},
|
||||||
|
commands: []tfCommand{
|
||||||
|
{
|
||||||
|
command: []string{"init"},
|
||||||
|
expectedCmdOutput: `Successfully configured the backend "local"!`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"apply"},
|
||||||
|
expectedCmdOutput: `Do you want to perform these actions?`,
|
||||||
|
userInput: []string{"yes"},
|
||||||
|
postInputOutput: []string{`Apply complete!`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "new", "prod"},
|
||||||
|
expectedCmdOutput: `Created and switched to workspace "prod"!`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"apply"},
|
||||||
|
expectedCmdOutput: `Do you want to perform these actions`,
|
||||||
|
userInput: []string{"yes"},
|
||||||
|
postInputOutput: []string{`Apply complete!`},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prep: func(t *testing.T, orgName, dir string) {
|
||||||
|
wsName := "new-workspace"
|
||||||
|
tfBlock := terraformConfigCloudBackendName(orgName, wsName)
|
||||||
|
writeMainTF(t, tfBlock, dir)
|
||||||
|
},
|
||||||
|
commands: []tfCommand{
|
||||||
|
{
|
||||||
|
command: []string{"init", "-migrate-state"},
|
||||||
|
expectedCmdOutput: `Do you want to copy only your current workspace?`,
|
||||||
|
userInput: []string{"yes", "yes"},
|
||||||
|
postInputOutput: []string{
|
||||||
|
`Do you want to copy existing state to the new backend?`,
|
||||||
|
`Successfully configured the backend "cloud"!`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "list"},
|
||||||
|
expectedCmdOutput: `new-workspace`, // this comes from the `prep` function
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"output"},
|
||||||
|
expectedCmdOutput: `val = "prod"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validations: func(t *testing.T, orgName string) {
|
||||||
|
wsList, err := tfeClient.Workspaces.List(ctx, orgName, tfe.WorkspaceListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ws := wsList.Items[0]
|
||||||
|
// this workspace name is what exists in the cloud backend configuration block
|
||||||
|
if ws.Name != "new-workspace" {
|
||||||
|
t.Fatalf("Expected workspace to be `new-workspace`, but is %s", ws.Name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
fmt.Println("Test: ", name)
|
||||||
|
organization, cleanup := createOrganization(t)
|
||||||
|
defer cleanup()
|
||||||
|
exp, err := expect.NewConsole(expect.WithStdout(os.Stdout), expect.WithDefaultTimeout(expectConsoleTimeout))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer exp.Close()
|
||||||
|
|
||||||
|
tmpDir, err := ioutil.TempDir("", "terraform-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
tf := e2e.NewBinary(terraformBin, tmpDir)
|
||||||
|
defer tf.Close()
|
||||||
|
tf.AddEnv("TF_LOG=INFO")
|
||||||
|
tf.AddEnv(cliConfigFileEnv)
|
||||||
|
|
||||||
|
for _, op := range tc.operations {
|
||||||
|
op.prep(t, organization.Name, tf.WorkDir())
|
||||||
|
for _, tfCmd := range op.commands {
|
||||||
|
t.Log("Running commands: ", tfCmd.command)
|
||||||
|
cmd := tf.Cmd(tfCmd.command...)
|
||||||
|
cmd.Stdin = exp.Tty()
|
||||||
|
cmd.Stdout = exp.Tty()
|
||||||
|
cmd.Stderr = exp.Tty()
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tfCmd.expectedCmdOutput != "" {
|
||||||
|
_, err := exp.ExpectString(tfCmd.expectedCmdOutput)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lenInput := len(tfCmd.userInput)
|
||||||
|
lenInputOutput := len(tfCmd.postInputOutput)
|
||||||
|
if lenInput > 0 {
|
||||||
|
for i := 0; i < lenInput; i++ {
|
||||||
|
input := tfCmd.userInput[i]
|
||||||
|
exp.SendLine(input)
|
||||||
|
// use the index to find the corresponding
|
||||||
|
// output that matches the input.
|
||||||
|
if lenInputOutput-1 >= i {
|
||||||
|
output := tfCmd.postInputOutput[i]
|
||||||
|
_, err := exp.ExpectString(output)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.validations != nil {
|
||||||
|
tc.validations(t, organization.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_migrate_multi_to_tfc_cloud_tags_strategy(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
cases := map[string]struct {
|
||||||
|
operations []operationSets
|
||||||
|
validations func(t *testing.T, orgName string)
|
||||||
|
}{
|
||||||
|
"migrating multiple workspaces to cloud using tags strategy; pattern is using prefix `app-*`": {
|
||||||
|
operations: []operationSets{
|
||||||
|
{
|
||||||
|
prep: func(t *testing.T, orgName, dir string) {
|
||||||
|
tfBlock := terraformConfigLocalBackend()
|
||||||
|
writeMainTF(t, tfBlock, dir)
|
||||||
|
},
|
||||||
|
commands: []tfCommand{
|
||||||
|
{
|
||||||
|
command: []string{"init"},
|
||||||
|
expectedCmdOutput: `Successfully configured the backend "local"!`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"apply"},
|
||||||
|
expectedCmdOutput: `Do you want to perform these actions?`,
|
||||||
|
userInput: []string{"yes"},
|
||||||
|
postInputOutput: []string{`Apply complete!`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "new", "prod"},
|
||||||
|
expectedCmdOutput: `Created and switched to workspace "prod"!`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"apply"},
|
||||||
|
expectedCmdOutput: `Do you want to perform these actions`,
|
||||||
|
userInput: []string{"yes"},
|
||||||
|
postInputOutput: []string{`Apply complete!`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "select", "default"},
|
||||||
|
expectedCmdOutput: `Switched to workspace "default".`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"output"},
|
||||||
|
expectedCmdOutput: `val = "default"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "select", "prod"},
|
||||||
|
expectedCmdOutput: `Switched to workspace "prod".`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"output"},
|
||||||
|
expectedCmdOutput: `val = "prod"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prep: func(t *testing.T, orgName, dir string) {
|
||||||
|
tag := "app"
|
||||||
|
tfBlock := terraformConfigCloudBackendTags(orgName, tag)
|
||||||
|
writeMainTF(t, tfBlock, dir)
|
||||||
|
},
|
||||||
|
commands: []tfCommand{
|
||||||
|
{
|
||||||
|
command: []string{"init", "-migrate-state"},
|
||||||
|
expectedCmdOutput: `The "cloud" backend configuration only allows named workspaces!`,
|
||||||
|
userInput: []string{"dev", "1", "app-*", "1"},
|
||||||
|
postInputOutput: []string{
|
||||||
|
`Would you like to rename your workspaces?`,
|
||||||
|
"What pattern would you like to add to all your workspaces?",
|
||||||
|
"The currently selected workspace (prod) does not exist.",
|
||||||
|
"Terraform has been successfully initialized!"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "select", "app-prod"},
|
||||||
|
expectedCmdOutput: `Switched to workspace "app-prod".`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"output"},
|
||||||
|
expectedCmdOutput: `val = "prod"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "select", "app-dev"},
|
||||||
|
expectedCmdOutput: `Switched to workspace "app-dev".`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"output"},
|
||||||
|
expectedCmdOutput: `val = "default"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validations: func(t *testing.T, orgName string) {
|
||||||
|
wsList, err := tfeClient.Workspaces.List(ctx, orgName, tfe.WorkspaceListOptions{
|
||||||
|
Tags: tfe.String("app"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(wsList.Items) != 2 {
|
||||||
|
t.Fatalf("Expected the number of workspaecs to be 2, but got %d", len(wsList.Items))
|
||||||
|
}
|
||||||
|
expectedWorkspaceNames := []string{"app-prod", "app-dev"}
|
||||||
|
for _, ws := range wsList.Items {
|
||||||
|
hasName := false
|
||||||
|
for _, expectedNames := range expectedWorkspaceNames {
|
||||||
|
if expectedNames == ws.Name {
|
||||||
|
hasName = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasName {
|
||||||
|
t.Fatalf("Worksapce %s is not in the expected list of workspaces", ws.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
fmt.Println("Test: ", name)
|
||||||
|
organization, cleanup := createOrganization(t)
|
||||||
|
t.Log(organization.Name)
|
||||||
|
defer cleanup()
|
||||||
|
exp, err := expect.NewConsole(expect.WithStdout(os.Stdout), expect.WithDefaultTimeout(expectConsoleTimeout))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer exp.Close()
|
||||||
|
|
||||||
|
tmpDir, err := ioutil.TempDir("", "terraform-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
tf := e2e.NewBinary(terraformBin, tmpDir)
|
||||||
|
defer tf.Close()
|
||||||
|
tf.AddEnv("TF_LOG=INFO")
|
||||||
|
tf.AddEnv(cliConfigFileEnv)
|
||||||
|
|
||||||
|
for _, op := range tc.operations {
|
||||||
|
op.prep(t, organization.Name, tf.WorkDir())
|
||||||
|
for _, tfCmd := range op.commands {
|
||||||
|
t.Log("running commands: ", tfCmd.command)
|
||||||
|
cmd := tf.Cmd(tfCmd.command...)
|
||||||
|
cmd.Stdin = exp.Tty()
|
||||||
|
cmd.Stdout = exp.Tty()
|
||||||
|
cmd.Stderr = exp.Tty()
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tfCmd.expectedCmdOutput != "" {
|
||||||
|
_, err := exp.ExpectString(tfCmd.expectedCmdOutput)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lenInput := len(tfCmd.userInput)
|
||||||
|
lenInputOutput := len(tfCmd.postInputOutput)
|
||||||
|
if lenInput > 0 {
|
||||||
|
for i := 0; i < lenInput; i++ {
|
||||||
|
input := tfCmd.userInput[i]
|
||||||
|
exp.SendLine(input)
|
||||||
|
// use the index to find the corresponding
|
||||||
|
// output that matches the input.
|
||||||
|
if lenInputOutput-1 >= i {
|
||||||
|
output := tfCmd.postInputOutput[i]
|
||||||
|
if output == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, err := exp.ExpectString(output)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.validations != nil {
|
||||||
|
tc.validations(t, organization.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
//go:build e2e
|
||||||
|
// +build e2e
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -23,9 +26,34 @@ import (
|
||||||
|
|
||||||
*/
|
*/
|
||||||
func Test_migrate_remote_backend_name_to_tfc(t *testing.T) {
|
func Test_migrate_remote_backend_name_to_tfc(t *testing.T) {
|
||||||
t.Skip("todo: see comments")
|
t.Skip("TODO: see comments")
|
||||||
|
_ = map[string]struct {
|
||||||
|
operations []operationSets
|
||||||
|
validations func(t *testing.T, orgName string)
|
||||||
|
}{
|
||||||
|
"single workspace with backend name strategy, to cloud with name strategy": {},
|
||||||
|
"single workspace with backend name strategy, to cloud with tags strategy": {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_migrate_remote_backend_prefix_to_tfc(t *testing.T) {
|
func Test_migrate_remote_backend_prefix_to_tfc_name(t *testing.T) {
|
||||||
t.Skip("todo: see comments")
|
t.Skip("TODO: see comments")
|
||||||
|
_ = map[string]struct {
|
||||||
|
operations []operationSets
|
||||||
|
validations func(t *testing.T, orgName string)
|
||||||
|
}{
|
||||||
|
"single workspace with backend prefix strategy, to cloud with name strategy": {},
|
||||||
|
"multiple workspaces with backend prefix strategy, to cloud with name strategy": {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_migrate_remote_backend_prefix_to_tfc_tags(t *testing.T) {
|
||||||
|
t.Skip("TODO: see comments")
|
||||||
|
_ = map[string]struct {
|
||||||
|
operations []operationSets
|
||||||
|
validations func(t *testing.T, orgName string)
|
||||||
|
}{
|
||||||
|
"single workspace with backend prefix strategy, to cloud with tags strategy": {},
|
||||||
|
"multiple workspaces with backend prefix strategy, to cloud with tags strategy": {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,13 +32,13 @@ func Test_migrate_single_to_tfc(t *testing.T) {
|
||||||
commands: []tfCommand{
|
commands: []tfCommand{
|
||||||
{
|
{
|
||||||
command: []string{"init"},
|
command: []string{"init"},
|
||||||
expectedOutput: `Successfully configured the backend "local"!`,
|
expectedCmdOutput: `Successfully configured the backend "local"!`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: []string{"apply"},
|
command: []string{"apply"},
|
||||||
|
expectedCmdOutput: `Do you want to perform these actions?`,
|
||||||
userInput: []string{"yes"},
|
userInput: []string{"yes"},
|
||||||
expectedOutput: `Do you want to perform these actions?`,
|
postInputOutput: []string{`Apply complete!`},
|
||||||
postInputOutput: `Apply complete!`,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -51,13 +51,13 @@ func Test_migrate_single_to_tfc(t *testing.T) {
|
||||||
commands: []tfCommand{
|
commands: []tfCommand{
|
||||||
{
|
{
|
||||||
command: []string{"init", "-migrate-state"},
|
command: []string{"init", "-migrate-state"},
|
||||||
expectedOutput: `Do you want to copy existing state to the new backend?`,
|
expectedCmdOutput: `Do you want to copy existing state to the new backend?`,
|
||||||
userInput: []string{"yes"},
|
userInput: []string{"yes"},
|
||||||
postInputOutput: `Successfully configured the backend "cloud"!`,
|
postInputOutput: []string{`Successfully configured the backend "cloud"!`},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: []string{"workspace", "list"},
|
command: []string{"workspace", "list"},
|
||||||
expectedOutput: `new-workspace`,
|
expectedCmdOutput: `new-workspace`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -83,13 +83,13 @@ func Test_migrate_single_to_tfc(t *testing.T) {
|
||||||
commands: []tfCommand{
|
commands: []tfCommand{
|
||||||
{
|
{
|
||||||
command: []string{"init"},
|
command: []string{"init"},
|
||||||
expectedOutput: `Successfully configured the backend "local"!`,
|
expectedCmdOutput: `Successfully configured the backend "local"!`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: []string{"apply"},
|
command: []string{"apply"},
|
||||||
|
expectedCmdOutput: `Do you want to perform these actions?`,
|
||||||
userInput: []string{"yes"},
|
userInput: []string{"yes"},
|
||||||
expectedOutput: `Do you want to perform these actions?`,
|
postInputOutput: []string{`Apply complete!`},
|
||||||
postInputOutput: `Apply complete!`,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -102,13 +102,13 @@ func Test_migrate_single_to_tfc(t *testing.T) {
|
||||||
commands: []tfCommand{
|
commands: []tfCommand{
|
||||||
{
|
{
|
||||||
command: []string{"init", "-migrate-state"},
|
command: []string{"init", "-migrate-state"},
|
||||||
expectedOutput: `The "cloud" backend configuration only allows named workspaces!`,
|
expectedCmdOutput: `The "cloud" backend configuration only allows named workspaces!`,
|
||||||
userInput: []string{"new-workspace", "yes"},
|
userInput: []string{"new-workspace", "yes"},
|
||||||
postInputOutput: `Successfully configured the backend "cloud"!`,
|
postInputOutput: []string{`Successfully configured the backend "cloud"!`},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: []string{"workspace", "list"},
|
command: []string{"workspace", "list"},
|
||||||
expectedOutput: `new-workspace`,
|
expectedCmdOutput: `new-workspace`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -162,25 +162,30 @@ func Test_migrate_single_to_tfc(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tfCmd.expectedOutput != "" {
|
if tfCmd.expectedCmdOutput != "" {
|
||||||
_, err := exp.ExpectString(tfCmd.expectedOutput)
|
_, err := exp.ExpectString(tfCmd.expectedCmdOutput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(tfCmd.userInput) > 0 {
|
lenInput := len(tfCmd.userInput)
|
||||||
for _, input := range tfCmd.userInput {
|
lenInputOutput := len(tfCmd.postInputOutput)
|
||||||
|
if lenInput > 0 {
|
||||||
|
for i := 0; i <= lenInput; i++ {
|
||||||
|
input := tfCmd.userInput[i]
|
||||||
exp.SendLine(input)
|
exp.SendLine(input)
|
||||||
}
|
// use the index to find the corresponding
|
||||||
}
|
// output that matches the input.
|
||||||
|
if lenInputOutput-1 >= i {
|
||||||
if tfCmd.postInputOutput != "" {
|
output := tfCmd.postInputOutput[i]
|
||||||
_, err := exp.ExpectString(tfCmd.postInputOutput)
|
_, err := exp.ExpectString(output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = cmd.Wait()
|
err = cmd.Wait()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -28,7 +28,7 @@ func Test_migrate_tfc_to_other(t *testing.T) {
|
||||||
commands: []tfCommand{
|
commands: []tfCommand{
|
||||||
{
|
{
|
||||||
command: []string{"init"},
|
command: []string{"init"},
|
||||||
expectedOutput: `Successfully configured the backend "cloud"!`,
|
expectedCmdOutput: `Successfully configured the backend "cloud"!`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -40,7 +40,7 @@ func Test_migrate_tfc_to_other(t *testing.T) {
|
||||||
commands: []tfCommand{
|
commands: []tfCommand{
|
||||||
{
|
{
|
||||||
command: []string{"init", "-migrate-state"},
|
command: []string{"init", "-migrate-state"},
|
||||||
expectedOutput: `Migrating state from Terraform Cloud to another backend is not yet implemented.`,
|
expectedCmdOutput: `Migrating state from Terraform Cloud to another backend is not yet implemented.`,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -83,26 +83,30 @@ func Test_migrate_tfc_to_other(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tfCmd.expectedOutput != "" {
|
if tfCmd.expectedCmdOutput != "" {
|
||||||
_, err := exp.ExpectString(tfCmd.expectedOutput)
|
_, err := exp.ExpectString(tfCmd.expectedCmdOutput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(tfCmd.userInput) > 0 {
|
lenInput := len(tfCmd.userInput)
|
||||||
for _, input := range tfCmd.userInput {
|
lenInputOutput := len(tfCmd.postInputOutput)
|
||||||
|
if lenInput > 0 {
|
||||||
|
for i := 0; i <= lenInput; i++ {
|
||||||
|
input := tfCmd.userInput[i]
|
||||||
exp.SendLine(input)
|
exp.SendLine(input)
|
||||||
}
|
// use the index to find the corresponding
|
||||||
}
|
// output that matches the input.
|
||||||
|
if lenInputOutput-1 >= i {
|
||||||
if tfCmd.postInputOutput != "" {
|
output := tfCmd.postInputOutput[i]
|
||||||
_, err := exp.ExpectString(tfCmd.postInputOutput)
|
_, err := exp.ExpectString(output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
err = cmd.Wait()
|
err = cmd.Wait()
|
||||||
if err != nil && !tfCmd.expectError {
|
if err != nil && !tfCmd.expectError {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
@ -275,16 +275,9 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
|
||||||
// for a new name and migrate the default state to the given named state.
|
// for a new name and migrate the default state to the given named state.
|
||||||
destinationState, err = func() (statemgr.Full, error) {
|
destinationState, err = func() (statemgr.Full, error) {
|
||||||
log.Print("[TRACE] backendMigrateState: destination doesn't support a default workspace, so we must prompt for a new name")
|
log.Print("[TRACE] backendMigrateState: destination doesn't support a default workspace, so we must prompt for a new name")
|
||||||
name, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{
|
name, err := m.promptNewWorkspaceName(opts.DestinationType)
|
||||||
Id: "new-state-name",
|
|
||||||
Query: fmt.Sprintf(
|
|
||||||
"[reset][bold][yellow]The %q backend configuration only allows "+
|
|
||||||
"named workspaces![reset]",
|
|
||||||
opts.DestinationType),
|
|
||||||
Description: strings.TrimSpace(inputBackendNewWorkspaceName),
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Error asking for new state name: %s", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the name of the destination state.
|
// Update the name of the destination state.
|
||||||
|
@ -562,23 +555,156 @@ func (m *Meta) backendMigrateTFC(opts *backendMigrateOpts) error {
|
||||||
|
|
||||||
multiSource := !sourceSingleState && len(sourceWorkspaces) > 1
|
multiSource := !sourceSingleState && len(sourceWorkspaces) > 1
|
||||||
if multiSource && destinationNameStrategy {
|
if multiSource && destinationNameStrategy {
|
||||||
// we have to take the current workspace from the source and migrate that
|
if err := m.promptMultiToSingleCloudMigration(opts); err != nil {
|
||||||
// over to destination. Since there is multiple sources, and we are using a
|
return err
|
||||||
// name strategy, we will only migrate the current workspace.
|
}
|
||||||
panic("not yet implemented")
|
|
||||||
|
currentEnv, err := m.Workspace()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.sourceWorkspace = currentEnv
|
||||||
|
opts.destinationWorkspace = cloudBackendDestination.WorkspaceMapping.Name
|
||||||
|
|
||||||
|
return m.backendMigrateState_s_s(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multiple sources, and using tags strategy. So migrate every source
|
// Multiple sources, and using tags strategy. So migrate every source
|
||||||
// workspace over to new one, prompt for workspace name pattern (*),
|
// workspace over to new one, prompt for workspace name pattern (*),
|
||||||
// and start migrating, and create tags for each workspace.
|
// and start migrating, and create tags for each workspace.
|
||||||
if multiSource && destinationTagsStrategy {
|
if multiSource && destinationTagsStrategy {
|
||||||
// TODO: see internal/cloud/e2e/migrate_state_multi_to_tfc_test.go for notes
|
return m.backendMigrateState_S_TFC(opts, sourceWorkspaces)
|
||||||
panic("not yet implemented")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// migrates a multi-state backend to Terraform Cloud
|
||||||
|
func (m *Meta) backendMigrateState_S_TFC(opts *backendMigrateOpts, sourceWorkspaces []string) error {
|
||||||
|
log.Print("[TRACE] backendMigrateState: migrating all named workspaces")
|
||||||
|
|
||||||
|
// This map is used later when doing the migration per source/destination.
|
||||||
|
// If a source has 'default', then we ask what the new name should be.
|
||||||
|
// And further down when we actually run state migration for each
|
||||||
|
// sourc/destination workspce, we use this new name (where source is 'default')
|
||||||
|
// and set as destinationWorkspace.
|
||||||
|
defaultNewName := map[string]string{}
|
||||||
|
for i := 0; i < len(sourceWorkspaces); i++ {
|
||||||
|
if sourceWorkspaces[i] == backend.DefaultStateName {
|
||||||
|
newName, err := m.promptNewWorkspaceName(opts.DestinationType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defaultNewName[sourceWorkspaces[i]] = newName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pattern, err := m.promptMultiStateMigrationPattern(opts.SourceType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through each and migrate
|
||||||
|
for _, name := range sourceWorkspaces {
|
||||||
|
|
||||||
|
// Copy the same names
|
||||||
|
opts.sourceWorkspace = name
|
||||||
|
if newName, ok := defaultNewName[name]; ok {
|
||||||
|
// this has to be done before setting destinationWorkspace
|
||||||
|
name = newName
|
||||||
|
}
|
||||||
|
opts.destinationWorkspace = strings.Replace(pattern, "*", name, -1)
|
||||||
|
|
||||||
|
// Force it, we confirmed above
|
||||||
|
opts.force = true
|
||||||
|
|
||||||
|
// Perform the migration
|
||||||
|
if err := m.backendMigrateState_s_s(opts); err != nil {
|
||||||
|
return fmt.Errorf(strings.TrimSpace(
|
||||||
|
errMigrateMulti), name, opts.SourceType, opts.DestinationType, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multi-state to single state.
|
||||||
|
func (m *Meta) promptMultiToSingleCloudMigration(opts *backendMigrateOpts) error {
|
||||||
|
migrate := opts.force
|
||||||
|
if !migrate {
|
||||||
|
var err error
|
||||||
|
// Ask the user if they want to migrate their existing remote state
|
||||||
|
migrate, err = m.confirm(&terraform.InputOpts{
|
||||||
|
Id: "backend-migrate-multistate-to-single",
|
||||||
|
Query: "Do you want to copy only your current workspace?",
|
||||||
|
Description: strings.TrimSpace(tfcInputBackendMigrateMultiToSingle),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error asking for state migration action: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !migrate {
|
||||||
|
return fmt.Errorf("Migration aborted by user.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Meta) promptNewWorkspaceName(destinationType string) (string, error) {
|
||||||
|
name, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{
|
||||||
|
Id: "new-state-name",
|
||||||
|
Query: fmt.Sprintf(
|
||||||
|
"[reset][bold][yellow]The %q backend configuration only allows "+
|
||||||
|
"named workspaces![reset]",
|
||||||
|
destinationType),
|
||||||
|
Description: strings.TrimSpace(inputBackendNewWorkspaceName),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Error asking for new state name: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Meta) promptMultiStateMigrationPattern(sourceType string) (string, error) {
|
||||||
|
renameWorkspaces, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{
|
||||||
|
Id: "backend-migrate-multistate-to-tfc",
|
||||||
|
Query: fmt.Sprintf("[reset][bold][yellow]%s[reset]", "Would you like to rename your workspaces?"),
|
||||||
|
Description: fmt.Sprintf(strings.TrimSpace(tfcInputBackendMigrateMultiToMulti), sourceType),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Error asking for state migration action: %s", err)
|
||||||
|
}
|
||||||
|
if renameWorkspaces != "2" && renameWorkspaces != "1" {
|
||||||
|
return "", fmt.Errorf("Please select 1 or 2 as part of this option.")
|
||||||
|
}
|
||||||
|
if renameWorkspaces == "2" {
|
||||||
|
// this means they did not want to rename their workspaces, and we are
|
||||||
|
// returning a generic '*' that means use the same workspace name during
|
||||||
|
// migration.
|
||||||
|
return "*", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{
|
||||||
|
Id: "backend-migrate-multistate-to-tfc-pattern",
|
||||||
|
Query: fmt.Sprintf("[reset][bold][yellow]%s[reset]", "What pattern would you like to add to all your workspaces?"),
|
||||||
|
Description: fmt.Sprintf(strings.TrimSpace(tfcInputBackendMigrateMultiToMultiPattern), sourceType),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Error asking for state migration action: %s", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(pattern, "*") {
|
||||||
|
return "", fmt.Errorf("The pattern must have an '*'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if count := strings.Count(pattern, "*"); count > 1 {
|
||||||
|
return "", fmt.Errorf("The pattern '*' cannot be used more than once.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return pattern, nil
|
||||||
|
}
|
||||||
|
|
||||||
const errMigrateLoadStates = `
|
const errMigrateLoadStates = `
|
||||||
Error inspecting states in the %q backend:
|
Error inspecting states in the %q backend:
|
||||||
%s
|
%s
|
||||||
|
@ -629,6 +755,37 @@ Migrating state from Terraform Cloud to another backend is not yet implemented.
|
||||||
Please use the API to do this: https://www.terraform.io/docs/cloud/api/state-versions.html
|
Please use the API to do this: https://www.terraform.io/docs/cloud/api/state-versions.html
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const tfcInputBackendMigrateMultiToMultiPattern = `
|
||||||
|
If you choose to NOT rename your workspaces, just input "*".
|
||||||
|
|
||||||
|
The asterisk "*" represents your workspace name. Here are a few examples
|
||||||
|
if a workspace was named 'prod':
|
||||||
|
* input: 'app-*'; output: 'app-prod'
|
||||||
|
* input: '*-app', output: 'prod-app'
|
||||||
|
* input: 'app-*-service', output: 'app-prod-service'
|
||||||
|
* input: '*'; output: 'prod'
|
||||||
|
`
|
||||||
|
|
||||||
|
const tfcInputBackendMigrateMultiToMulti = `
|
||||||
|
When migrating existing workspaces from the backend %[1]q to Terraform Cloud, would you like to
|
||||||
|
rename your workspaces?
|
||||||
|
|
||||||
|
Unlike typical Terraform workspaces representing an environment associated with a particular
|
||||||
|
configuration (e.g. production, staging, development), Terraform Cloud workspaces are named uniquely
|
||||||
|
across all configurations used within an organization. A typical strategy to start with is
|
||||||
|
<COMPONENT>-<ENVIRONMENT>-<REGION> (e.g. networking-prod-us-east, networking-staging-us-east).
|
||||||
|
|
||||||
|
For more information on workspace naming, see https://www.terraform.io/docs/cloud/workspaces/naming.html
|
||||||
|
|
||||||
|
1. Yes, rename workspaces according to a pattern.
|
||||||
|
2. No, I would not like to rename my workspaces. Migrate them as currently named.
|
||||||
|
`
|
||||||
|
|
||||||
|
const tfcInputBackendMigrateMultiToSingle = `
|
||||||
|
The cloud configuration has one workspace declared, and you are attemtping to migrate multiple workspaces
|
||||||
|
to a single workspace. By continuing, you will only migrate your current workspace.
|
||||||
|
`
|
||||||
|
|
||||||
const inputBackendMigrateEmpty = `
|
const inputBackendMigrateEmpty = `
|
||||||
Pre-existing state was found while migrating the previous %q backend to the
|
Pre-existing state was found while migrating the previous %q backend to the
|
||||||
newly configured %q backend. No existing state was found in the newly
|
newly configured %q backend. No existing state was found in the newly
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBackendMigrate_promptMultiStatePattern(t *testing.T) {
|
||||||
|
// Setup the meta
|
||||||
|
|
||||||
|
cases := map[string]struct {
|
||||||
|
renamePrompt string
|
||||||
|
patternPrompt string
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
"valid pattern": {
|
||||||
|
renamePrompt: "1",
|
||||||
|
patternPrompt: "hello-*",
|
||||||
|
expectedErr: "",
|
||||||
|
},
|
||||||
|
"invalid pattern, only one asterisk allowed": {
|
||||||
|
renamePrompt: "1",
|
||||||
|
patternPrompt: "hello-*-world-*",
|
||||||
|
expectedErr: "The pattern '*' cannot be used more than once.",
|
||||||
|
},
|
||||||
|
"invalid pattern, missing asterisk": {
|
||||||
|
renamePrompt: "1",
|
||||||
|
patternPrompt: "hello-world",
|
||||||
|
expectedErr: "The pattern must have an '*'",
|
||||||
|
},
|
||||||
|
"invalid rename": {
|
||||||
|
renamePrompt: "3",
|
||||||
|
expectedErr: "Please select 1 or 2 as part of this option.",
|
||||||
|
},
|
||||||
|
"no rename": {
|
||||||
|
renamePrompt: "2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, tc := range cases {
|
||||||
|
fmt.Println("Test: ", name)
|
||||||
|
m := testMetaBackend(t, nil)
|
||||||
|
input := map[string]string{}
|
||||||
|
cleanup := testInputMap(t, input)
|
||||||
|
if tc.renamePrompt != "" {
|
||||||
|
input["backend-migrate-multistate-to-tfc"] = tc.renamePrompt
|
||||||
|
}
|
||||||
|
if tc.patternPrompt != "" {
|
||||||
|
input["backend-migrate-multistate-to-tfc-pattern"] = tc.patternPrompt
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceType := "cloud"
|
||||||
|
_, err := m.promptMultiStateMigrationPattern(sourceType)
|
||||||
|
if tc.expectedErr == "" && err != nil {
|
||||||
|
t.Fatalf("expected error to be nil, but was %s", err.Error())
|
||||||
|
}
|
||||||
|
if tc.expectedErr != "" && tc.expectedErr != err.Error() {
|
||||||
|
t.Fatalf("expected error to eq %s but got %s", tc.expectedErr, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue