diff --git a/internal/cloud/e2e/init_with_empty_tags_test.go b/internal/cloud/e2e/init_with_empty_tags_test.go new file mode 100644 index 000000000..313d66e69 --- /dev/null +++ b/internal/cloud/e2e/init_with_empty_tags_test.go @@ -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) + } + } + } + } +} diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index 75eab4351..1729b42d9 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -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. `