Skip to content

Comments

improve cd ls output and cd teardown error handling#1914

Merged
jordanstephens merged 11 commits intomainfrom
jordan/confusing-cd-teardown-error
Feb 10, 2026
Merged

improve cd ls output and cd teardown error handling#1914
jordanstephens merged 11 commits intomainfrom
jordan/confusing-cd-teardown-error

Conversation

@jordanstephens
Copy link
Member

@jordanstephens jordanstephens commented Feb 9, 2026

Description

  • only print suggestion to use --force if there are existing projects
  • separate hint from error
  • print commands which explain how to teardown existing projects

Previous cd ls output:

AWS_REGION=us-west-2 AWS_PROFILE=defang-lab defang cd ls -a -Paws
 * Using the "beta" stack on aws from DEFANG_PROVIDER
 - crewai/defanglab {jordanstephens} [us-west-2]
 - crewai/jordanaws {jordanstephens} [us-west-2]
 - html-css-js/beta {defang} (pending "*.html-css-js.defang.defang.appValidation" "*.html-css-js.defang.defang.appValidation" "*.html-css-js.defang.defang.appValidation") [us-west-2]
 - html-css-js/foobar {jordanstephens} [us-west-2]
 - html-css-js/jordanaws {jordanstephens} [us-west-2]
 - html-css-js/test2 {edwardrf} [us-west-2]

New cd ls output:

PROJECT      STACK      WORKSPACE       REGION
crewai       defanglab  jordanstephens  us-west-2  
crewai       jordanaws  jordanstephens  us-west-2  
html-css-js  beta       defang          us-west-2  
html-css-js  foobar     jordanstephens  us-west-2  
html-css-js  jordanaws  jordanstephens  us-west-2  
html-css-js  test2      edwardrf        us-west-2  

(--json mode is now supported as well):

AWS_REGION=us-west-2 AWS_PROFILE=defang-lab defang cd ls -a -Paws --json
 * Using the "beta" stack on aws from DEFANG_PROVIDER
[
	{
		"Project": "crewai",
		"Name": "defanglab",
		"Workspace": "jordanstephens",
		"Region": "us-west-2"
	},
	{
		"Project": "crewai",
		"Name": "jordanaws",
		"Workspace": "jordanstephens",
		"Region": "us-west-2"
	},
	{
		"Project": "html-css-js",
		"Name": "beta",
		"Workspace": "defang",
		"Region": "us-west-2"
	},
	{
		"Project": "html-css-js",
		"Name": "foobar",
		"Workspace": "jordanstephens",
		"Region": "us-west-2"
	},
	{
		"Project": "html-css-js",
		"Name": "jordanaws",
		"Workspace": "jordanstephens",
		"Region": "us-west-2"
	},
	{
		"Project": "html-css-js",
		"Name": "test2",
		"Workspace": "edwardrf",
		"Region": "us-west-2"
	}
]

Previous cd teardown error output:

AWS_REGION=us-west-2 AWS_PROFILE=defang-lab defang cd teardown -Paws
 * Using the "beta" stack on aws from DEFANG_PROVIDER
Error: there are still deployed services; use --force to tear down anyway

New cd teardown error output:

AWS_REGION=us-west-2 AWS_PROFILE=defang-lab defang cd teardown -Paws
 * Using the "beta" stack on aws from DEFANG_PROVIDER
 * Some stacks are currently deployed, to tear them down run the following commands:
 *   `defang down --workspace defang --project-name html-css-js --stack beta`
 *   `defang down --workspace edwardrf --project-name html-css-js --stack test2`
 *   `defang down --workspace jordanstephens --project-name crewai --stack defanglab`
 *   `defang down --workspace jordanstephens --project-name crewai --stack jordanaws`
 *   `defang down --workspace jordanstephens --project-name html-css-js --stack foobar`
 *   `defang down --workspace jordanstephens --project-name html-css-js --stack jordanaws`

Use `defang cd destroy --force` to force teardown the CD cluster, leaving existing projects orphaned

Error: there are still deployed stacks

Linked Issues

Checklist

  • I have performed a self-review of my code
  • I have added appropriate tests
  • I have updated the Defang CLI docs and/or README to reflect my changes, if necessary

Summary by CodeRabbit

  • Bug Fixes

    • Teardown now detects existing deployed stacks, shows a clear hint when stacks remain, and prevents proceeding unless forced.
  • Improvements

    • Stack listing consolidated into a single table with columns: Project, Stack, Workspace, Region.
    • Pre-teardown shows which stacks would be affected, provides per-stack tear-down commands, and logs a warning before destruction.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 9, 2026

📝 Walkthrough

Walkthrough

CD listing and teardown now propagate a structured state.Info type through provider CdList APIs, list and sort existing stacks before teardown, introduce cli.ErrExistingStacks when non-forced, and surface a user hint in the command handler when that error occurs.

Changes

Cohort / File(s) Summary
CLI command handler
src/cmd/cli/command/cd.go
Capture TearDownCD error locally, detect cli.ErrExistingStacks, call printDefangHint for that case, then return the error.
Teardown logic
src/pkg/cli/teardown_cd.go
Add exported ErrExistingStacks; always fetch list via provider.CdList(..., false), convert to []state.Info, sort by Workspace/Project/Stack, print stacks and per-stack commands, return ErrExistingStacks if stacks exist and not forced, otherwise call provider.TearDownCD.
CLI listing & output
src/pkg/cli/cd.go
Consume iter.Seq[state.Info], collect into a slice, and render a single terminal table (Project, Stack, Workspace, Region) instead of printing per-item.
Provider interfaces & base client
src/pkg/cli/client/provider.go, src/pkg/cli/client/byoc/baseclient.go
Change CdList signature from iter.Seq[string]iter.Seq[state.Info]; update callers to use structured fields (e.g., stack.Project).
BYOC state types & parsing
src/pkg/cli/client/byoc/state/state.go, .../state/parse.go, .../state/parse_test.go
Rename parsing package to state, add public state.Info type (Project, Stack, Workspace, Region), rename PulumiState.DefangOrg→Workspace and update parsing/strings.
BYOC providers (AWS / DO / GCP / Playground)
src/pkg/cli/client/byoc/aws/byoc.go, .../aws/list.go, src/pkg/cli/client/byoc/do/byoc.go, src/pkg/cli/client/byoc/gcp/byoc.go, src/pkg/cli/client/playground.go
Change CdList implementations to yield state.Info/state.PulumiState values instead of strings; use state.ParsePulumiStateFile; add warning logs in some TearDownCD paths.
AWS list & tests / module
src/pkg/cli/client/byoc/aws/list_test.go, go.mod
Adjust tests to account for new types/representations and update module imports as needed.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as CLI (cd teardown)
  participant Provider as Provider (CdList / TearDownCD)
  participant Storage as Remote storage (S3 / buckets)
  participant Printer as Terminal / Table renderer

  CLI->>Provider: CdList(ctx, allRegions=false)
  Provider->>Storage: list Pulumi state files (multi-region)
  Storage-->>Provider: stream state.PulumiState / state.Info
  Provider-->>CLI: iter.Seq[state.Info]
  CLI->>Printer: collect, sort, render table of stacks
  alt stacks exist && not --force
    CLI->>Printer: print per-stack commands + header
    CLI->>CLI: return ErrExistingStacks
    CLI->>Printer: print defang hint
  else
    CLI->>Provider: TearDownCD(ctx)
    Provider->>Storage: perform teardown operations
    Provider-->>CLI: teardown result
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • lionello
  • raphaeltm

Poem

🐇 I hopped through buckets, parsed each stack and place,
I sorted workspaces, projects — neat in every case.
I whispered, "There are stacks," then left a helpful hint,
So teardowns stay tidy and no one needs to squint.
🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: improving cd ls output formatting and enhancing cd teardown error handling with better hints.
Linked Issues check ✅ Passed Code changes address all linked issue requirements: #1842 lists services during teardown, #1843 removes --force suggestion for playground, #1906 handles StackNotFoundException properly.
Out of Scope Changes check ✅ Passed All changes are within scope: refactoring related to state info representation, output formatting, and error handling. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jordan/confusing-cd-teardown-error

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.5.0)

level=warning msg="[linters_context] running gomodguard failed: unable to read module file go.mod: current working directory must have a go.mod file: if you are not using go modules it is suggested to disable this linter"
level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Comment @coderabbitai help to get the list of available commands and usage tips.

* only print suggestion to use `--force` if there are existing projects
* separate hint from error
* print the names of the existing projects and stacks
@jordanstephens jordanstephens force-pushed the jordan/confusing-cd-teardown-error branch from ffd5fc1 to 5331e5d Compare February 9, 2026 21:23
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/cmd/cli/command/cd.go`:
- Around line 111-115: The hint text used when TearDownCD returns
cli.ErrExistingProjects is incorrect—update the message passed to
printDefangHint in the cdTearDownCmd teardown flow so it references the teardown
command and flag correctly ("defang cd teardown --force") instead of "defang cd
destroy --force"; locate the error-handling block where cli.TearDownCD(...) is
called and replace the hint string passed to printDefangHint accordingly.

@jordanstephens jordanstephens changed the title improve cd teardown error handling improve cd ls output and cd teardown error handling Feb 9, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pkg/cli/client/byoc/baseclient.go (1)

98-115: ⚠️ Potential issue | 🟠 Major

Deduplicate project names before deciding “multiple projects.”

CdList can yield multiple stacks for the same project (different stacks/regions). The current logic counts duplicates and can incorrectly raise ErrMultipleProjects even when only one unique project exists.

🔧 Proposed fix
-	var projectNames []string
-	for stack := range stacks {
-		projectNames = append(projectNames, stack.Project)
-	}
+	projectNameSet := make(map[string]struct{})
+	var projectNames []string
+	for stack := range stacks {
+		if _, seen := projectNameSet[stack.Project]; seen {
+			continue
+		}
+		projectNameSet[stack.Project] = struct{}{}
+		projectNames = append(projectNames, stack.Project)
+	}
🤖 Fix all issues with AI agents
In `@src/pkg/cli/teardown_cd.go`:
- Around line 22-30: TearDownCD currently aborts on any provider.CdList error;
update the logic so "CD already deleted" / "no bucket found" cases are treated
as non-fatal by either normalizing provider.CdList to return an empty channel
and nil error for not-found, or by detecting provider-specific not-found errors
in TearDownCD and treating them as a no-op (i.e., proceed with an empty list).
Ensure the iteration in TearDownCD that does "for stackInfo := range list" is
safe by guaranteeing list is non-nil (create an empty channel or slice) or by
guarding the loop when list is nil; update provider implementations (CdList) or
add an error normalization helper used by TearDownCD to identify
annotated/not-found errors (e.g., AWS AnnotateAwsError variants, GCP "no defang
cd bucket found") and convert them into an empty result rather than returning an
error.
🧹 Nitpick comments (3)
src/pkg/cli/cd.go (1)

147-163: Consider skipping the table when no stacks exist.

When len(stacks) == 0, the code prints "No projects found in ..." and then still renders an empty table. This might produce confusing output with table headers but no data rows.

♻️ Optional: Skip table when empty
 	if len(stacks) == 0 {
 		accountInfo, err := provider.AccountInfo(ctx)
 		if err != nil {
 			return err
 		}
 		if allRegions {
 			accountInfo.Region = ""
 		}
 		term.Printf("No projects found in %v\n", accountInfo)
+		return nil
 	}
 
 	return term.Table(stacks, "Project", "Stack", "Workspace", "Region")
src/pkg/cli/client/byoc/aws/list_test.go (1)

70-72: Error message format inconsistency.

Line 70 uses stack.String() for comparison, but line 71 prints stack directly in the error message. For consistency, consider using stack.String() in both places.

Note: This also depends on StackInfo having a String() method, which appears to be missing (see review comment on state.go).

♻️ Proposed fix for consistency
 	for stack := range stacks {
 		if stack.String() != expectedStacks[count] {
-			t.Errorf("expected stack %q, got %q", expectedStacks[count], stack)
+			t.Errorf("expected stack %q, got %q", expectedStacks[count], stack.String())
 		}
 		count++
 	}
src/pkg/cli/client/byoc/gcp/byoc.go (1)

797-799: Consider removing the commented-out warn line.

Now that teardown messaging is handled at the CLI layer, keeping commented code here adds noise; either delete it or re-enable with a clear rationale when TearDownCD is implemented.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/cmd/cli/command/cd.go`:
- Around line 111-115: The code checks for a non-existent error constant
cli.ErrExistingProjects causing build failure; update the error comparison in
the cd teardown path to use the correct constant cli.ErrExistingStacks (used by
TearDownCD in teardown_cd.go) so the condition compiles and the defang hint
still triggers for existing stacks; locate the check in the cd command (the
TearDownCD error handling block) and replace cli.ErrExistingProjects with
cli.ErrExistingStacks.

@jordanstephens jordanstephens force-pushed the jordan/confusing-cd-teardown-error branch from de526cd to bbc4569 Compare February 9, 2026 23:03
Copy link
Member

@lionello lionello left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can clean up some superfluous types.


func (b *ByocGcp) TearDownCD(ctx context.Context) error {
// term.Warn("Deleting Defang CD; currently existing stacks or configs will not be deleted, but they will be orphaned and they will need to be cleaned up manually")
// FIXME: implement
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible there's really nothing to do here. @edwardrf thoughts?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/pkg/cli/client/byoc/baseclient.go`:
- Around line 105-107: The loop building projectNames from stacks currently
appends duplicates (for stack.Project) which can cause a false
ErrMultipleProjects; replace the naive append with logic that deduplicates
(e.g., use a map[string]struct{} to collect unique stack.Project values), then
convert the keys to a slice, sort it, and run the existing length checks that
raise ErrMultipleProjects/ErrNoProject against this deduped, sorted slice (refer
to the stacks range loop and projectNames variable and the ErrMultipleProjects
checks to locate where to change).
🧹 Nitpick comments (1)
src/pkg/cli/client/byoc/aws/list.go (1)

86-86: Variable shadows imported package name.

The variable state shadows the imported state package. While this works due to Go's evaluation order (RHS before LHS declaration), it's confusing and error-prone for future maintenance.

♻️ Suggested refactor to avoid shadowing
-			state, err := state.ParsePulumiStateFile(ctx, s3Obj{obj}, bucketName, func(ctx context.Context, bucket, path string) ([]byte, error) {
+			st, err := state.ParsePulumiStateFile(ctx, s3Obj{obj}, bucketName, func(ctx context.Context, bucket, path string) ([]byte, error) {
 				getObjectOutput, err := s3client.GetObject(ctx, &s3.GetObjectInput{
 					Bucket: &bucket,
 					Key:    &path,
 				})
 				if err != nil {
 					return nil, err
 				}
 				return io.ReadAll(getObjectOutput.Body)
 			})
 			if err != nil {
 				term.Debugf("Skipping %q in bucket %s: %v", *obj.Key, bucketName, AnnotateAwsError(err))
 				continue
 			}
-			if state != nil {
-				if !yield(*state) {
+			if st != nil {
+				if !yield(*st) {
 					break
 				}
 			}

Comment on lines +105 to 107
for stack := range stacks {
projectNames = append(projectNames, stack.Project)
}
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Deduplicate project names to avoid false “multiple projects” errors.

If multiple stacks exist for the same project, the current logic will append duplicates and trigger ErrMultipleProjects incorrectly. Consider de-duping (and sorting) before the length checks.

🔧 Suggested fix
 import (
 	"context"
 	"errors"
 	"fmt"
 	"iter"
+	"sort"
 	"strings"
@@
 	var projectNames []string
-	for stack := range stacks {
-		projectNames = append(projectNames, stack.Project)
-	}
+	projectSet := map[string]struct{}{}
+	for stack := range stacks {
+		projectSet[stack.Project] = struct{}{}
+	}
+	projectNames = make([]string, 0, len(projectSet))
+	for name := range projectSet {
+		projectNames = append(projectNames, name)
+	}
+	sort.Strings(projectNames)
🤖 Prompt for AI Agents
In `@src/pkg/cli/client/byoc/baseclient.go` around lines 105 - 107, The loop
building projectNames from stacks currently appends duplicates (for
stack.Project) which can cause a false ErrMultipleProjects; replace the naive
append with logic that deduplicates (e.g., use a map[string]struct{} to collect
unique stack.Project values), then convert the keys to a slice, sort it, and run
the existing length checks that raise ErrMultipleProjects/ErrNoProject against
this deduped, sorted slice (refer to the stacks range loop and projectNames
variable and the ErrMultipleProjects checks to locate where to change).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is true, but unrelated to our changes here. I'll handle separately.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jordanstephens Understood! Since you'll be handling this separately, would you like me to create a GitHub issue to track the deduplication fix?


🧠 Learnings used
Learnt from: lionello
Repo: DefangLabs/defang PR: 1734
File: src/cmd/cli/command/commands.go:1220-1226
Timestamp: 2025-12-25T04:38:40.356Z
Learning: In the Defang CLI codebase (src/cmd/cli/command/commands.go), empty project names (from inputs like "/stack" or when --project-name is omitted) are acceptable and intentional behavior, as they work similarly to not providing the --project-name flag at all.

Learnt from: edwardrf
Repo: DefangLabs/defang PR: 1747
File: src/pkg/cli/client/byoc/gcp/stream.go:497-512
Timestamp: 2026-01-09T20:31:23.614Z
Learning: In src/pkg/cli/client/byoc/gcp/stream.go, the getReadyServicesCompletedResps helper function intentionally uses variable shadowing. The loop variable `status` from `readyServices` map represents individual service statuses, while the function parameter (to be renamed `cdStatus`) represents only the CD service (defangCD) completion status. Each ready service should retain its original status from the map.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done: #1918

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jordanstephens Thank you for creating issue #1918! I appreciate you tracking this separately.


🧠 Learnings used
Learnt from: lionello
Repo: DefangLabs/defang PR: 1734
File: src/cmd/cli/command/commands.go:1220-1226
Timestamp: 2025-12-25T04:38:40.356Z
Learning: In the Defang CLI codebase (src/cmd/cli/command/commands.go), empty project names (from inputs like "/stack" or when --project-name is omitted) are acceptable and intentional behavior, as they work similarly to not providing the --project-name flag at all.

Learnt from: edwardrf
Repo: DefangLabs/defang PR: 1747
File: src/pkg/cli/client/byoc/gcp/stream.go:497-512
Timestamp: 2026-01-09T20:31:23.614Z
Learning: In src/pkg/cli/client/byoc/gcp/stream.go, the getReadyServicesCompletedResps helper function intentionally uses variable shadowing. The loop variable `status` from `readyServices` map represents individual service statuses, while the function parameter (to be renamed `cdStatus`) represents only the CD service (defangCD) completion status. Each ready service should retain its original status from the map.

@jordanstephens jordanstephens merged commit a38e89d into main Feb 10, 2026
14 checks passed
@jordanstephens jordanstephens deleted the jordan/confusing-cd-teardown-error branch February 10, 2026 21:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Teardown fails if CD already torn down confusing defang cd teardown error on playground defang cd teardown should list existing services

2 participants