Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkgs/defang/cli.nix
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ buildGo125Module {
pname = "defang-cli";
version = "git";
src = lib.cleanSource ../../src;
vendorHash = "sha256-qNsk3rEco0mzBIPodsp3GZpzgaIzthAPSIMmVCja50I=";
vendorHash = "sha256-eNm2ChvQ10xAYPJ6jN+Cm4CCnZfqk2e2MFGQ1Uj6T3w=";

subPackages = [ "cmd/cli" ];

Expand Down
20 changes: 18 additions & 2 deletions src/cmd/cli/command/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ func promptToCreateStack(ctx context.Context, targetDirectory string, params sta

func handleComposeUpErr(ctx context.Context, debugger *debug.Debugger, project *compose.Project, provider client.Provider, originalErr error) error {
if errors.Is(originalErr, types.ErrComposeFileNotFound) {
// TODO: generate a compose file based on the current project
// TODO: suggest to generate a compose file based on the current project
printDefangHint("To start a new project, do:", "new")
}

Expand Down Expand Up @@ -482,7 +482,23 @@ func makeComposeDownCmd() *cobra.Command {
term.Warn("Unable to tail logs. Detaching.")
return nil
}
return err
// A failed destroy (e.g. CodeBuild exit status) is when resources get orphaned, so prompt
// the AI debugger just like `up` does; it can guide the user through cleanup.
// handleTailAndMonitorErr skips the prompt in non-interactive mode.
deploymentErr := err
debugger, dbgErr := debug.NewDebugger(cmd.Context(), global.FabricAddr, session.Stack)
if dbgErr != nil {
term.Warn("Failed to initialize debugger:", dbgErr)
return deploymentErr
}
handleTailAndMonitorErr(cmd.Context(), deploymentErr, debugger, debug.DebugConfig{
Deployment: deployment,
ProviderID: &session.Stack.Provider,
Stack: session.Stack.Name,
Since: since,
Until: time.Now(),
})
return deploymentErr
Comment thread
lionello marked this conversation as resolved.
}
term.Info("Done.")

Expand Down
15 changes: 9 additions & 6 deletions src/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,23 @@ require (
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0
github.com/DefangLabs/secret-detector v0.0.0-20250811234530-d4b4214cd679
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883
github.com/aws/aws-sdk-go-v2 v1.41.5
github.com/aws/aws-sdk-go-v2 v1.42.0
github.com/aws/aws-sdk-go-v2/config v1.32.7
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.42.6
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.65.0
github.com/aws/aws-sdk-go-v2/service/codebuild v1.68.12
github.com/aws/aws-sdk-go-v2/service/ec2 v1.145.0
github.com/aws/aws-sdk-go-v2/service/ecr v1.58.5
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.7
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.55.5
github.com/aws/aws-sdk-go-v2/service/rds v1.119.4
github.com/aws/aws-sdk-go-v2/service/route53 v1.37.1
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.40.5
github.com/aws/aws-sdk-go-v2/service/servicequotas v1.25.5
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7
github.com/aws/aws-sdk-go-v2/service/sts v1.41.6
github.com/aws/smithy-go v1.24.2
github.com/aws/smithy-go v1.27.1
github.com/awslabs/goformation/v7 v7.14.9
github.com/compose-spec/compose-go/v2 v2.10.1
github.com/digitalocean/godo v1.131.1
Expand Down Expand Up @@ -168,13 +171,13 @@ require (
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.7
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 // indirect
Expand Down
30 changes: 18 additions & 12 deletions src/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7l
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY=
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
github.com/aws/aws-sdk-go-v2 v1.42.0 h1:XvXMJTkFQtpBKIWZnmr9ZEOc2InWM2yldjXEJ/bymhA=
github.com/aws/aws-sdk-go-v2 v1.42.0/go.mod h1:27+ACypSLljLAEKsCYOmrjKh83vuTRkuAe9Uv/3A4bg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
github.com/aws/aws-sdk-go-v2/config v1.32.7 h1:vxUyWGUwmkQ2g19n7JY/9YL8MfAIl7bTesIUykECXmY=
Expand All @@ -110,10 +110,10 @@ github.com/aws/aws-sdk-go-v2/credentials v1.19.7 h1:tHK47VqqtJxOymRrNtUXN5SP/zUT
github.com/aws/aws-sdk-go-v2/credentials v1.19.7/go.mod h1:qOZk8sPDrxhf+4Wf4oT2urYJrYt3RejHSzgAquYeppw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 h1:I0GyV8wiYrP8XpA70g1HBcQO1JlQxCMTW9npl5UbDHY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17/go.mod h1:tyw7BOl5bBe/oqvoIeECFJjMdzXoa/dfVz3QQ5lgHGA=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 h1:f3vKqSo13fhTYb+JEcXwXefZQE26I1FB5eTSniU67ko=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29/go.mod h1:MzoLFUArKGpGD+ukmPiTPG1X5x4o6M2kq4v2dr1FiEc=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 h1:RdwIf/CuUsvJX3RgJagbOyotl/cxoLY4xviKuE7p2GY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29/go.mod h1:71wt8W2EgswdZy9Mf9KNnzxZ3TiZlv4caKghPktDOkA=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ=
Expand All @@ -126,16 +126,22 @@ github.com/aws/aws-sdk-go-v2/service/codebuild v1.68.12 h1:lQTVEv/YAk8Rw1Yf4XZS/
github.com/aws/aws-sdk-go-v2/service/codebuild v1.68.12/go.mod h1:yoa0R6Xku788EmJYkFiARzJBxt4A3hgFjQPRmMAttr0=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.145.0 h1:SkSW6wtJmXqJJlBxSc+0mykDdv5nhl9xifMB7JuzNVo=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.145.0/go.mod h1:hIsHE0PaWAQakLCshKS7VKWMGXaqrAFp4m95s2W9E6c=
github.com/aws/aws-sdk-go-v2/service/ecr v1.58.5 h1:y6KxDUTvYd43ODh5o00oPSOTL6RP+aqWHfYDoElCy7Q=
github.com/aws/aws-sdk-go-v2/service/ecr v1.58.5/go.mod h1:7VJFM2lSPHz2I1rRb0a+lbphoOp7hXIgYjGhSTOLY7k=
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.7 h1:NHy1+Jq8gVp8fSLF6Z8SazA+R4Qzsbla/0SbHHReH4Y=
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.7/go.mod h1:KxsaVRXo+DeRMHVp65WqyM49XZiS6n74lEGQindkdgA=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.55.5 h1:tEWkZW08+2cM3BPhymRzo2dEz2aWyHkQzT9av4eIMig=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.55.5/go.mod h1:sUBnPF4iTc3KaCTIbLTr8xXjsnw8J0kXwr0nPCaAK3I=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12 h1:ZD2+BSw9vFsNlKYIasSNt3uDbjqqXIBcM13UJv/Lx2k=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12/go.mod h1:Ms4zlcVBbXbiP7EVLhl+lgjvA/a7YphqQ3Ih3174EmI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29 h1:DRebniUGZ2MqiiIVmQJ04vIXr918hubdHMnarSLEWyU=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29/go.mod h1:LfRkPCD8YHDM2E5eTkos2UpwYeZnBcVarTa8L59bJHA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ=
github.com/aws/aws-sdk-go-v2/service/rds v1.119.4 h1:on9PiAL5ucRg9t17cecI/NagAUVH6zVv2sItyZyq/I0=
github.com/aws/aws-sdk-go-v2/service/rds v1.119.4/go.mod h1:zCRPUdp05FEZG3OO7LmJq9xkSDjMEhkiVrZV0oJs2a0=
github.com/aws/aws-sdk-go-v2/service/route53 v1.37.1 h1:U7OksynDSIFScG+7sGqOuJh+fP1USMkNtjxzGFZYG34=
github.com/aws/aws-sdk-go-v2/service/route53 v1.37.1/go.mod h1:8qqfpG4mug2JLlEyWPSFhEGvJiaZ9iPmMDDMYc5Xtas=
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo=
Expand All @@ -154,8 +160,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 h1:gd84Omyu9JLriJVCbGApcLz
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 h1:5fFjR/ToSOzB2OQ/XqWpZBmNvmP/pJ1jOWYlFDJTjRQ=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.6/go.mod h1:qgFDZQSD/Kys7nJnVqYlWKnh0SSdMjAi0uSwON4wgYQ=
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/aws/smithy-go v1.27.1 h1:4T340VFndXtADGF52gYa1POyL7s9E4Z1OeZ1hCscIw8=
github.com/aws/smithy-go v1.27.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/awslabs/goformation/v7 v7.14.9 h1:sZjjpTqXrcBDz4Fi07JWTT7zKM68XsQkW/7iLAJbA/M=
github.com/awslabs/goformation/v7 v7.14.9/go.mod h1:7obldQ8NQ/AkMsgL5K3l4lRMDFB6kCGUloz5dURcXIs=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
Expand Down
105 changes: 105 additions & 0 deletions src/pkg/agent/tools/cleanup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package tools

import (
"context"
"errors"
"fmt"
"strings"

"github.com/DefangLabs/defang/src/pkg/agent/common"
"github.com/DefangLabs/defang/src/pkg/auth"
"github.com/DefangLabs/defang/src/pkg/cli/client"
"github.com/DefangLabs/defang/src/pkg/elicitations"
"github.com/DefangLabs/defang/src/pkg/stacks"
"github.com/DefangLabs/defang/src/pkg/term"
)

type CleanupParams struct {
common.LoaderParams
}

func HandleCleanupTool(ctx context.Context, loader client.Loader, params CleanupParams, cli CLIInterface, ec elicitations.Controller, sc StackConfig) (string, error) {
term.Debug("Function invoked: cli.Connect")
fabric, err := GetClientWithRetry(ctx, cli, sc.FabricAddr)
if err != nil {
var noBrowserErr auth.ErrNoBrowser
if errors.As(err, &noBrowserErr) {
return noBrowserErr.Error(), nil
}
return "", err
}

workingDir, _ := loader.ProjectWorkingDir(ctx)
sm, err := stacks.NewManager(fabric, workingDir, params.ProjectName, ec)
if err != nil {
return "", fmt.Errorf("failed to create stack manager: %w", err)
Comment thread
lionello marked this conversation as resolved.
}
pp := NewProviderPreparer(cli, ec, fabric, sm)
_, provider, err := pp.SetupProvider(ctx, sc.Stack)
if err != nil {
return "", fmt.Errorf("failed to setup provider: %w", err)
}

projectName, err := cli.LoadProjectNameWithFallback(ctx, loader, provider)
if err != nil {
return "", fmt.Errorf("failed to load project name: %w", err)
}

if err := cli.CanIUseProvider(ctx, fabric, provider, projectName, 0); err != nil {
return "", fmt.Errorf("failed to use provider: %w", err)
}

cleaner, ok := provider.(client.OrphanCleaner)
if !ok {
return "Resource cleanup is currently only supported for AWS. The selected provider does not retain resources that need manual cleanup.", nil
}

orphans, err := cleaner.DiscoverOrphans(ctx, projectName)
if err != nil {
return "", fmt.Errorf("failed to discover leftover resources: %w", err)
}
if len(orphans) == 0 {
return fmt.Sprintf("No leftover resources found for project %q that are blocking cleanup.", projectName), nil
}

var report strings.Builder
fmt.Fprintf(&report, "Found %d leftover resource(s) for project %q blocking cleanup:\n", len(orphans), projectName)

// Without interactive elicitation we cannot get confirmation for these destructive actions,
// so only report what was found and let the caller decide.
if !ec.IsSupported() {
for _, o := range orphans {
fmt.Fprintf(&report, "- [%s] %s — would %s\n", o.Category, o.Name, o.Action)
}
report.WriteString("\nRe-run this tool in an interactive session to apply these changes, then run `defang down` so Pulumi can finish removing the resources.")
return report.String(), nil
}

var cleaned, skipped, failed int
for _, o := range orphans {
confirm, err := ec.RequestEnum(ctx,
fmt.Sprintf("Cleanup will %s for %s %q. Proceed?", o.Action, o.Category, o.Name),
"confirm", []string{"no", "yes"})
if err != nil {
return "", fmt.Errorf("failed to confirm cleanup: %w", err)
}
if confirm != "yes" {
skipped++
fmt.Fprintf(&report, "- [%s] %s — skipped\n", o.Category, o.Name)
continue
}
if err := cleaner.CleanupOrphan(ctx, o); err != nil {
failed++
fmt.Fprintf(&report, "- [%s] %s — failed: %v\n", o.Category, o.Name, err)
continue
}
cleaned++
fmt.Fprintf(&report, "- [%s] %s — done (%s)\n", o.Category, o.Name, o.Action)
}

fmt.Fprintf(&report, "\n%d cleaned, %d skipped, %d failed.", cleaned, skipped, failed)
if cleaned > 0 {
report.WriteString(" Run `defang down` (or the destroy tool) so Pulumi can finish removing the now-unblocked resources.")
}
return report.String(), nil
}
Loading
Loading