Cloud: Init without erroring when no workspaces match the `tags` (#29835)
Previously, `terraform init` was throwing an error if you configured the cloud block with `tags` and there weren't any tagged workspaces yet. Confusing and alienating, since that that's a fairly normal situation! Basically TFC was handling an empty list of workspaces worse than other backends, because it doesn't support an unnamed default workspace. This commit catches that condition during `Meta.selectBackend()` and asks the user to pick a name for their first tagged workspace. If they cancel out, we still error, but if we know what name they want, we can handle it the same way as a nonexistent workspace specified in `name` -- just pass it to `Meta.SetWorkspace()`, and let the workspace get implicitly created when `InitCommand.Run()` eventually calls `StateMgr()`.
This commit is contained in:
parent
079aabb70e
commit
02e62c9851
|
@ -0,0 +1,108 @@
|
||||||
|
//go:build e2e
|
||||||
|
// +build e2e
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
expect "github.com/Netflix/go-expect"
|
||||||
|
"github.com/hashicorp/terraform/internal/e2e"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_init_with_empty_tags(t *testing.T) {
|
||||||
|
skipWithoutRemoteTerraformVersion(t)
|
||||||
|
|
||||||
|
cases := map[string]struct {
|
||||||
|
operations []operationSets
|
||||||
|
}{
|
||||||
|
"terraform init with cloud block - no tagged workspaces exist yet": {
|
||||||
|
operations: []operationSets{
|
||||||
|
{
|
||||||
|
prep: func(t *testing.T, orgName, dir string) {
|
||||||
|
wsTag := "emptytag"
|
||||||
|
tfBlock := terraformConfigCloudBackendTags(orgName, wsTag)
|
||||||
|
writeMainTF(t, tfBlock, dir)
|
||||||
|
},
|
||||||
|
commands: []tfCommand{
|
||||||
|
{
|
||||||
|
command: []string{"init"},
|
||||||
|
expectedCmdOutput: `There are no workspaces with the configured tags`,
|
||||||
|
userInput: []string{"emptytag-prod"},
|
||||||
|
postInputOutput: []string{`Terraform Cloud has been successfully initialized!`},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
tf.AddEnv("TF_LOG=info")
|
||||||
|
tf.AddEnv(cliConfigFileEnv)
|
||||||
|
defer tf.Close()
|
||||||
|
|
||||||
|
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 && !tfCmd.expectError {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -223,8 +223,25 @@ func (m *Meta) selectWorkspace(b backend.Backend) error {
|
||||||
return fmt.Errorf("Failed to get existing workspaces: %s", err)
|
return fmt.Errorf("Failed to get existing workspaces: %s", err)
|
||||||
}
|
}
|
||||||
if len(workspaces) == 0 {
|
if len(workspaces) == 0 {
|
||||||
|
if c, ok := b.(*cloud.Cloud); ok && m.input {
|
||||||
|
// len is always 1 if using Name; 0 means we're using Tags and there
|
||||||
|
// aren't any matching workspaces. Which might be normal and fine, so
|
||||||
|
// let's just ask:
|
||||||
|
name, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{
|
||||||
|
Id: "create-workspace",
|
||||||
|
Query: "\n[reset][bold][yellow]No workspaces found.[reset]",
|
||||||
|
Description: fmt.Sprintf(inputCloudInitCreateWorkspace, strings.Join(c.WorkspaceMapping.Tags, ", ")),
|
||||||
|
})
|
||||||
|
name = strings.TrimSpace(name)
|
||||||
|
if err != nil || name == "" {
|
||||||
|
return fmt.Errorf("Couldn't create initial workspace: no name provided")
|
||||||
|
}
|
||||||
|
log.Printf("[TRACE] Meta.selectWorkspace: selecting the new TFC workspace requested by the user (%s)", name)
|
||||||
|
return m.SetWorkspace(name)
|
||||||
|
} else {
|
||||||
return fmt.Errorf(strings.TrimSpace(errBackendNoExistingWorkspaces))
|
return fmt.Errorf(strings.TrimSpace(errBackendNoExistingWorkspaces))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get the currently selected workspace.
|
// Get the currently selected workspace.
|
||||||
workspace, err := m.Workspace()
|
workspace, err := m.Workspace()
|
||||||
|
@ -1376,6 +1393,15 @@ Terraform has detected that the configuration specified for the backend
|
||||||
has changed. Terraform will now check for existing state in the backends.
|
has changed. Terraform will now check for existing state in the backends.
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const inputCloudInitCreateWorkspace = `
|
||||||
|
There are no workspaces with the configured tags (%s)
|
||||||
|
in your Terraform Cloud organization. To finish initializing, Terraform needs at
|
||||||
|
least one workspace available.
|
||||||
|
|
||||||
|
Terraform can create a properly tagged workspace for you now. Please enter a
|
||||||
|
name to create a new Terraform Cloud workspace:
|
||||||
|
`
|
||||||
|
|
||||||
const successBackendUnset = `
|
const successBackendUnset = `
|
||||||
Successfully unset the backend %q. Terraform will now operate locally.
|
Successfully unset the backend %q. Terraform will now operate locally.
|
||||||
`
|
`
|
||||||
|
|
Loading…
Reference in New Issue