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:
Nick Fagerlund 2021-11-01 10:20:15 -07:00 committed by GitHub
parent 079aabb70e
commit 02e62c9851
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 135 additions and 1 deletions

View File

@ -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)
}
}
}
}
}

View File

@ -223,7 +223,24 @@ func (m *Meta) selectWorkspace(b backend.Backend) error {
return fmt.Errorf("Failed to get existing workspaces: %s", err)
}
if len(workspaces) == 0 {
return fmt.Errorf(strings.TrimSpace(errBackendNoExistingWorkspaces))
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))
}
}
// Get the currently selected 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.
`
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 = `
Successfully unset the backend %q. Terraform will now operate locally.
`