From 9212a2efddbb04069009d5e03eefa366944f013f Mon Sep 17 00:00:00 2001 From: Sarah French <15078782+SarahFrench@users.noreply.github.com> Date: Thu, 26 Jun 2025 12:26:58 +0100 Subject: [PATCH] Make Terraform reject empty strings as invalid workspace names (#37267) * Add tests defining what are valid workspace names * Update workspace name validation to mark an empty string as not valid * Add change file * Add command-level test showing the "" workspace cannot be created or selected * Update invalid name error text to include empty string as invalid. --- .changes/v1.13/BUG FIXES-20250623-125514.yaml | 5 ++ internal/command/workspace_command.go | 5 +- internal/command/workspace_command_test.go | 76 +++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 .changes/v1.13/BUG FIXES-20250623-125514.yaml diff --git a/.changes/v1.13/BUG FIXES-20250623-125514.yaml b/.changes/v1.13/BUG FIXES-20250623-125514.yaml new file mode 100644 index 0000000000..22ee097380 --- /dev/null +++ b/.changes/v1.13/BUG FIXES-20250623-125514.yaml @@ -0,0 +1,5 @@ +kind: BUG FIXES +body: 'workspace: Updated validation to reject workspaces named ""' +time: 2025-06-23T12:55:14.226554+01:00 +custom: + Issue: "37267" diff --git a/internal/command/workspace_command.go b/internal/command/workspace_command.go index a895af53d9..1d79698346 100644 --- a/internal/command/workspace_command.go +++ b/internal/command/workspace_command.go @@ -45,6 +45,9 @@ func (c *WorkspaceCommand) Synopsis() string { // Since most named states are accessed via a filesystem path or URL, check if // escaping the name would be required. func validWorkspaceName(name string) bool { + if name == "" { + return false + } return name == url.PathEscape(name) } @@ -100,7 +103,7 @@ to another workspace and try again. envInvalidName = ` The workspace name %q is not allowed. The name must contain only URL safe -characters, and no path separators. +characters, contain no path separators, and not be an empty string. ` envIsOverriddenNote = ` diff --git a/internal/command/workspace_command_test.go b/internal/command/workspace_command_test.go index ede8bbff8d..c1bc08807b 100644 --- a/internal/command/workspace_command_test.go +++ b/internal/command/workspace_command_test.go @@ -62,6 +62,49 @@ func TestWorkspace_createAndChange(t *testing.T) { } +func TestWorkspace_cannotCreateOrSelectEmptyStringWorkspace(t *testing.T) { + // Create a temporary working directory that is empty + td := t.TempDir() + os.MkdirAll(td, 0755) + defer testChdir(t, td)() + + newCmd := &WorkspaceNewCommand{} + + current, _ := newCmd.Workspace() + if current != backend.DefaultStateName { + t.Fatal("current workspace should be 'default'") + } + + args := []string{""} + ui := cli.NewMockUi() + view, _ := testView(t) + newCmd.Meta = Meta{Ui: ui, View: view} + if code := newCmd.Run(args); code != 1 { + t.Fatalf("expected failure when trying to create the \"\" workspace.\noutput: %s", ui.OutputWriter) + } + + gotStderr := ui.ErrorWriter.String() + if want, got := `The workspace name "" is not allowed`, gotStderr; !strings.Contains(got, want) { + t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, got) + } + + ui = cli.NewMockUi() + selectCmd := &WorkspaceSelectCommand{ + Meta: Meta{ + Ui: ui, + View: view, + }, + } + if code := selectCmd.Run(args); code != 1 { + t.Fatalf("expected failure when trying to select the the \"\" workspace.\noutput: %s", ui.OutputWriter) + } + + gotStderr = ui.ErrorWriter.String() + if want, got := `The workspace name "" is not allowed`, gotStderr; !strings.Contains(got, want) { + t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, got) + } +} + // Create some workspaces and test the list output. // This also ensures we switch to the correct env after each call func TestWorkspace_createAndList(t *testing.T) { @@ -476,3 +519,36 @@ func TestWorkspace_selectWithOrCreate(t *testing.T) { } } + +func TestValidWorkspaceName(t *testing.T) { + cases := map[string]struct { + input string + valid bool + }{ + "foobar": { + input: "foobar", + valid: true, + }, + "valid symbols": { + input: "-._~@:", + valid: true, + }, + "includes space": { + input: "two words", + valid: false, + }, + "empty string": { + input: "", + valid: false, + }, + } + + for tn, tc := range cases { + t.Run(tn, func(t *testing.T) { + valid := validWorkspaceName(tc.input) + if valid != tc.valid { + t.Fatalf("unexpected output when processing input %q. Wanted %v got %v", tc.input, tc.valid, valid) + } + }) + } +}