Prevent creating stack with name of existing stack#1969
Prevent creating stack with name of existing stack#1969jordanstephens wants to merge 9 commits intomainfrom
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds centralized stack-name validation and backend existence checks to stack creation: exports Changes
Sequence DiagramsequenceDiagram
participant User
participant CLI as "CLI Command"
participant Validator as "ValidateStackName"
participant Fabric as "Fabric Client (GetStack)"
participant Backend as "Backend / Project Store"
User->>CLI: invoke "stack new" (with name or prompt)
CLI->>Validator: ValidateStackName(name)
Validator-->>CLI: valid / invalid
alt invalid
CLI-->>User: error "invalid stack name"
else valid
CLI->>Fabric: GetStack(project, name)
Fabric->>Backend: RPC GetStack query
Backend-->>Fabric: stack found / not found / error
Fabric-->>CLI: GetStackResponse / error
alt stack found
CLI-->>User: error "stack already exists"
else not found
CLI->>Backend: create stack
Backend-->>CLI: created
CLI-->>User: success
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Comment Tip Migrating from UI to YAML configuration.Use the |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/cmd/cli/command/stack_test.go (1)
165-184:⚠️ Potential issue | 🟡 MinorTwo table cases aren’t exercising the paths they’re named after.
"missing stack name"callsRunEwith one empty arg, which the CLI can’t actually produce, and"stack already exists"usesexisting-stack, which fails name validation beforestackExists()runs. That leaves both the zero-arg path and the duplicate-name path untested.💡 Suggested change
{ name: "missing stack name", parameters: stacks.Parameters{ Name: "", @@ { name: "stack already exists", parameters: stacks.Parameters{ - Name: "existing-stack", + Name: "existingstack", Provider: client.ProviderAWS, Region: "us-test-2", Mode: modes.ModeAffordable, }, - existingStacks: []*defangv1.Stack{{Name: "existing-stack", Project: ""}}, + existingStacks: []*defangv1.Stack{{Name: "existingstack", Project: ""}}, expectErr: true, }, @@ - err := stackCreateCmd.RunE(stackCreateCmd, []string{tt.parameters.Name}) + var args []string + if tt.parameters.Name != "" { + args = []string{tt.parameters.Name} + } + err := stackCreateCmd.RunE(stackCreateCmd, args)Also applies to: 191-200
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/cmd/cli/command/stack_test.go` around lines 165 - 184, The two test cases in stack_test.go are not exercising the intended paths: change the "missing stack name" case to call the command's RunE with zero args (simulate CLI invocation with no arguments) so the name-validation path for empty input is hit, and change the "stack already exists" case to use a valid, non-empty stack name that passes name validation (e.g., "existing-stack" kept but ensure the test supplies it as a proper argument) so that stackExists() is reached and triggers the duplicate-name error; update the test invocations around RunE/Execute/command creation in the same file to reflect zero-arg invocation for the first case and valid-arg invocation for the second so the code paths for name missing and duplicate name are both covered.
🧹 Nitpick comments (1)
src/pkg/stacks/stacks_test.go (1)
30-31: UseValidateStackName()here instead of the raw regexp.This test is checking whether
MakeDefaultName()produces a name the package accepts. MatchingStackNamePatterndirectly couples the assertion to the current implementation and will miss any extra rules added inValidateStackName().💡 Suggested change
- if !StackNamePattern.MatchString(result) { - t.Errorf("MakeDefaultName() produced invalid stack name: %q", result) - } + if err := ValidateStackName(result); err != nil { + t.Errorf("MakeDefaultName() produced invalid stack name %q: %v", result, err) + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pkg/stacks/stacks_test.go` around lines 30 - 31, The test currently asserts MakeDefaultName() by matching against StackNamePattern directly; change it to call ValidateStackName(result) and assert that it returns nil (or no error) so the test verifies the full validation logic. Replace the direct StackNamePattern.MatchString(result) check with a call to ValidateStackName(result) and update the failure message to include the returned error (e.g., "MakeDefaultName() produced invalid stack name: %q: %v") so the test fails with the validator's reason.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/cmd/cli/command/stack_test.go`:
- Around line 25-29: The mock ListStacks implementation
(mockFabricClientWithStacks.ListStacks) ignores ListStacksRequest.Project, so
tests like TestStackExists won't catch code that fails to scope by project;
modify ListStacks to check the incoming request.Project against an expected
project value stored on the mock (e.g., add a field like expectedProject or use
m.project) and return an error (or empty result) when they differ, while still
returning m.listStacksErr when set and returning the stacked response when the
project matches.
In `@src/cmd/cli/command/stack.go`:
- Around line 47-49: The validation call to stacks.ValidateStackName should only
run when the user actually supplied the positional STACK_NAME; currently it runs
unconditionally and rejects the zero-arg interactive flow when stackName == "".
Update the branch around ValidateStackName (the code referencing stackName and
stacks.ValidateStackName) to first check that stackName is non-empty (or that
the positional arg count > 0) and only then call stacks.ValidateStackName,
leaving the empty stackName path to proceed to the interactive survey.
In `@src/pkg/stacks/stacks_test.go`:
- Around line 202-205: The test asserts a platform-specific error string after
calling RemoveInDirectory(".", "nonexistingstack"); instead, check for the
standard not-found sentinel: replace assert.ErrorContains(t, err, "...no such
file...") with a portability-safe assertion like assert.ErrorIs(t, err,
os.ErrNotExist) (or assert.True(t, errors.Is(err, os.ErrNotExist)) if ErrorIs
isn't available) and add the necessary import for os (and errors if used); keep
the existing assert.Error(t, err) or remove it since ErrorIs covers presence.
---
Outside diff comments:
In `@src/cmd/cli/command/stack_test.go`:
- Around line 165-184: The two test cases in stack_test.go are not exercising
the intended paths: change the "missing stack name" case to call the command's
RunE with zero args (simulate CLI invocation with no arguments) so the
name-validation path for empty input is hit, and change the "stack already
exists" case to use a valid, non-empty stack name that passes name validation
(e.g., "existing-stack" kept but ensure the test supplies it as a proper
argument) so that stackExists() is reached and triggers the duplicate-name
error; update the test invocations around RunE/Execute/command creation in the
same file to reflect zero-arg invocation for the first case and valid-arg
invocation for the second so the code paths for name missing and duplicate name
are both covered.
---
Nitpick comments:
In `@src/pkg/stacks/stacks_test.go`:
- Around line 30-31: The test currently asserts MakeDefaultName() by matching
against StackNamePattern directly; change it to call ValidateStackName(result)
and assert that it returns nil (or no error) so the test verifies the full
validation logic. Replace the direct StackNamePattern.MatchString(result) check
with a call to ValidateStackName(result) and update the failure message to
include the returned error (e.g., "MakeDefaultName() produced invalid stack
name: %q: %v") so the test fails with the validator's reason.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 61061d17-cd8e-4771-b5cd-0416519ff04b
📒 Files selected for processing (5)
src/cmd/cli/command/stack.gosrc/cmd/cli/command/stack_test.gosrc/pkg/stacks/stacks.gosrc/pkg/stacks/stacks_test.gosrc/pkg/stacks/wizard.go
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/cmd/cli/command/stack_test.go`:
- Around line 184-194: The test case "stack already exists" uses an invalid Name
"existing-stack" (hyphen) which fails ValidateStackName; change the
Parameters.Name and the existingStacks[*].Name to a valid name that matches
^[a-z][a-z0-9]*$ (e.g., "existingstack" or "existingstack1") so validation
passes and the test exercise the existence-check path in the code that handles
stacks.Parameters and the existingStacks slice.
In `@src/cmd/cli/command/stack.go`:
- Around line 220-232: The stackExists helper should treat a "not found"
response from GetStack as a non-error: modify stackExists (the function calling
global.Client.GetStack) to detect a CodeNotFound/NotFound error (use grpc/status
to get the error code or check the server's CodeNotFound sentinel) and return
(false, nil) when that occurs; for any other error, return (false, err), and
otherwise keep the existing logic that returns resp.GetStack() != nil.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 10df2ee6-1a67-43aa-92a4-44bb4e55e376
📒 Files selected for processing (5)
src/cmd/cli/command/stack.gosrc/cmd/cli/command/stack_test.gosrc/pkg/cli/client/client.gosrc/pkg/cli/client/grpc.gosrc/pkg/stacks/stacks_test.go
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/cmd/cli/command/stack_test.go (2)
26-39: Mock doesn't simulateCodeNotFounderror for non-existent stacks.The real
GetStackserver returns aCodeNotFounderror when a stack doesn't exist, but this mock returns an empty response. This means tests exercise theresp.GetStack() != nilpath but don't verify theCodeNotFoundhandling instackExists.Consider returning a connect error to match server behavior:
♻️ Suggested improvement
+import "connectrpc.com/connect" func (m mockFabricClientWithStacks) GetStack(_ context.Context, req *defangv1.GetStackRequest) (*defangv1.GetStackResponse, error) { if m.listStacksErr != nil { return nil, m.listStacksErr } if m.expectedProject != "" && req.Project != m.expectedProject { - return &defangv1.GetStackResponse{}, nil + return nil, connect.NewError(connect.CodeNotFound, nil) } for _, s := range m.existingStacks { if s.Name == req.Stack { return &defangv1.GetStackResponse{Stack: s}, nil } } - return &defangv1.GetStackResponse{}, nil + return nil, connect.NewError(connect.CodeNotFound, nil) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/cmd/cli/command/stack_test.go` around lines 26 - 39, The mock GetStack implementation in mockFabricClientWithStacks should simulate the server's NotFound behavior instead of returning an empty response; update mockFabricClientWithStacks.GetStack to return a connect error with CodeNotFound (matching the connect/grpc error used by the real server) when the requested stack is not present or project mismatch so tests exercise the stackExists error path and the code that handles the not-found error.
173-183: Clarify test intent: empty name bypasses validation, error comes later.When
Nameis""and passed asargs[0], the validation at lines 45-47 instack.gois skipped (guarded bystackName != ""). The error occurs downstream inCreateInDirectory. Consider adding a comment or asserting the specific error message to clarify what's being tested.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/cmd/cli/command/stack_test.go` around lines 173 - 183, The test case "missing stack name" is ambiguous because an empty Parameters.Name bypasses the validation guarded by stackName != "" in stack.go and the actual error originates from CreateInDirectory; update the test by either adding a clarifying comment explaining that an empty name skips the early validation and triggers an error later in CreateInDirectory, or change the assertion to check for the specific error message returned by CreateInDirectory (referencing CreateInDirectory and the test case name "missing stack name") so the intent and failure source are explicit.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/cmd/cli/command/stack.go`:
- Around line 228-232: The build fails because the connect package is referenced
(connect.CodeOf and connect.CodeNotFound) but not imported; open the imports
block in the file containing stack.go and add the appropriate connect package
import (the package that defines CodeOf/CodeNotFound) so the references to
connect.CodeOf and connect.CodeNotFound resolve and the file compiles.
---
Nitpick comments:
In `@src/cmd/cli/command/stack_test.go`:
- Around line 26-39: The mock GetStack implementation in
mockFabricClientWithStacks should simulate the server's NotFound behavior
instead of returning an empty response; update
mockFabricClientWithStacks.GetStack to return a connect error with CodeNotFound
(matching the connect/grpc error used by the real server) when the requested
stack is not present or project mismatch so tests exercise the stackExists error
path and the code that handles the not-found error.
- Around line 173-183: The test case "missing stack name" is ambiguous because
an empty Parameters.Name bypasses the validation guarded by stackName != "" in
stack.go and the actual error originates from CreateInDirectory; update the test
by either adding a clarifying comment explaining that an empty name skips the
early validation and triggers an error later in CreateInDirectory, or change the
assertion to check for the specific error message returned by CreateInDirectory
(referencing CreateInDirectory and the test case name "missing stack name") so
the intent and failure source are explicit.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ec698264-9515-4472-b4be-39260561ac18
📒 Files selected for processing (2)
src/cmd/cli/command/stack.gosrc/cmd/cli/command/stack_test.go
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/cmd/cli/command/stack.go`:
- Around line 220-243: The function stackExists currently contains a duplicated
import block inside its body and lacks proper handling for not-found errors;
remove the duplicated import block from inside stackExists, add the missing
import "connectrpc.com/connect" to the file's top-level imports, and update the
error handling after calling global.Client.GetStack (in stackExists with
defangv1.GetStackRequest) to detect and treat a CodeNotFound response as "stack
does not exist" (e.g. check connect.CodeOf(err) == connect.CodeNotFound and
return (false, nil) in that case), otherwise return the error.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2e737c72-2bc2-42f6-beb2-60a8b6536392
📒 Files selected for processing (1)
src/cmd/cli/command/stack.go
87c6c66 to
4e0dcfb
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/cmd/cli/command/stack.go (1)
3-14:⚠️ Potential issue | 🔴 CriticalMissing import for
connectpackage causes build failure.The
connectpackage is referenced at line 229 but not imported. Add the missing import to fix the pipeline failure.🐛 Proposed fix
import ( "context" "encoding/json" "fmt" + "connectrpc.com/connect" "github.com/DefangLabs/defang/src/pkg/cli" "github.com/DefangLabs/defang/src/pkg/modes" "github.com/DefangLabs/defang/src/pkg/stacks" "github.com/DefangLabs/defang/src/pkg/term" defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" "github.com/spf13/cobra" )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/cmd/cli/command/stack.go` around lines 3 - 14, The build fails because the connect package (used as connect.NewRequest in this file) is not imported; add the missing import for the connect package (e.g., "github.com/bufbuild/connect-go") to the import block in stack.go so references like connect.NewRequest (around the stack command handler) resolve and the file compiles.
🧹 Nitpick comments (1)
src/cmd/cli/command/stack_test.go (1)
26-39: Mock may not accurately simulate realGetStackbehavior for non-existent stacks.The mock returns an empty
GetStackResponse{}when a stack isn't found, but the real gRPC implementation likely returns aconnect.CodeNotFounderror. SincestackExists(in stack.go) handlesCodeNotFoundas the "not found" signal, these tests may pass while masking issues with the actual error handling path.Consider updating the mock to return
connect.NewError(connect.CodeNotFound, ...)when the stack isn't found to better simulate production behavior.♻️ Suggested improvement
+import "connectrpc.com/connect" + func (m mockFabricClientWithStacks) GetStack(_ context.Context, req *defangv1.GetStackRequest) (*defangv1.GetStackResponse, error) { if m.listStacksErr != nil { return nil, m.listStacksErr } if m.expectedProject != "" && req.Project != m.expectedProject { - return &defangv1.GetStackResponse{}, nil + return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("stack not found")) } for _, s := range m.existingStacks { if s.Name == req.Stack { return &defangv1.GetStackResponse{Stack: s}, nil } } - return &defangv1.GetStackResponse{}, nil + return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("stack %q not found", req.Stack)) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/cmd/cli/command/stack_test.go` around lines 26 - 39, The mock GetStack implementation in mockFabricClientWithStacks should simulate the real gRPC behavior for missing stacks by returning a connect.CodeNotFound error instead of an empty GetStackResponse; update the GetStack method (mockFabricClientWithStacks.GetStack) so that when no matching stack is found it returns connect.NewError(connect.CodeNotFound, fmt.Errorf("stack %s not found", req.Stack)) (and add the necessary connect import) so tests exercising stackExists will follow the same error path as production.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/cmd/cli/command/stack.go`:
- Around line 3-14: The build fails because the connect package (used as
connect.NewRequest in this file) is not imported; add the missing import for the
connect package (e.g., "github.com/bufbuild/connect-go") to the import block in
stack.go so references like connect.NewRequest (around the stack command
handler) resolve and the file compiles.
---
Nitpick comments:
In `@src/cmd/cli/command/stack_test.go`:
- Around line 26-39: The mock GetStack implementation in
mockFabricClientWithStacks should simulate the real gRPC behavior for missing
stacks by returning a connect.CodeNotFound error instead of an empty
GetStackResponse; update the GetStack method
(mockFabricClientWithStacks.GetStack) so that when no matching stack is found it
returns connect.NewError(connect.CodeNotFound, fmt.Errorf("stack %s not found",
req.Stack)) (and add the necessary connect import) so tests exercising
stackExists will follow the same error path as production.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a8996a33-4fbd-4e78-9dae-e3a413e8723a
📒 Files selected for processing (7)
src/cmd/cli/command/stack.gosrc/cmd/cli/command/stack_test.gosrc/pkg/cli/client/client.gosrc/pkg/cli/client/grpc.gosrc/pkg/stacks/stacks.gosrc/pkg/stacks/stacks_test.gosrc/pkg/stacks/wizard.go
🚧 Files skipped from review as they are similar to previous changes (2)
- src/pkg/stacks/stacks_test.go
- src/pkg/cli/client/grpc.go
4e0dcfb to
a7e7e07
Compare
Replace platform-specific error string check with a portable sentinel check so the test passes on all operating systems (e.g. Windows). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tests stackExists switched from ListStacks to GetStack but lost the empty-stack early-return guard; restore it. Replace the unused ListStacks mock with a GetStack implementation that validates project scope via expectedProject, and add a test case covering wrong-project returns false. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
8b6356c to
73cfbe1
Compare
Description
Fail to create a stack if the name is invalid. Check if passed by arg or by interactive survey
Linked Issues
Checklist
Summary by CodeRabbit
New Features
Bug Fixes
Tests