Skip to content

Remove the empty .bundle/<name> directory on destroy - #6317

Draft
shreyas-goenka wants to merge 2 commits into
mainfrom
isaac/destroy-parent-cleanup
Draft

Remove the empty .bundle/<name> directory on destroy#6317
shreyas-goenka wants to merge 2 commits into
mainfrom
isaac/destroy-parent-cleanup

Conversation

@shreyas-goenka

Copy link
Copy Markdown
Contributor

Why

bundle destroy deletes ~/.bundle/<name>/<target> but never the <name> parent, so every destroy leaves an empty directory behind:

// bundle/config/mutator/default_workspace_root.go
RootPath = fmt.Sprintf("~/.bundle/%s/%s", bundle.Name, bundle.Target)
// bundle/deploy/files/delete.go — removes RootPath and below, never the ancestor
Workspace.Delete(ctx, workspace.Delete{Path: RootPath, Recursive: true})

Deployments that use a fresh bundle name each time never reuse those directories, so nothing ever reclaims them. Our own CI workspace is the worst case: the acceptance suite deploys ~1,056 bundles per run under names like ci<RUN_ID>x<rand>, across ~26 runs a day. That workspace's .bundle reached 48k+ nodes and triggered a sev0 on webapp MySQL (ES-2138924); its child-node limit had already been raised from 10k to 100k to accommodate it.

The nightly workspace sweep does reclaim them, so this is a sawtooth rather than unbounded growth — but of the 27,243 directories one pass removed, 26,982 were empty leftovers of exactly this shape and only 261 were genuinely leaked deployments. This removes the cause instead of relying on the sweep.

Related: acceptance/bundle/destroy/force-lock-node-limit already exists to cope with MAX_CHILD_NODE_SIZE_EXCEEDED once the limit is hit.

What

Delete the parent directory too, non-recursively, so it is removed only when nothing else is deployed under it — a sibling target of the same bundle keeps it alive. Every failure (sibling present, already gone) is expected and ignored at debug level.

The delete is restricted to the ~/.bundle/<name>/<target> layout the CLI generates. workspace.root_path is user-configurable, and the parent of an arbitrary path is not ours to remove — without the guard, a root_path of /Workspace/Shared/foo would make us try to delete /Workspace/Shared.

Testserver

The fake workspace accepted a non-recursive delete of a non-empty directory, which the real API rejects with DIRECTORY_NOT_EMPTY. Modelled that, so the sibling-target behaviour is covered by the local suite rather than only on cloud.

Tests

  • acceptance/bundle/destroy/all-resources — extended: .bundle/test-bundle is gone after destroy.
  • acceptance/bundle/destroy/sibling-target — new: deploy dev + prod, destroy dev → directory kept; destroy prod → directory deleted.
  • libs/testserver unit test for the new DIRECTORY_NOT_EMPTY behaviour.
  • Recorded-request goldens in bundle/user_agent/simple and bundle/resource_deps/remote_app_url pick up the extra workspace/delete call.

Full local acceptance suite passes (1143/1146 dirs, both engines). The two unrelated failures on my box — fips and bundle/templates/lakeflow-integrations — fail identically without this change (non-FIPS local build; uv cannot reach the network).

This pull request and its description were written by Isaac.

`bundle destroy` deleted `~/.bundle/<name>/<target>` but never the `<name>`
parent, so every destroy left an empty directory behind. Deployments that use a
fresh bundle name each time never reuse those directories, so they accumulate
and count against the workspace child-node limit; the CLI's own CI workspace
reached ~27k of them per day.

Delete the parent too, non-recursively so it survives while another target of
the bundle is still deployed there. The delete is restricted to the
`~/.bundle/<name>/<target>` layout the CLI generates, since `root_path` is
user-configurable and the parent of an arbitrary path is not ours to remove.

The fake workspace accepted a non-recursive delete of a non-empty directory,
which the real API rejects with DIRECTORY_NOT_EMPTY. Model that, so the
sibling-target case is covered by the local suite.

Co-authored-by: Isaac
@eng-dev-ecosystem-bot

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: ccf0ff7

Run: 32240141808

Env 🔄​flaky 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
💚​ aws linux 4 4 286 1160 5:34
💚​ aws windows 4 4 288 1158 7:18
💚​ azure linux 4 4 285 1160 6:08
💚​ azure windows 4 4 287 1158 7:31
🔄​ gcp linux 1 3 4 286 1160 6:56
🔄​ gcp windows 1 3 4 288 1158 8:44
8 interesting tests: 4 SKIP, 3 RECOVERED, 1 flaky
Test Name aws linux aws windows azure linux azure windows gcp linux gcp windows
💚​ TestAccept 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/bundle/invariant/no_drift 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_indexes/recreate/embedding_dimension 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/ssh/connection 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🔄​ TestFetchRepositoryInfoAPI_FromRepo 💚​R 💚​R 💚​R 💚​R 🔄​f 🔄​f
💚​ TestFetchRepositoryInfoAPI_FromRepo/root 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
💚​ TestFetchRepositoryInfoAPI_FromRepo/subdir 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
Top 6 slowest tests (at least 2 minutes):
duration env testname
7:27 gcp windows TestAccept
7:23 azure windows TestAccept
7:10 aws windows TestAccept
3:52 azure linux TestAccept
3:49 aws linux TestAccept
3:43 gcp linux TestAccept

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.

2 participants