Backend State Migration: Add remote backend test
This commit is contained in:
parent
3fedd6898c
commit
dc76bbee73
|
@ -29,7 +29,7 @@ func Test_terraform_apply_autoApprove(t *testing.T) {
|
||||||
TerraformVersion: tfe.String(terraformVersion),
|
TerraformVersion: tfe.String(terraformVersion),
|
||||||
AutoApply: tfe.Bool(false),
|
AutoApply: tfe.Bool(false),
|
||||||
}
|
}
|
||||||
workspace := createWorkspace(t, org, wOpts)
|
workspace := createWorkspace(t, org.Name, wOpts)
|
||||||
cleanup := func() {
|
cleanup := func() {
|
||||||
defer orgCleanup()
|
defer orgCleanup()
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ func Test_terraform_apply_autoApprove(t *testing.T) {
|
||||||
TerraformVersion: tfe.String(terraformVersion),
|
TerraformVersion: tfe.String(terraformVersion),
|
||||||
AutoApply: tfe.Bool(true),
|
AutoApply: tfe.Bool(true),
|
||||||
}
|
}
|
||||||
workspace := createWorkspace(t, org, wOpts)
|
workspace := createWorkspace(t, org.Name, wOpts)
|
||||||
cleanup := func() {
|
cleanup := func() {
|
||||||
defer orgCleanup()
|
defer orgCleanup()
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ func Test_terraform_apply_autoApprove(t *testing.T) {
|
||||||
TerraformVersion: tfe.String(terraformVersion),
|
TerraformVersion: tfe.String(terraformVersion),
|
||||||
AutoApply: tfe.Bool(false),
|
AutoApply: tfe.Bool(false),
|
||||||
}
|
}
|
||||||
workspace := createWorkspace(t, org, wOpts)
|
workspace := createWorkspace(t, org.Name, wOpts)
|
||||||
cleanup := func() {
|
cleanup := func() {
|
||||||
defer orgCleanup()
|
defer orgCleanup()
|
||||||
}
|
}
|
||||||
|
@ -162,7 +162,7 @@ func Test_terraform_apply_autoApprove(t *testing.T) {
|
||||||
TerraformVersion: tfe.String(terraformVersion),
|
TerraformVersion: tfe.String(terraformVersion),
|
||||||
AutoApply: tfe.Bool(true),
|
AutoApply: tfe.Bool(true),
|
||||||
}
|
}
|
||||||
workspace := createWorkspace(t, org, wOpts)
|
workspace := createWorkspace(t, org.Name, wOpts)
|
||||||
cleanup := func() {
|
cleanup := func() {
|
||||||
defer orgCleanup()
|
defer orgCleanup()
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,9 +56,9 @@ func createOrganization(t *testing.T) (*tfe.Organization, func()) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createWorkspace(t *testing.T, org *tfe.Organization, wOpts tfe.WorkspaceCreateOptions) *tfe.Workspace {
|
func createWorkspace(t *testing.T, orgName string, wOpts tfe.WorkspaceCreateOptions) *tfe.Workspace {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
w, err := tfeClient.Workspaces.Create(ctx, org.Name, wOpts)
|
w, err := tfeClient.Workspaces.Create(ctx, orgName, wOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,15 @@ func createWorkspace(t *testing.T, org *tfe.Organization, wOpts tfe.WorkspaceCre
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getWorkspace(workspaces []*tfe.Workspace, workspace string) (*tfe.Workspace, bool) {
|
||||||
|
for _, ws := range workspaces {
|
||||||
|
if ws.Name == workspace {
|
||||||
|
return ws, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
|
||||||
func randomString(t *testing.T) string {
|
func randomString(t *testing.T) string {
|
||||||
v, err := uuid.GenerateUUID()
|
v, err := uuid.GenerateUUID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -87,6 +96,44 @@ output "val" {
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func terraformConfigRemoteBackendName(org, name string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
terraform {
|
||||||
|
backend "remote" {
|
||||||
|
hostname = "%s"
|
||||||
|
organization = "%s"
|
||||||
|
|
||||||
|
workspaces {
|
||||||
|
name = "%s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output "val" {
|
||||||
|
value = "${terraform.workspace}"
|
||||||
|
}
|
||||||
|
`, tfeHostname, org, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func terraformConfigRemoteBackendPrefix(org, prefix string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
terraform {
|
||||||
|
backend "remote" {
|
||||||
|
hostname = "%s"
|
||||||
|
organization = "%s"
|
||||||
|
|
||||||
|
workspaces {
|
||||||
|
prefix = "%s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output "val" {
|
||||||
|
value = "${terraform.workspace}"
|
||||||
|
}
|
||||||
|
`, tfeHostname, org, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
func terraformConfigCloudBackendTags(org, tag string) string {
|
func terraformConfigCloudBackendTags(org, tag string) string {
|
||||||
return fmt.Sprintf(`
|
return fmt.Sprintf(`
|
||||||
terraform {
|
terraform {
|
||||||
|
|
|
@ -5,7 +5,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -168,7 +167,7 @@ func Test_migrate_multi_to_tfc_cloud_name_strategy(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, tc := range cases {
|
for name, tc := range cases {
|
||||||
fmt.Println("Test: ", name)
|
t.Log("Test: ", name)
|
||||||
organization, cleanup := createOrganization(t)
|
organization, cleanup := createOrganization(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
exp, err := expect.NewConsole(expect.WithStdout(os.Stdout), expect.WithDefaultTimeout(expectConsoleTimeout))
|
exp, err := expect.NewConsole(expect.WithStdout(os.Stdout), expect.WithDefaultTimeout(expectConsoleTimeout))
|
||||||
|
@ -192,6 +191,7 @@ func Test_migrate_multi_to_tfc_cloud_name_strategy(t *testing.T) {
|
||||||
op.prep(t, organization.Name, tf.WorkDir())
|
op.prep(t, organization.Name, tf.WorkDir())
|
||||||
for _, tfCmd := range op.commands {
|
for _, tfCmd := range op.commands {
|
||||||
t.Log("Running commands: ", tfCmd.command)
|
t.Log("Running commands: ", tfCmd.command)
|
||||||
|
tfCmd.command = append(tfCmd.command, "-ignore-remote-version")
|
||||||
cmd := tf.Cmd(tfCmd.command...)
|
cmd := tf.Cmd(tfCmd.command...)
|
||||||
cmd.Stdin = exp.Tty()
|
cmd.Stdin = exp.Tty()
|
||||||
cmd.Stdout = exp.Tty()
|
cmd.Stdout = exp.Tty()
|
||||||
|
@ -357,7 +357,7 @@ func Test_migrate_multi_to_tfc_cloud_tags_strategy(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, tc := range cases {
|
for name, tc := range cases {
|
||||||
fmt.Println("Test: ", name)
|
t.Log("Test: ", name)
|
||||||
organization, cleanup := createOrganization(t)
|
organization, cleanup := createOrganization(t)
|
||||||
t.Log(organization.Name)
|
t.Log(organization.Name)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
|
@ -4,56 +4,912 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
expect "github.com/Netflix/go-expect"
|
||||||
|
tfe "github.com/hashicorp/go-tfe"
|
||||||
|
"github.com/hashicorp/terraform/internal/e2e"
|
||||||
)
|
)
|
||||||
|
|
||||||
// REMOTE BACKEND
|
func Test_migrate_remote_backend_name_to_tfc_name(t *testing.T) {
|
||||||
/*
|
ctx := context.Background()
|
||||||
- RB name -> TFC name
|
cases := map[string]struct {
|
||||||
-- straight copy if only if different name, or same WS name in diff org
|
|
||||||
-- other
|
|
||||||
-- ensure that the local workspace, after migration, is the new name (in the tfc config block)
|
|
||||||
- RB name -> TFC tags
|
|
||||||
-- just add tag, if in same org
|
|
||||||
-- If new org, if WS exists, just add tag
|
|
||||||
-- If new org, if WS not exists, create and add tag
|
|
||||||
- RB prefix -> TFC name
|
|
||||||
-- create if not exists
|
|
||||||
-- migrate the current worksapce state to ws name
|
|
||||||
- RB prefix -> TFC tags
|
|
||||||
-- update previous workspaces (prefix + local) with cloud config tag
|
|
||||||
-- Rename the local workspaces to match the TFC workspaces (prefix + former local, ie app-prod). inform user
|
|
||||||
|
|
||||||
*/
|
|
||||||
func Test_migrate_remote_backend_name_to_tfc(t *testing.T) {
|
|
||||||
t.Skip("TODO: see comments")
|
|
||||||
_ = map[string]struct {
|
|
||||||
operations []operationSets
|
operations []operationSets
|
||||||
validations func(t *testing.T, orgName string)
|
validations func(t *testing.T, orgName string)
|
||||||
}{
|
}{
|
||||||
"single workspace with backend name strategy, to cloud with name strategy": {},
|
"backend name strategy, to cloud with name strategy": {
|
||||||
"single workspace with backend name strategy, to cloud with tags strategy": {},
|
operations: []operationSets{
|
||||||
|
{
|
||||||
|
prep: func(t *testing.T, orgName, dir string) {
|
||||||
|
remoteWorkspace := "remote-workspace"
|
||||||
|
tfBlock := terraformConfigRemoteBackendName(orgName, remoteWorkspace)
|
||||||
|
writeMainTF(t, tfBlock, dir)
|
||||||
|
},
|
||||||
|
commands: []tfCommand{
|
||||||
|
{
|
||||||
|
command: []string{"init"},
|
||||||
|
expectedCmdOutput: `Successfully configured the backend "remote"!`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"apply"},
|
||||||
|
expectedCmdOutput: `Do you want to perform these actions in workspace "remote-workspace"?`,
|
||||||
|
userInput: []string{"yes"},
|
||||||
|
postInputOutput: []string{`Apply complete!`},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prep: func(t *testing.T, orgName, dir string) {
|
||||||
|
wsName := "cloud-workspace"
|
||||||
|
tfBlock := terraformConfigCloudBackendName(orgName, wsName)
|
||||||
|
writeMainTF(t, tfBlock, dir)
|
||||||
|
},
|
||||||
|
commands: []tfCommand{
|
||||||
|
{
|
||||||
|
command: []string{"init", "-migrate-state", "-ignore-remote-version"},
|
||||||
|
expectedCmdOutput: `Do you want to copy existing state to the new backend?`,
|
||||||
|
userInput: []string{"yes"},
|
||||||
|
postInputOutput: []string{`Successfully configured the backend "cloud"!`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "show"},
|
||||||
|
expectedCmdOutput: `cloud-workspace`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validations: func(t *testing.T, orgName string) {
|
||||||
|
expectedName := "cloud-workspace"
|
||||||
|
ws, err := tfeClient.Workspaces.Read(ctx, orgName, expectedName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if ws == nil {
|
||||||
|
t.Fatalf("Expected workspace %s to be present, but is not.", expectedName)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"backend name strategy, to cloud name strategy, using the same name": {
|
||||||
|
operations: []operationSets{
|
||||||
|
{
|
||||||
|
prep: func(t *testing.T, orgName, dir string) {
|
||||||
|
remoteWorkspace := "remote-workspace"
|
||||||
|
tfBlock := terraformConfigRemoteBackendName(orgName, remoteWorkspace)
|
||||||
|
writeMainTF(t, tfBlock, dir)
|
||||||
|
},
|
||||||
|
commands: []tfCommand{
|
||||||
|
{
|
||||||
|
command: []string{"init"},
|
||||||
|
expectedCmdOutput: `Successfully configured the backend "remote"!`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"apply"},
|
||||||
|
expectedCmdOutput: `Do you want to perform these actions in workspace "remote-workspace"?`,
|
||||||
|
userInput: []string{"yes"},
|
||||||
|
postInputOutput: []string{`Apply complete!`},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prep: func(t *testing.T, orgName, dir string) {
|
||||||
|
wsName := "remote-workspace"
|
||||||
|
tfBlock := terraformConfigCloudBackendName(orgName, wsName)
|
||||||
|
writeMainTF(t, tfBlock, dir)
|
||||||
|
},
|
||||||
|
commands: []tfCommand{
|
||||||
|
{
|
||||||
|
command: []string{"init", "-migrate-state", "-ignore-remote-version"},
|
||||||
|
expectedCmdOutput: `Terraform has been successfully initialized!`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "show"},
|
||||||
|
expectedCmdOutput: `remote-workspace`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validations: func(t *testing.T, orgName string) {
|
||||||
|
expectedName := "remote-workspace"
|
||||||
|
ws, err := tfeClient.Workspaces.Read(ctx, orgName, expectedName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if ws == nil {
|
||||||
|
t.Fatalf("Expected workspace %s to be present, but is not.", expectedName)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
t.Log("Test: ", name)
|
||||||
|
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)
|
||||||
|
tf.AddEnv("TF_LOG=INFO")
|
||||||
|
tf.AddEnv(cliConfigFileEnv)
|
||||||
|
defer tf.Close()
|
||||||
|
|
||||||
|
organization, cleanup := createOrganization(t)
|
||||||
|
defer cleanup()
|
||||||
|
for _, op := range tc.operations {
|
||||||
|
op.prep(t, organization.Name, tf.WorkDir())
|
||||||
|
for _, tfCmd := range op.commands {
|
||||||
|
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_remote_backend_name_to_tfc_name_different_org(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
cases := map[string]struct {
|
||||||
|
operations []operationSets
|
||||||
|
validations func(t *testing.T, orgName string)
|
||||||
|
}{
|
||||||
|
"backend name strategy, to cloud name strategy, using the same name, different organization": {
|
||||||
|
operations: []operationSets{
|
||||||
|
{
|
||||||
|
prep: func(t *testing.T, orgName, dir string) {
|
||||||
|
remoteWorkspace := "remote-workspace"
|
||||||
|
tfBlock := terraformConfigRemoteBackendName(orgName, remoteWorkspace)
|
||||||
|
writeMainTF(t, tfBlock, dir)
|
||||||
|
},
|
||||||
|
commands: []tfCommand{
|
||||||
|
{
|
||||||
|
command: []string{"init"},
|
||||||
|
expectedCmdOutput: `Successfully configured the backend "remote"!`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"apply"},
|
||||||
|
expectedCmdOutput: `Do you want to perform these actions in workspace "remote-workspace"?`,
|
||||||
|
userInput: []string{"yes"},
|
||||||
|
postInputOutput: []string{`Apply complete!`},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prep: func(t *testing.T, orgName, dir string) {
|
||||||
|
wsName := "remote-workspace"
|
||||||
|
tfBlock := terraformConfigCloudBackendName(orgName, wsName)
|
||||||
|
writeMainTF(t, tfBlock, dir)
|
||||||
|
},
|
||||||
|
commands: []tfCommand{
|
||||||
|
{
|
||||||
|
command: []string{"init", "-migrate-state", "-ignore-remote-version"},
|
||||||
|
expectedCmdOutput: `Do you want to copy existing state to the new backend?`,
|
||||||
|
userInput: []string{"yes"},
|
||||||
|
postInputOutput: []string{`Successfully configured the backend "cloud"!`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "show"},
|
||||||
|
expectedCmdOutput: `remote-workspace`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validations: func(t *testing.T, orgName string) {
|
||||||
|
expectedName := "remote-workspace"
|
||||||
|
ws, err := tfeClient.Workspaces.Read(ctx, orgName, expectedName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if ws == nil {
|
||||||
|
t.Fatalf("Expected workspace %s to be present, but is not.", expectedName)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
t.Log("Test: ", name)
|
||||||
|
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)
|
||||||
|
tf.AddEnv("TF_LOG=INFO")
|
||||||
|
tf.AddEnv(cliConfigFileEnv)
|
||||||
|
defer tf.Close()
|
||||||
|
|
||||||
|
orgOne, cleanupOne := createOrganization(t)
|
||||||
|
orgTwo, cleanupTwo := createOrganization(t)
|
||||||
|
defer cleanupOne()
|
||||||
|
defer cleanupTwo()
|
||||||
|
orgs := []string{orgOne.Name, orgTwo.Name}
|
||||||
|
var orgName string
|
||||||
|
for index, op := range tc.operations {
|
||||||
|
orgName = orgs[index]
|
||||||
|
op.prep(t, orgName, tf.WorkDir())
|
||||||
|
for _, tfCmd := range op.commands {
|
||||||
|
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, orgName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_migrate_remote_backend_name_to_tfc_tags(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
cases := map[string]struct {
|
||||||
|
operations []operationSets
|
||||||
|
validations func(t *testing.T, orgName string)
|
||||||
|
}{
|
||||||
|
"single workspace with backend name strategy, to cloud with tags strategy": {
|
||||||
|
operations: []operationSets{
|
||||||
|
{
|
||||||
|
prep: func(t *testing.T, orgName, dir string) {
|
||||||
|
remoteWorkspace := "remote-workspace"
|
||||||
|
tfBlock := terraformConfigRemoteBackendName(orgName, remoteWorkspace)
|
||||||
|
writeMainTF(t, tfBlock, dir)
|
||||||
|
},
|
||||||
|
commands: []tfCommand{
|
||||||
|
{
|
||||||
|
command: []string{"init"},
|
||||||
|
expectedCmdOutput: `Successfully configured the backend "remote"!`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"apply"},
|
||||||
|
expectedCmdOutput: `Do you want to perform these actions in workspace "remote-workspace"?`,
|
||||||
|
userInput: []string{"yes"},
|
||||||
|
postInputOutput: []string{`Apply complete!`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "show"},
|
||||||
|
expectedCmdOutput: `default`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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", "-ignore-remote-version"},
|
||||||
|
expectedCmdOutput: `The "cloud" backend configuration only allows named workspaces!`,
|
||||||
|
userInput: []string{"cloud-workspace", "yes"},
|
||||||
|
postInputOutput: []string{
|
||||||
|
`Do you want to copy existing state to the new backend?`,
|
||||||
|
`Successfully configured the backend "cloud"!`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "show"},
|
||||||
|
expectedCmdOutput: `cloud-workspace`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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) != 1 {
|
||||||
|
t.Fatalf("Expected number of workspaces to be 1, but got %d", len(wsList.Items))
|
||||||
|
}
|
||||||
|
ws := wsList.Items[0]
|
||||||
|
if ws.Name != "cloud-workspace" {
|
||||||
|
t.Fatalf("Expected workspace to be `cloud-workspace`, but is %s", ws.Name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
t.Log("Test: ", name)
|
||||||
|
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)
|
||||||
|
tf.AddEnv("TF_LOG=INFO")
|
||||||
|
tf.AddEnv(cliConfigFileEnv)
|
||||||
|
defer tf.Close()
|
||||||
|
|
||||||
|
organization, cleanup := createOrganization(t)
|
||||||
|
defer cleanup()
|
||||||
|
for _, op := range tc.operations {
|
||||||
|
op.prep(t, organization.Name, tf.WorkDir())
|
||||||
|
for _, tfCmd := range op.commands {
|
||||||
|
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_remote_backend_prefix_to_tfc_name(t *testing.T) {
|
func Test_migrate_remote_backend_prefix_to_tfc_name(t *testing.T) {
|
||||||
t.Skip("TODO: see comments")
|
ctx := context.Background()
|
||||||
_ = map[string]struct {
|
cases := map[string]struct {
|
||||||
operations []operationSets
|
operations []operationSets
|
||||||
validations func(t *testing.T, orgName string)
|
validations func(t *testing.T, orgName string)
|
||||||
}{
|
}{
|
||||||
"single workspace with backend prefix strategy, to cloud with name strategy": {},
|
"single workspace with backend prefix strategy, to cloud with name strategy": {
|
||||||
"multiple workspaces with backend prefix strategy, to cloud with name strategy": {},
|
operations: []operationSets{
|
||||||
|
{
|
||||||
|
prep: func(t *testing.T, orgName, dir string) {
|
||||||
|
_ = createWorkspace(t, orgName, tfe.WorkspaceCreateOptions{Name: tfe.String("app-one")})
|
||||||
|
prefix := "app-"
|
||||||
|
tfBlock := terraformConfigRemoteBackendPrefix(orgName, prefix)
|
||||||
|
writeMainTF(t, tfBlock, dir)
|
||||||
|
},
|
||||||
|
commands: []tfCommand{
|
||||||
|
{
|
||||||
|
command: []string{"init"},
|
||||||
|
expectedCmdOutput: `Terraform has been successfully initialized!`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"apply"},
|
||||||
|
expectedCmdOutput: `Do you want to perform these actions in workspace "app-one"?`,
|
||||||
|
userInput: []string{"yes"},
|
||||||
|
postInputOutput: []string{`Apply complete!`},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prep: func(t *testing.T, orgName, dir string) {
|
||||||
|
wsName := "cloud-workspace"
|
||||||
|
tfBlock := terraformConfigCloudBackendName(orgName, wsName)
|
||||||
|
writeMainTF(t, tfBlock, dir)
|
||||||
|
},
|
||||||
|
commands: []tfCommand{
|
||||||
|
{
|
||||||
|
command: []string{"init", "-migrate-state", "-ignore-remote-version"},
|
||||||
|
expectedCmdOutput: `Do you want to copy existing state to the new backend?`,
|
||||||
|
userInput: []string{"yes"},
|
||||||
|
postInputOutput: []string{
|
||||||
|
`Successfully configured the backend "cloud"!`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "show"},
|
||||||
|
expectedCmdOutput: `cloud-workspace`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validations: func(t *testing.T, orgName string) {
|
||||||
|
expectedName := "cloud-workspace"
|
||||||
|
ws, err := tfeClient.Workspaces.Read(ctx, orgName, expectedName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if ws == nil {
|
||||||
|
t.Fatalf("Expected workspace %s to be present, but is not.", expectedName)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"multiple workspaces with backend prefix strategy, to cloud with name strategy": {
|
||||||
|
operations: []operationSets{
|
||||||
|
{
|
||||||
|
prep: func(t *testing.T, orgName, dir string) {
|
||||||
|
_ = createWorkspace(t, orgName, tfe.WorkspaceCreateOptions{Name: tfe.String("app-one")})
|
||||||
|
_ = createWorkspace(t, orgName, tfe.WorkspaceCreateOptions{Name: tfe.String("app-two")})
|
||||||
|
prefix := "app-"
|
||||||
|
tfBlock := terraformConfigRemoteBackendPrefix(orgName, prefix)
|
||||||
|
writeMainTF(t, tfBlock, dir)
|
||||||
|
},
|
||||||
|
commands: []tfCommand{
|
||||||
|
{
|
||||||
|
command: []string{"init"},
|
||||||
|
expectedCmdOutput: `The currently selected workspace (default) does not exist.`,
|
||||||
|
userInput: []string{"1"},
|
||||||
|
postInputOutput: []string{`Terraform has been successfully initialized!`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"apply"},
|
||||||
|
expectedCmdOutput: `Do you want to perform these actions in workspace "app-one"?`,
|
||||||
|
userInput: []string{"yes"},
|
||||||
|
postInputOutput: []string{`Apply complete!`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "list"},
|
||||||
|
expectedCmdOutput: "* one", // app name retrieved via prefix
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "select", "two"},
|
||||||
|
expectedCmdOutput: `Switched to workspace "two".`, // app name retrieved via prefix
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prep: func(t *testing.T, orgName, dir string) {
|
||||||
|
wsName := "cloud-workspace"
|
||||||
|
tfBlock := terraformConfigCloudBackendName(orgName, wsName)
|
||||||
|
writeMainTF(t, tfBlock, dir)
|
||||||
|
},
|
||||||
|
commands: []tfCommand{
|
||||||
|
{
|
||||||
|
command: []string{"init", "-migrate-state", "-ignore-remote-version"},
|
||||||
|
expectedCmdOutput: `Do you want to copy only your current workspace?`,
|
||||||
|
userInput: []string{"yes"},
|
||||||
|
postInputOutput: []string{
|
||||||
|
`Successfully configured the backend "cloud"!`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "show"},
|
||||||
|
expectedCmdOutput: `cloud-workspace`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validations: func(t *testing.T, orgName string) {
|
||||||
|
expectedName := "cloud-workspace"
|
||||||
|
ws, err := tfeClient.Workspaces.Read(ctx, orgName, expectedName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if ws == nil {
|
||||||
|
t.Fatalf("Expected workspace %s to be present, but is not.", expectedName)
|
||||||
|
}
|
||||||
|
wsList, err := tfeClient.Workspaces.List(ctx, orgName, tfe.WorkspaceListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(wsList.Items) != 3 {
|
||||||
|
t.Fatalf("expected number of workspaces in this org to be 3, but got %d", len(wsList.Items))
|
||||||
|
}
|
||||||
|
ws, empty := getWorkspace(wsList.Items, "cloud-workspace")
|
||||||
|
if empty {
|
||||||
|
t.Fatalf("expected workspaces to include 'cloud-workspace' but didn't.")
|
||||||
|
}
|
||||||
|
ws, empty = getWorkspace(wsList.Items, "app-one")
|
||||||
|
if empty {
|
||||||
|
t.Fatalf("expected workspaces to include 'app-one' but didn't.")
|
||||||
|
}
|
||||||
|
ws, empty = getWorkspace(wsList.Items, "app-two")
|
||||||
|
if empty {
|
||||||
|
t.Fatalf("expected workspaces to include 'app-two' but didn't.")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
t.Log("Test: ", name)
|
||||||
|
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)
|
||||||
|
tf.AddEnv("TF_LOG=INFO")
|
||||||
|
tf.AddEnv(cliConfigFileEnv)
|
||||||
|
defer tf.Close()
|
||||||
|
|
||||||
|
organization, cleanup := createOrganization(t)
|
||||||
|
defer cleanup()
|
||||||
|
for _, op := range tc.operations {
|
||||||
|
op.prep(t, organization.Name, tf.WorkDir())
|
||||||
|
for _, tfCmd := range op.commands {
|
||||||
|
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_remote_backend_prefix_to_tfc_tags(t *testing.T) {
|
func Test_migrate_remote_backend_prefix_to_tfc_tags(t *testing.T) {
|
||||||
t.Skip("TODO: see comments")
|
ctx := context.Background()
|
||||||
_ = map[string]struct {
|
cases := map[string]struct {
|
||||||
operations []operationSets
|
operations []operationSets
|
||||||
validations func(t *testing.T, orgName string)
|
validations func(t *testing.T, orgName string)
|
||||||
}{
|
}{
|
||||||
"single workspace with backend prefix strategy, to cloud with tags strategy": {},
|
"single workspace with backend prefix strategy, to cloud with tags strategy": {
|
||||||
"multiple workspaces with backend prefix strategy, to cloud with tags strategy": {},
|
operations: []operationSets{
|
||||||
|
{
|
||||||
|
prep: func(t *testing.T, orgName, dir string) {
|
||||||
|
_ = createWorkspace(t, orgName, tfe.WorkspaceCreateOptions{Name: tfe.String("app-one")})
|
||||||
|
prefix := "app-"
|
||||||
|
tfBlock := terraformConfigRemoteBackendPrefix(orgName, prefix)
|
||||||
|
writeMainTF(t, tfBlock, dir)
|
||||||
|
},
|
||||||
|
commands: []tfCommand{
|
||||||
|
{
|
||||||
|
command: []string{"init"},
|
||||||
|
expectedCmdOutput: `Terraform has been successfully initialized!`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"apply"},
|
||||||
|
expectedCmdOutput: `Do you want to perform these actions in workspace "app-one"?`,
|
||||||
|
userInput: []string{"yes"},
|
||||||
|
postInputOutput: []string{`Apply complete!`},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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", "-ignore-remote-version"},
|
||||||
|
expectedCmdOutput: `The "cloud" backend configuration only allows named workspaces!`,
|
||||||
|
userInput: []string{"cloud-workspace", "yes"},
|
||||||
|
postInputOutput: []string{
|
||||||
|
`Do you want to copy existing state to the new backend?`,
|
||||||
|
`Successfully configured the backend "cloud"!`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "list"},
|
||||||
|
expectedCmdOutput: `cloud-workspace`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validations: func(t *testing.T, orgName string) {
|
||||||
|
expectedName := "cloud-workspace"
|
||||||
|
ws, err := tfeClient.Workspaces.Read(ctx, orgName, expectedName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if ws == nil {
|
||||||
|
t.Fatalf("Expected workspace %s to be present, but is not.", expectedName)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"multiple workspaces with backend prefix strategy, to cloud with tags strategy": {
|
||||||
|
operations: []operationSets{
|
||||||
|
{
|
||||||
|
prep: func(t *testing.T, orgName, dir string) {
|
||||||
|
_ = createWorkspace(t, orgName, tfe.WorkspaceCreateOptions{Name: tfe.String("app-one")})
|
||||||
|
_ = createWorkspace(t, orgName, tfe.WorkspaceCreateOptions{Name: tfe.String("app-two")})
|
||||||
|
prefix := "app-"
|
||||||
|
tfBlock := terraformConfigRemoteBackendPrefix(orgName, prefix)
|
||||||
|
writeMainTF(t, tfBlock, dir)
|
||||||
|
},
|
||||||
|
commands: []tfCommand{
|
||||||
|
{
|
||||||
|
command: []string{"init"},
|
||||||
|
expectedCmdOutput: `The currently selected workspace (default) does not exist.`,
|
||||||
|
userInput: []string{"1"},
|
||||||
|
postInputOutput: []string{`Terraform has been successfully initialized!`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"apply"},
|
||||||
|
expectedCmdOutput: `Do you want to perform these actions in workspace "app-one"?`,
|
||||||
|
userInput: []string{"yes"},
|
||||||
|
postInputOutput: []string{`Apply complete!`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "select", "two"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"apply"},
|
||||||
|
expectedCmdOutput: `Do you want to perform these actions in workspace "app-two"?`,
|
||||||
|
userInput: []string{"yes"},
|
||||||
|
postInputOutput: []string{`Apply complete!`},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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", "-ignore-remote-version"},
|
||||||
|
expectedCmdOutput: `Would you like to rename your workspaces?`,
|
||||||
|
userInput: []string{"1", "*"},
|
||||||
|
postInputOutput: []string{`What pattern would you like to add to all your workspaces?`,
|
||||||
|
`Successfully configured the backend "cloud"!`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "show"},
|
||||||
|
expectedCmdOutput: "two", // this comes from the original workspace name from the previous backend.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: []string{"workspace", "select", "one"},
|
||||||
|
expectedCmdOutput: `Switched to workspace "one".`, // this comes from the original workspace name from the previous backend.
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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.Logf("Expected the number of workspaces to be 2, but got %d", len(wsList.Items))
|
||||||
|
}
|
||||||
|
ws, empty := getWorkspace(wsList.Items, "one")
|
||||||
|
if empty {
|
||||||
|
t.Fatalf("expected workspaces to include 'one' but didn't.")
|
||||||
|
}
|
||||||
|
if len(ws.TagNames) == 0 {
|
||||||
|
t.Fatalf("expected workspaces 'one' to have tags.")
|
||||||
|
}
|
||||||
|
ws, empty = getWorkspace(wsList.Items, "two")
|
||||||
|
if empty {
|
||||||
|
t.Fatalf("expected workspaces to include 'two' but didn't.")
|
||||||
|
}
|
||||||
|
if len(ws.TagNames) == 0 {
|
||||||
|
t.Fatalf("expected workspaces 'two' to have tags.")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
t.Log("Test: ", name)
|
||||||
|
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)
|
||||||
|
tf.AddEnv("TF_LOG=INFO")
|
||||||
|
tf.AddEnv(cliConfigFileEnv)
|
||||||
|
defer tf.Close()
|
||||||
|
|
||||||
|
organization, cleanup := createOrganization(t)
|
||||||
|
defer cleanup()
|
||||||
|
for _, op := range tc.operations {
|
||||||
|
op.prep(t, organization.Name, tf.WorkDir())
|
||||||
|
for _, tfCmd := range op.commands {
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,16 +57,16 @@ func Test_cloud_run_variables(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"!`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: []string{"plan", "-var", "foo=bar"},
|
command: []string{"plan", "-var", "foo=bar"},
|
||||||
expectedOutput: ` + test_cli = "bar"`,
|
expectedCmdOutput: ` + test_cli = "bar"`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: []string{"plan", "-var", "foo=bar"},
|
command: []string{"plan", "-var", "foo=bar"},
|
||||||
expectedOutput: ` + test_env = "qux"`,
|
expectedCmdOutput: ` + test_env = "qux"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -110,23 +110,28 @@ func Test_cloud_run_variables(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ type backendMigrateOpts struct {
|
||||||
//
|
//
|
||||||
// This will attempt to lock both states for the migration.
|
// This will attempt to lock both states for the migration.
|
||||||
func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
|
func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
|
||||||
log.Printf("[TRACE] backendMigrateState: need to migrate from %q to %q backend config", opts.SourceType, opts.DestinationType)
|
log.Printf("[INFO] backendMigrateState: need to migrate from %q to %q backend config", opts.SourceType, opts.DestinationType)
|
||||||
// We need to check what the named state status is. If we're converting
|
// We need to check what the named state status is. If we're converting
|
||||||
// from multi-state to single-state for example, we need to handle that.
|
// from multi-state to single-state for example, we need to handle that.
|
||||||
var sourceSingleState, destinationSingleState, sourceTFC, destinationTFC bool
|
var sourceSingleState, destinationSingleState, sourceTFC, destinationTFC bool
|
||||||
|
@ -154,7 +154,7 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
|
||||||
|
|
||||||
// Multi-state to multi-state.
|
// Multi-state to multi-state.
|
||||||
func (m *Meta) backendMigrateState_S_S(opts *backendMigrateOpts) error {
|
func (m *Meta) backendMigrateState_S_S(opts *backendMigrateOpts) error {
|
||||||
log.Print("[TRACE] backendMigrateState: migrating all named workspaces")
|
log.Print("[INFO] backendMigrateState: migrating all named workspaces")
|
||||||
|
|
||||||
migrate := opts.force
|
migrate := opts.force
|
||||||
if !migrate {
|
if !migrate {
|
||||||
|
@ -209,9 +209,9 @@ func (m *Meta) backendMigrateState_S_S(opts *backendMigrateOpts) error {
|
||||||
|
|
||||||
// Multi-state to single state.
|
// Multi-state to single state.
|
||||||
func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
|
func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
|
||||||
log.Printf("[TRACE] backendMigrateState: destination backend type %q does not support named workspaces", opts.DestinationType)
|
log.Printf("[INFO] backendMigrateState: destination backend type %q does not support named workspaces", opts.DestinationType)
|
||||||
|
|
||||||
currentEnv, err := m.Workspace()
|
currentWorkspace, err := m.Workspace()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -228,7 +228,7 @@ func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
|
||||||
opts.DestinationType),
|
opts.DestinationType),
|
||||||
Description: fmt.Sprintf(
|
Description: fmt.Sprintf(
|
||||||
strings.TrimSpace(inputBackendMigrateMultiToSingle),
|
strings.TrimSpace(inputBackendMigrateMultiToSingle),
|
||||||
opts.SourceType, opts.DestinationType, currentEnv),
|
opts.SourceType, opts.DestinationType, currentWorkspace),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
|
@ -241,7 +241,7 @@ func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the default state
|
// Copy the default state
|
||||||
opts.sourceWorkspace = currentEnv
|
opts.sourceWorkspace = currentWorkspace
|
||||||
|
|
||||||
// now switch back to the default env so we can acccess the new backend
|
// now switch back to the default env so we can acccess the new backend
|
||||||
m.SetWorkspace(backend.DefaultStateName)
|
m.SetWorkspace(backend.DefaultStateName)
|
||||||
|
@ -251,7 +251,7 @@ func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
|
||||||
|
|
||||||
// Single state to single state, assumed default state name.
|
// Single state to single state, assumed default state name.
|
||||||
func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
|
func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
|
||||||
log.Printf("[TRACE] backendMigrateState: migrating %q workspace to %q workspace", opts.sourceWorkspace, opts.destinationWorkspace)
|
log.Printf("[INFO] backendMigrateState: single-to-single migrating %q workspace to %q workspace", opts.sourceWorkspace, opts.destinationWorkspace)
|
||||||
|
|
||||||
sourceState, err := opts.Source.StateMgr(opts.sourceWorkspace)
|
sourceState, err := opts.Source.StateMgr(opts.sourceWorkspace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -534,7 +534,7 @@ func (m *Meta) backendMigrateTFC(opts *backendMigrateOpts) error {
|
||||||
// Everything below, by the above two conditionals, now assumes that the
|
// Everything below, by the above two conditionals, now assumes that the
|
||||||
// destination is always Terraform Cloud (TFC).
|
// destination is always Terraform Cloud (TFC).
|
||||||
|
|
||||||
sourceSingle := sourceSingleState || (len(sourceWorkspaces) == 1 && sourceWorkspaces[0] == backend.DefaultStateName)
|
sourceSingle := sourceSingleState || (len(sourceWorkspaces) == 1)
|
||||||
if sourceSingle {
|
if sourceSingle {
|
||||||
if cloudBackendDestination.WorkspaceMapping.Strategy() == cloud.WorkspaceNameStrategy {
|
if cloudBackendDestination.WorkspaceMapping.Strategy() == cloud.WorkspaceNameStrategy {
|
||||||
// If we know the name via WorkspaceNameStrategy, then set the
|
// If we know the name via WorkspaceNameStrategy, then set the
|
||||||
|
@ -543,6 +543,14 @@ func (m *Meta) backendMigrateTFC(opts *backendMigrateOpts) error {
|
||||||
// in TFC if it does not exist.
|
// in TFC if it does not exist.
|
||||||
opts.destinationWorkspace = cloudBackendDestination.WorkspaceMapping.Name
|
opts.destinationWorkspace = cloudBackendDestination.WorkspaceMapping.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentWorkspace, err := m.Workspace()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
opts.sourceWorkspace = currentWorkspace
|
||||||
|
|
||||||
|
log.Printf("[INFO] backendMigrateTFC: single-to-single migration from source %s to destination %q", opts.sourceWorkspace, opts.destinationWorkspace)
|
||||||
// Run normal single-to-single state migration
|
// Run normal single-to-single state migration
|
||||||
// This will handle both situations where the new cloud backend
|
// This will handle both situations where the new cloud backend
|
||||||
// configuration is using a workspace.name strategy or workspace.tags
|
// configuration is using a workspace.name strategy or workspace.tags
|
||||||
|
@ -559,13 +567,14 @@ func (m *Meta) backendMigrateTFC(opts *backendMigrateOpts) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
currentEnv, err := m.Workspace()
|
currentWorkspace, err := m.Workspace()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.sourceWorkspace = currentEnv
|
opts.sourceWorkspace = currentWorkspace
|
||||||
opts.destinationWorkspace = cloudBackendDestination.WorkspaceMapping.Name
|
opts.destinationWorkspace = cloudBackendDestination.WorkspaceMapping.Name
|
||||||
|
log.Printf("[INFO] backendMigrateTFC: multi-to-single migration from source %s to destination %q", opts.sourceWorkspace, opts.destinationWorkspace)
|
||||||
|
|
||||||
return m.backendMigrateState_s_s(opts)
|
return m.backendMigrateState_s_s(opts)
|
||||||
}
|
}
|
||||||
|
@ -574,9 +583,13 @@ func (m *Meta) backendMigrateTFC(opts *backendMigrateOpts) error {
|
||||||
// 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 {
|
||||||
|
log.Printf("[INFO] backendMigrateTFC: multi-to-multi migration from source workspaces %q", sourceWorkspaces)
|
||||||
return m.backendMigrateState_S_TFC(opts, sourceWorkspaces)
|
return m.backendMigrateState_S_TFC(opts, sourceWorkspaces)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(omar): after the check for sourceSingle is done, everything following
|
||||||
|
// it has to be multi. So rework the code to not need to check for multi, adn
|
||||||
|
// return m.backendMigrateState_S_TFC here.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -619,6 +632,7 @@ func (m *Meta) backendMigrateState_S_TFC(opts *backendMigrateOpts, sourceWorkspa
|
||||||
opts.force = true
|
opts.force = true
|
||||||
|
|
||||||
// Perform the migration
|
// Perform the migration
|
||||||
|
log.Printf("[INFO] backendMigrateTFC: multi-to-multi migration, source workspace %q to destination workspace %q", opts.sourceWorkspace, opts.destinationWorkspace)
|
||||||
if err := m.backendMigrateState_s_s(opts); err != nil {
|
if err := m.backendMigrateState_s_s(opts); err != nil {
|
||||||
return fmt.Errorf(strings.TrimSpace(
|
return fmt.Errorf(strings.TrimSpace(
|
||||||
errMigrateMulti), name, opts.SourceType, opts.DestinationType, err)
|
errMigrateMulti), name, opts.SourceType, opts.DestinationType, err)
|
||||||
|
|
Loading…
Reference in New Issue