command: Auto-select single workspace if necessary

When initializing a backend, if the currently selected workspace does
not exist, the user is prompted to select from the list of workspaces
the backend provides.

Instead, we should automatically select the only workspace available
_if_ that's all that's there.

Although with being a nice bit of polish, this enables future
improvments with Terraform Cloud in potentially removing the implicit
depenency on always using the 'default' workspace when the current
configuration is mapped to a single TFC workspace.
This commit is contained in:
Chris Arcand 2021-09-21 23:29:02 -05:00
parent cd1d30ea95
commit 60bc7aa05d
10 changed files with 177 additions and 4 deletions

View File

@ -196,13 +196,19 @@ func (m *Meta) selectWorkspace(b backend.Backend) error {
var list strings.Builder
for i, w := range workspaces {
if w == workspace {
log.Printf("[TRACE] Meta.selectWorkspace: the currently selected workspace is present in the configured backend (%s)", workspace)
return nil
}
fmt.Fprintf(&list, "%d. %s\n", i+1, w)
}
// If the selected workspace doesn't exist, ask the user to select
// a workspace from the list of existing workspaces.
// If the backend only has a single workspace, select that as the current workspace
if len(workspaces) == 1 {
log.Printf("[TRACE] Meta.selectWorkspace: automatically selecting the single workspace provided by the backend (%s)", workspaces[0])
return m.SetWorkspace(workspaces[0])
}
// Otherwise, ask the user to select a workspace from the list of existing workspaces.
v, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{
Id: "select-workspace",
Query: fmt.Sprintf(
@ -220,7 +226,9 @@ func (m *Meta) selectWorkspace(b backend.Backend) error {
return fmt.Errorf("Failed to select workspace: input not a valid number")
}
return m.SetWorkspace(workspaces[idx-1])
workspace = workspaces[idx-1]
log.Printf("[TRACE] Meta.selectWorkspace: setting the current workpace according to user selection (%s)", workspace)
return m.SetWorkspace(workspace)
}
// BackendForPlan is similar to Backend, but uses backend settings that were

View File

@ -789,6 +789,84 @@ func TestMetaBackend_reconfigureChange(t *testing.T) {
}
}
// Initializing a backend which supports workspaces and does *not* have
// the currently selected workspace should prompt the user with a list of
// workspaces to choose from to select a valid one, if more than one workspace
// is available.
func TestMetaBackend_initSelectedWorkspaceDoesNotExist(t *testing.T) {
// Create a temporary working directory that is empty
td := tempDir(t)
testCopyDir(t, testFixturePath("init-backend-selected-workspace-doesnt-exist-multi"), td)
defer os.RemoveAll(td)
defer testChdir(t, td)()
// Setup the meta
m := testMetaBackend(t, nil)
defer testInputMap(t, map[string]string{
"select-workspace": "2",
})()
// Get the backend
_, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
expected := "foo"
actual, err := m.Workspace()
if err != nil {
t.Fatal(err)
}
if actual != expected {
t.Fatalf("expected selected workspace to be %q, but was %q", expected, actual)
}
}
// Initializing a backend which supports workspaces and does *not* have the
// currently selected workspace - and which only has a single workspace - should
// automatically select that single workspace.
func TestMetaBackend_initSelectedWorkspaceDoesNotExistAutoSelect(t *testing.T) {
// Create a temporary working directory that is empty
td := tempDir(t)
testCopyDir(t, testFixturePath("init-backend-selected-workspace-doesnt-exist-single"), td)
defer os.RemoveAll(td)
defer testChdir(t, td)()
// Setup the meta
m := testMetaBackend(t, nil)
// this should not ask for input
m.input = false
// Assert test precondition: The current selected workspace is "bar"
previousName, err := m.Workspace()
if err != nil {
t.Fatal(err)
}
if previousName != "bar" {
t.Fatalf("expected test fixture to start with 'bar' as the current selected workspace")
}
// Get the backend
_, diags := m.Backend(&BackendOpts{Init: true})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
expected := "default"
actual, err := m.Workspace()
if err != nil {
t.Fatal(err)
}
if actual != expected {
t.Fatalf("expected selected workspace to be %q, but was %q", expected, actual)
}
}
// Changing a configured backend, copying state
func TestMetaBackend_configuredChangeCopy(t *testing.T) {
// Create a temporary working directory that is empty
@ -1267,7 +1345,6 @@ func TestMetaBackend_configuredChangeCopy_multiToNoDefaultWithoutDefault(t *test
// Ask input
defer testInputMap(t, map[string]string{
"backend-migrate-multistate-to-multistate": "yes",
"select-workspace": "1",
})()
// Setup the meta

View File

@ -0,0 +1,23 @@
{
"version": 3,
"serial": 2,
"lineage": "2f3864a6-1d3e-1999-0f84-36cdb61179d3",
"backend": {
"type": "local",
"config": {
"path": null,
"workspace_dir": null
},
"hash": 666019178
},
"modules": [
{
"path": [
"root"
],
"outputs": {},
"resources": {},
"depends_on": []
}
]
}

View File

@ -0,0 +1,7 @@
terraform {
backend "local" {}
}
output "foo" {
value = "bar"
}

View File

@ -0,0 +1,13 @@
{
"version": 4,
"terraform_version": "1.1.0",
"serial": 1,
"lineage": "cc4bb587-aa35-87ad-b3b7-7abdb574f2a1",
"outputs": {
"foo": {
"value": "bar",
"type": "string"
}
},
"resources": []
}

View File

@ -0,0 +1,13 @@
{
"version": 4,
"terraform_version": "1.1.0",
"serial": 1,
"lineage": "8ad3c77d-51aa-d90a-4f12-176f538b6e8b",
"outputs": {
"foo": {
"value": "bar",
"type": "string"
}
},
"resources": []
}

View File

@ -0,0 +1,23 @@
{
"version": 3,
"serial": 2,
"lineage": "2f3864a6-1d3e-1999-0f84-36cdb61179d3",
"backend": {
"type": "local",
"config": {
"path": null,
"workspace_dir": null
},
"hash": 666019178
},
"modules": [
{
"path": [
"root"
],
"outputs": {},
"resources": {},
"depends_on": []
}
]
}

View File

@ -0,0 +1,7 @@
terraform {
backend "local" {}
}
output "foo" {
value = "bar"
}