caffeinated-whale-cli 3.1.0__tar.gz → 3.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/PKG-INFO +58 -6
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/README.md +57 -5
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/pyproject.toml +1 -1
- caffeinated_whale_cli-3.1.2/src/caffeinated_whale_cli/__init__.py +1 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/axi.py +124 -0
- caffeinated_whale_cli-3.1.2/src/caffeinated_whale_cli/commands/rm_bench.py +184 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/docker.py +175 -3
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/init.py +8 -4
- caffeinated_whale_cli-3.1.2/src/caffeinated_whale_cli/core/rm_bench.py +353 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/scale.py +4 -1
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/start.py +12 -9
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/update.py +23 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/main.py +2 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli.egg-info/SOURCES.txt +2 -0
- caffeinated_whale_cli-3.1.0/src/caffeinated_whale_cli/__init__.py +0 -1
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/LICENSE +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/setup.cfg +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/__main__.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/__init__.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/apps.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/backup.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/config.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/console.html +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/doctor.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/init.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/inspect.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/label.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/list.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/logs.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/open.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/restart.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/restore.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/rm.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/rm_site.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/run.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/scale.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/self_update.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/serve.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/setup.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/start.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/status.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/stop.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/unlock.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/update.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/utils.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/commands/where.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/__init__.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/apps.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/auto_inspect.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/backup.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/bench_ops.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/config.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/cred_bridge.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/credbridge.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/doctor.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/envelope.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/errors.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/exec_stream.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/fleet.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/inspect.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/label.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/list.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/logs.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/open.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/resolvers.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/restart.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/restore.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/rm.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/rm_site.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/run.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/setup.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/status.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/stop.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/supervision.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/unlock.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/url.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/version.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/core/where.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/update_notice.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/utils/__init__.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/utils/agent_hooks.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/utils/auto_inspect.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/utils/bench_labels.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/utils/bench_sites.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/utils/cache.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/utils/completion_utils.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/utils/config_utils.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/utils/console.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/utils/cred_daemon.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/utils/daemon_identity.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/utils/db_utils.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/utils/docker_utils.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/utils/port_utils.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/utils/sendme_utils.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/utils/shared_home.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/utils/startup.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/utils/tips.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/utils/toon.py +0 -0
- {caffeinated_whale_cli-3.1.0 → caffeinated_whale_cli-3.1.2}/src/caffeinated_whale_cli/utils/vscode_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: caffeinated-whale-cli
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.2
|
|
4
4
|
Summary: A CLI tool to help manage Frappe Docker instances.
|
|
5
5
|
Author-email: Christopher McKay <mckay.christopher73@outlook.com>
|
|
6
6
|
License: MIT
|
|
@@ -886,6 +886,58 @@ See [For agents: the `cwcli axi` surface](#for-agents-the-cwcli-axi-surface).
|
|
|
886
886
|
|
|
887
887
|
---
|
|
888
888
|
|
|
889
|
+
### `rm-bench` - Remove One Bench
|
|
890
|
+
|
|
891
|
+
Permanently removes ONE bench from an instance, leaving every other bench and every container running.
|
|
892
|
+
Like `rm-site`, this is a separate, noun-scoped command rather than a `--bench` flag on `cwcli rm`: `cwcli rm` destroys the whole instance, while `rm-bench` only ever touches the one bench you name, so forgetting a flag can never turn a single-bench cleanup into an instance-wide deletion.
|
|
893
|
+
|
|
894
|
+
A bench is a directory on the instance's shared workspace bind mount, and its sites' data lives in the shared database - a bench has **no** dedicated container or named volume of its own.
|
|
895
|
+
So `cwcli rm-bench` backs up and drops every site on the target bench (deleting each site's database and files), then deletes the bench directory.
|
|
896
|
+
|
|
897
|
+
**WARNING:** This action is destructive and cannot be undone.
|
|
898
|
+
Each site is dropped through the same verified copy-out that `cwcli rm-site` uses: `bench drop-site` backs the site up, and cwcli copies that archive out to `~/.cwcli/archive/{project}_dropped_sites/` (or `$CWCLI_HOME/archive/...` when that override is set) and verifies it on disk before the bench directory is deleted.
|
|
899
|
+
If any site's backup cannot be copied out, the bench directory is kept (so the trapped backup is never destroyed) and the command exits non-zero.
|
|
900
|
+
|
|
901
|
+
A bench that is still running is refused: stop it first with `cwcli stop <project> --bench <selector>`.
|
|
902
|
+
`cwcli rm-bench` never deletes a bench out from under its own running code.
|
|
903
|
+
|
|
904
|
+
```bash
|
|
905
|
+
cwcli rm-bench [OPTIONS] PROJECT_NAME
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
**Arguments:**
|
|
909
|
+
|
|
910
|
+
| Argument | Description |
|
|
911
|
+
|----------|-------------|
|
|
912
|
+
| `PROJECT_NAME` | The Docker Compose project name (required) |
|
|
913
|
+
|
|
914
|
+
**Options:**
|
|
915
|
+
|
|
916
|
+
| Option | Description |
|
|
917
|
+
|--------|-------------|
|
|
918
|
+
| `--bench TEXT` | Which bench to remove: its numeric index or label (see [Working with Multiple Benches](#working-with-multiple-benches)) |
|
|
919
|
+
| `-p`, `--path TEXT` | Explicit bench directory inside the container (lower-level alternative to `--bench`; cannot be combined with it) |
|
|
920
|
+
| `--db-root-password TEXT` | MariaDB root password used by `bench drop-site` (default: `123`, matching the compose file's own default) |
|
|
921
|
+
| `-y`, `--yes` | Skip the destructive confirmation and auto-start the container |
|
|
922
|
+
| `-v`, `--verbose` | Enable verbose output |
|
|
923
|
+
|
|
924
|
+
**Examples:**
|
|
925
|
+
|
|
926
|
+
```bash
|
|
927
|
+
# Remove a bench (with confirmation)
|
|
928
|
+
cwcli rm-bench my-project --bench 2
|
|
929
|
+
|
|
930
|
+
# Skip the confirmation prompt
|
|
931
|
+
cwcli rm-bench my-project --bench staging --yes
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
**Agent surface:** `cwcli axi rm-bench <project> --bench <index|label> --yes` emits the outcome as TOON, including `sites_dropped`, `archived_host_paths`, `dir_removed`, and `ok`.
|
|
935
|
+
`--yes` is required (never a prompt) and grants ONLY consent - it never auto-starts a stopped project, exactly like every other bench-scoped `axi` verb.
|
|
936
|
+
A running bench is a non-zero refusal naming `cwcli stop`.
|
|
937
|
+
See [For agents: the `cwcli axi` surface](#for-agents-the-cwcli-axi-surface).
|
|
938
|
+
|
|
939
|
+
---
|
|
940
|
+
|
|
889
941
|
### `logs` - View Bench Logs
|
|
890
942
|
|
|
891
943
|
View the bench's process logs. supervisord writes one log file per Procfile
|
|
@@ -1201,7 +1253,7 @@ A bench that was not running is left alone.
|
|
|
1201
1253
|
**`apps uninstall`** - removes each app from the target site(s) (`bench --site <site> uninstall-app`). This destroys site data, so it is gated by `-y`/`--yes` or an interactive confirmation (a non-TTY without `--yes` refuses). It uses the same resynchronise-and-verify step as install, so no supervised process - web, scheduler, or worker - can keep running against the code and tables of an app that is gone.
|
|
1202
1254
|
|
|
1203
1255
|
**`apps update`** - the canonical app-update path (what the deprecated `cwcli update` now delegates to). Updating the `frappe` framework app runs `bench update --reset`; other apps use the normal git-pull + migrate flow. After the migrations finish and maintenance mode is lifted, it runs the same **resynchronise** step as `apps install`, so a `git pull` cannot leave the bench's web, scheduler, and workers on the code that was there before it; every migrated site must answer Frappe before the run reports success. `--site` narrows which affected sites are migrated; if none of the named site(s) actually have the app installed, the command refuses and exits non-zero rather than silently migrating nothing (a genuine typo/mismatch guard - a bench with no affected sites at all still exits zero). It accepts the same migration flags as the [deprecated `update` command](#update---update-apps-and-migrate) (`--clear-cache`, `--clear-website-cache`, `--build`, `--skip-maintenance`, `--no-recache`). When updating the `frappe` framework app the flow runs the bench-wide `bench update --reset`, so `--site` and those per-app migration flags do not apply and are reported as ignored.
|
|
1204
|
-
`--force` is an opt-in, **destructive** recovery for a conflicting update: if an app's `git pull` hits a **conflict** (local changes that would be overwritten, or a conflicted merge), it hard-resets that app to its remote tracking branch (`git fetch` then `git reset --hard @{u}`), discarding the local changes, so the update completes instead of failing. It fires **only on a conflict** - a clean update is untouched, and a pull that failed for another reason (network, auth) is left as the failure it is rather than having work discarded. Without `--force`, a conflict fails safely and discards nothing. Every force-reset app is reported (`force_reset_apps`) and a warning is printed at the reset. `--force` does not delete untracked files (it never runs `git clean`), matching `apps checkout --reset`.
|
|
1256
|
+
`--force` is an opt-in, **destructive** recovery for a conflicting update: if an app's `git pull` hits a **conflict** (local changes that would be overwritten, or a conflicted merge), it hard-resets that app to its remote tracking branch (`git fetch` then `git reset --hard @{u}`), discarding the local changes, so the update completes instead of failing. It fires **only on a conflict** - a clean update is untouched, and a pull that failed for another reason (network, auth) is left as the failure it is rather than having work discarded. Without `--force`, a conflict fails safely and discards nothing. Every force-reset app is reported (`force_reset_apps`) and a warning is printed at the reset. `--force` does not delete untracked files (it never runs `git clean`), matching `apps checkout --reset`. `--force` also clears a git **"dubious ownership"** refusal before pulling: when an app's repository is owned by a different user than the container runs as - common on a shared-mode or migrated instance whose app sources are owned by a pre-migration uid - it re-owns the repository to the container user so the pull proceeds, rather than surfacing a raw git ownership error. A `cwcli start`/`cwcli restart` self-heals the same ownership so a plain `apps update` works too; `--force` fixes it inline without a restart.
|
|
1205
1257
|
|
|
1206
1258
|
**`apps checkout`** - fetches and checks out an arbitrary branch, tag, or commit (`REF`) into an app that is **already present** in the bench (`apps/<app>`), so a specific feature branch can be put under test in the instance the app lives in.
|
|
1207
1259
|
Unlike `apps install` (bench acquisition when needed, then site installation) and `apps update` (the tracked upstream on every app), this targets one existing checkout: it runs `git fetch <remote> <ref>` then `git checkout -B <ref> FETCH_HEAD` in the app directory (the remote is auto-detected - `upstream` for a bench-installed app, `origin` for a hand-cloned one).
|
|
@@ -2204,11 +2256,11 @@ cwcli run my-project migrate --bench 1 # by index
|
|
|
2204
2256
|
cwcli update my-project --app erpnext --bench staging # by label
|
|
2205
2257
|
```
|
|
2206
2258
|
|
|
2207
|
-
`--bench` is available on `run`, `backup`, `update`, `open`, `unlock`, `restore`, `rm-site`, `start`, `stop`, `restart`, `status`, and `logs`. Setting labels lives in the [`label`](#label---manage-bench-labels) command.
|
|
2259
|
+
`--bench` is available on `run`, `backup`, `update`, `open`, `unlock`, `restore`, `rm-site`, `rm-bench`, `start`, `stop`, `restart`, `status`, and `logs`. Setting labels lives in the [`label`](#label---manage-bench-labels) command.
|
|
2208
2260
|
|
|
2209
2261
|
**No silent guessing on data commands:**
|
|
2210
2262
|
|
|
2211
|
-
If a project has more than one bench and you do not pass `--bench` (or `--path`), the data commands (`run`, `backup`, `update`, `open`, `unlock`, `restore`, `rm-site`) stop and list the benches instead of guessing:
|
|
2263
|
+
If a project has more than one bench and you do not pass `--bench` (or `--path`), the data commands (`run`, `backup`, `update`, `open`, `unlock`, `restore`, `rm-site`, `rm-bench`) stop and list the benches instead of guessing:
|
|
2212
2264
|
|
|
2213
2265
|
```
|
|
2214
2266
|
Error: project 'my-project' has multiple benches; specify one with --bench <index|label>:
|
|
@@ -2592,14 +2644,14 @@ The CLI uses:
|
|
|
2592
2644
|
- **Peewee ORM** - SQLite-based caching
|
|
2593
2645
|
|
|
2594
2646
|
**Logic core:** business logic and I/O live in a UI-pure `core/` package that carries no `rich`/`questionary`/`typer`; it returns a serializable typed envelope (or raises a typed error) so the human CLI, the `cwcli axi` agent surface, and the Console GUI are all thin frontends over one implementation.
|
|
2595
|
-
`backup`, `unlock`, `stop`, `label`, `run`, `ls`/`list`, `where`, `start`/`status`/`restart`, `logs`, `inspect`, `apps`, `update`, `open`, `init`, `config`, `restore`, `rm`, and `rm-
|
|
2647
|
+
`backup`, `unlock`, `stop`, `label`, `run`, `ls`/`list`, `where`, `start`/`status`/`restart`, `logs`, `inspect`, `apps`, `update`, `open`, `init`, `config`, `restore`, `rm`, `rm-site`, and `rm-bench` are migrated onto it so far.
|
|
2596
2648
|
|
|
2597
2649
|
**Data Directories:**
|
|
2598
2650
|
- **Projects**: `~/.cwcli/projects/` - Project directories created by `cwcli init`
|
|
2599
2651
|
- **Config**: `~/.cwcli/config/` - Configuration files
|
|
2600
2652
|
- **Cache**: `~/.cwcli/cache/cwc-cache.db` - Project inspection cache
|
|
2601
2653
|
- **Runtime**: `~/.cwcli/run/` - PID and log files for background services
|
|
2602
|
-
- **Archive**: `~/.cwcli/archive/` - Pre-deletion backups and config snapshots from `cwcli rm`, plus dropped-site archives from `cwcli rm-site`
|
|
2654
|
+
- **Archive**: `~/.cwcli/archive/` - Pre-deletion backups and config snapshots from `cwcli rm`, plus dropped-site archives from `cwcli rm-site` and `cwcli rm-bench`
|
|
2603
2655
|
- **Transfers**: `~/.cwcli/tmp/` by default, or `$TMPDIR/cwcli/`, `$TEMP/cwcli/`, or `$TMP/cwcli/` when the first non-empty variable in that order is set. Temporary files used by `restore --send` and `restore --receive`; each attempt is removed when it finishes or fails
|
|
2604
2656
|
|
|
2605
2657
|
**Relocating cwcli's data (`CWCLI_HOME`):**
|
|
@@ -846,6 +846,58 @@ See [For agents: the `cwcli axi` surface](#for-agents-the-cwcli-axi-surface).
|
|
|
846
846
|
|
|
847
847
|
---
|
|
848
848
|
|
|
849
|
+
### `rm-bench` - Remove One Bench
|
|
850
|
+
|
|
851
|
+
Permanently removes ONE bench from an instance, leaving every other bench and every container running.
|
|
852
|
+
Like `rm-site`, this is a separate, noun-scoped command rather than a `--bench` flag on `cwcli rm`: `cwcli rm` destroys the whole instance, while `rm-bench` only ever touches the one bench you name, so forgetting a flag can never turn a single-bench cleanup into an instance-wide deletion.
|
|
853
|
+
|
|
854
|
+
A bench is a directory on the instance's shared workspace bind mount, and its sites' data lives in the shared database - a bench has **no** dedicated container or named volume of its own.
|
|
855
|
+
So `cwcli rm-bench` backs up and drops every site on the target bench (deleting each site's database and files), then deletes the bench directory.
|
|
856
|
+
|
|
857
|
+
**WARNING:** This action is destructive and cannot be undone.
|
|
858
|
+
Each site is dropped through the same verified copy-out that `cwcli rm-site` uses: `bench drop-site` backs the site up, and cwcli copies that archive out to `~/.cwcli/archive/{project}_dropped_sites/` (or `$CWCLI_HOME/archive/...` when that override is set) and verifies it on disk before the bench directory is deleted.
|
|
859
|
+
If any site's backup cannot be copied out, the bench directory is kept (so the trapped backup is never destroyed) and the command exits non-zero.
|
|
860
|
+
|
|
861
|
+
A bench that is still running is refused: stop it first with `cwcli stop <project> --bench <selector>`.
|
|
862
|
+
`cwcli rm-bench` never deletes a bench out from under its own running code.
|
|
863
|
+
|
|
864
|
+
```bash
|
|
865
|
+
cwcli rm-bench [OPTIONS] PROJECT_NAME
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
**Arguments:**
|
|
869
|
+
|
|
870
|
+
| Argument | Description |
|
|
871
|
+
|----------|-------------|
|
|
872
|
+
| `PROJECT_NAME` | The Docker Compose project name (required) |
|
|
873
|
+
|
|
874
|
+
**Options:**
|
|
875
|
+
|
|
876
|
+
| Option | Description |
|
|
877
|
+
|--------|-------------|
|
|
878
|
+
| `--bench TEXT` | Which bench to remove: its numeric index or label (see [Working with Multiple Benches](#working-with-multiple-benches)) |
|
|
879
|
+
| `-p`, `--path TEXT` | Explicit bench directory inside the container (lower-level alternative to `--bench`; cannot be combined with it) |
|
|
880
|
+
| `--db-root-password TEXT` | MariaDB root password used by `bench drop-site` (default: `123`, matching the compose file's own default) |
|
|
881
|
+
| `-y`, `--yes` | Skip the destructive confirmation and auto-start the container |
|
|
882
|
+
| `-v`, `--verbose` | Enable verbose output |
|
|
883
|
+
|
|
884
|
+
**Examples:**
|
|
885
|
+
|
|
886
|
+
```bash
|
|
887
|
+
# Remove a bench (with confirmation)
|
|
888
|
+
cwcli rm-bench my-project --bench 2
|
|
889
|
+
|
|
890
|
+
# Skip the confirmation prompt
|
|
891
|
+
cwcli rm-bench my-project --bench staging --yes
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
**Agent surface:** `cwcli axi rm-bench <project> --bench <index|label> --yes` emits the outcome as TOON, including `sites_dropped`, `archived_host_paths`, `dir_removed`, and `ok`.
|
|
895
|
+
`--yes` is required (never a prompt) and grants ONLY consent - it never auto-starts a stopped project, exactly like every other bench-scoped `axi` verb.
|
|
896
|
+
A running bench is a non-zero refusal naming `cwcli stop`.
|
|
897
|
+
See [For agents: the `cwcli axi` surface](#for-agents-the-cwcli-axi-surface).
|
|
898
|
+
|
|
899
|
+
---
|
|
900
|
+
|
|
849
901
|
### `logs` - View Bench Logs
|
|
850
902
|
|
|
851
903
|
View the bench's process logs. supervisord writes one log file per Procfile
|
|
@@ -1161,7 +1213,7 @@ A bench that was not running is left alone.
|
|
|
1161
1213
|
**`apps uninstall`** - removes each app from the target site(s) (`bench --site <site> uninstall-app`). This destroys site data, so it is gated by `-y`/`--yes` or an interactive confirmation (a non-TTY without `--yes` refuses). It uses the same resynchronise-and-verify step as install, so no supervised process - web, scheduler, or worker - can keep running against the code and tables of an app that is gone.
|
|
1162
1214
|
|
|
1163
1215
|
**`apps update`** - the canonical app-update path (what the deprecated `cwcli update` now delegates to). Updating the `frappe` framework app runs `bench update --reset`; other apps use the normal git-pull + migrate flow. After the migrations finish and maintenance mode is lifted, it runs the same **resynchronise** step as `apps install`, so a `git pull` cannot leave the bench's web, scheduler, and workers on the code that was there before it; every migrated site must answer Frappe before the run reports success. `--site` narrows which affected sites are migrated; if none of the named site(s) actually have the app installed, the command refuses and exits non-zero rather than silently migrating nothing (a genuine typo/mismatch guard - a bench with no affected sites at all still exits zero). It accepts the same migration flags as the [deprecated `update` command](#update---update-apps-and-migrate) (`--clear-cache`, `--clear-website-cache`, `--build`, `--skip-maintenance`, `--no-recache`). When updating the `frappe` framework app the flow runs the bench-wide `bench update --reset`, so `--site` and those per-app migration flags do not apply and are reported as ignored.
|
|
1164
|
-
`--force` is an opt-in, **destructive** recovery for a conflicting update: if an app's `git pull` hits a **conflict** (local changes that would be overwritten, or a conflicted merge), it hard-resets that app to its remote tracking branch (`git fetch` then `git reset --hard @{u}`), discarding the local changes, so the update completes instead of failing. It fires **only on a conflict** - a clean update is untouched, and a pull that failed for another reason (network, auth) is left as the failure it is rather than having work discarded. Without `--force`, a conflict fails safely and discards nothing. Every force-reset app is reported (`force_reset_apps`) and a warning is printed at the reset. `--force` does not delete untracked files (it never runs `git clean`), matching `apps checkout --reset`.
|
|
1216
|
+
`--force` is an opt-in, **destructive** recovery for a conflicting update: if an app's `git pull` hits a **conflict** (local changes that would be overwritten, or a conflicted merge), it hard-resets that app to its remote tracking branch (`git fetch` then `git reset --hard @{u}`), discarding the local changes, so the update completes instead of failing. It fires **only on a conflict** - a clean update is untouched, and a pull that failed for another reason (network, auth) is left as the failure it is rather than having work discarded. Without `--force`, a conflict fails safely and discards nothing. Every force-reset app is reported (`force_reset_apps`) and a warning is printed at the reset. `--force` does not delete untracked files (it never runs `git clean`), matching `apps checkout --reset`. `--force` also clears a git **"dubious ownership"** refusal before pulling: when an app's repository is owned by a different user than the container runs as - common on a shared-mode or migrated instance whose app sources are owned by a pre-migration uid - it re-owns the repository to the container user so the pull proceeds, rather than surfacing a raw git ownership error. A `cwcli start`/`cwcli restart` self-heals the same ownership so a plain `apps update` works too; `--force` fixes it inline without a restart.
|
|
1165
1217
|
|
|
1166
1218
|
**`apps checkout`** - fetches and checks out an arbitrary branch, tag, or commit (`REF`) into an app that is **already present** in the bench (`apps/<app>`), so a specific feature branch can be put under test in the instance the app lives in.
|
|
1167
1219
|
Unlike `apps install` (bench acquisition when needed, then site installation) and `apps update` (the tracked upstream on every app), this targets one existing checkout: it runs `git fetch <remote> <ref>` then `git checkout -B <ref> FETCH_HEAD` in the app directory (the remote is auto-detected - `upstream` for a bench-installed app, `origin` for a hand-cloned one).
|
|
@@ -2164,11 +2216,11 @@ cwcli run my-project migrate --bench 1 # by index
|
|
|
2164
2216
|
cwcli update my-project --app erpnext --bench staging # by label
|
|
2165
2217
|
```
|
|
2166
2218
|
|
|
2167
|
-
`--bench` is available on `run`, `backup`, `update`, `open`, `unlock`, `restore`, `rm-site`, `start`, `stop`, `restart`, `status`, and `logs`. Setting labels lives in the [`label`](#label---manage-bench-labels) command.
|
|
2219
|
+
`--bench` is available on `run`, `backup`, `update`, `open`, `unlock`, `restore`, `rm-site`, `rm-bench`, `start`, `stop`, `restart`, `status`, and `logs`. Setting labels lives in the [`label`](#label---manage-bench-labels) command.
|
|
2168
2220
|
|
|
2169
2221
|
**No silent guessing on data commands:**
|
|
2170
2222
|
|
|
2171
|
-
If a project has more than one bench and you do not pass `--bench` (or `--path`), the data commands (`run`, `backup`, `update`, `open`, `unlock`, `restore`, `rm-site`) stop and list the benches instead of guessing:
|
|
2223
|
+
If a project has more than one bench and you do not pass `--bench` (or `--path`), the data commands (`run`, `backup`, `update`, `open`, `unlock`, `restore`, `rm-site`, `rm-bench`) stop and list the benches instead of guessing:
|
|
2172
2224
|
|
|
2173
2225
|
```
|
|
2174
2226
|
Error: project 'my-project' has multiple benches; specify one with --bench <index|label>:
|
|
@@ -2552,14 +2604,14 @@ The CLI uses:
|
|
|
2552
2604
|
- **Peewee ORM** - SQLite-based caching
|
|
2553
2605
|
|
|
2554
2606
|
**Logic core:** business logic and I/O live in a UI-pure `core/` package that carries no `rich`/`questionary`/`typer`; it returns a serializable typed envelope (or raises a typed error) so the human CLI, the `cwcli axi` agent surface, and the Console GUI are all thin frontends over one implementation.
|
|
2555
|
-
`backup`, `unlock`, `stop`, `label`, `run`, `ls`/`list`, `where`, `start`/`status`/`restart`, `logs`, `inspect`, `apps`, `update`, `open`, `init`, `config`, `restore`, `rm`, and `rm-
|
|
2607
|
+
`backup`, `unlock`, `stop`, `label`, `run`, `ls`/`list`, `where`, `start`/`status`/`restart`, `logs`, `inspect`, `apps`, `update`, `open`, `init`, `config`, `restore`, `rm`, `rm-site`, and `rm-bench` are migrated onto it so far.
|
|
2556
2608
|
|
|
2557
2609
|
**Data Directories:**
|
|
2558
2610
|
- **Projects**: `~/.cwcli/projects/` - Project directories created by `cwcli init`
|
|
2559
2611
|
- **Config**: `~/.cwcli/config/` - Configuration files
|
|
2560
2612
|
- **Cache**: `~/.cwcli/cache/cwc-cache.db` - Project inspection cache
|
|
2561
2613
|
- **Runtime**: `~/.cwcli/run/` - PID and log files for background services
|
|
2562
|
-
- **Archive**: `~/.cwcli/archive/` - Pre-deletion backups and config snapshots from `cwcli rm`, plus dropped-site archives from `cwcli rm-site`
|
|
2614
|
+
- **Archive**: `~/.cwcli/archive/` - Pre-deletion backups and config snapshots from `cwcli rm`, plus dropped-site archives from `cwcli rm-site` and `cwcli rm-bench`
|
|
2563
2615
|
- **Transfers**: `~/.cwcli/tmp/` by default, or `$TMPDIR/cwcli/`, `$TEMP/cwcli/`, or `$TMP/cwcli/` when the first non-empty variable in that order is set. Temporary files used by `restore --send` and `restore --receive`; each attempt is removed when it finishes or fails
|
|
2564
2616
|
|
|
2565
2617
|
**Relocating cwcli's data (`CWCLI_HOME`):**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "3.1.2"
|
|
@@ -47,6 +47,7 @@ from ..core import list as core_list
|
|
|
47
47
|
from ..core import logs as core_logs
|
|
48
48
|
from ..core import restart as core_restart
|
|
49
49
|
from ..core import rm as core_rm
|
|
50
|
+
from ..core import rm_bench as core_rm_bench
|
|
50
51
|
from ..core import rm_site as core_rm_site
|
|
51
52
|
from ..core import scale as core_scale
|
|
52
53
|
from ..core import start as core_start
|
|
@@ -578,6 +579,11 @@ def _choice_error_message(choice: Choice) -> str:
|
|
|
578
579
|
# `axi rm-site` only. Dropping a site deletes its database and files;
|
|
579
580
|
# the agent must consent. The --yes flag rides the `help:` line below.
|
|
580
581
|
return "dropping this site permanently deletes its database and files"
|
|
582
|
+
if choice.kind == "confirm_remove_bench":
|
|
583
|
+
# `axi rm-bench` only. Removing a bench drops every site on it (their
|
|
584
|
+
# databases and files) and deletes the bench directory; the agent must
|
|
585
|
+
# consent. The --yes flag rides the `help:` line below.
|
|
586
|
+
return "removing this bench drops every site on it and deletes its directory"
|
|
581
587
|
return f"a decision is required: {choice.prompt}"
|
|
582
588
|
|
|
583
589
|
|
|
@@ -602,6 +608,8 @@ def emit_axi_choice_as_usage_error(choice: Choice) -> None:
|
|
|
602
608
|
typer.echo(toon.kv("help", "re-run with --yes to accept the whole-instance restart"))
|
|
603
609
|
elif choice.kind == "confirm_drop_site":
|
|
604
610
|
typer.echo(toon.kv("help", "re-run with --yes to consent to permanently dropping it"))
|
|
611
|
+
elif choice.kind == "confirm_remove_bench":
|
|
612
|
+
typer.echo(toon.kv("help", "re-run with --yes to consent to permanently removing it"))
|
|
605
613
|
elif choice.kind == "confirm_reuse_bench":
|
|
606
614
|
typer.echo(
|
|
607
615
|
toon.kv(
|
|
@@ -2673,6 +2681,122 @@ def axi_rm_site(
|
|
|
2673
2681
|
raise typer.Exit(0 if outcome.ok else 1)
|
|
2674
2682
|
|
|
2675
2683
|
|
|
2684
|
+
# --------------------------------------------------------------------------- rm-bench
|
|
2685
|
+
|
|
2686
|
+
|
|
2687
|
+
def _rm_bench_narrate(event) -> None:
|
|
2688
|
+
"""Every rm-bench event to STDERR (the `_drop_site_narrate` reasoning): the
|
|
2689
|
+
per-site `bench drop-site` output and progress notes are not supervised-process
|
|
2690
|
+
logs, and they must not touch stdout, which carries the one TOON document.
|
|
2691
|
+
"""
|
|
2692
|
+
if isinstance(event, core_rm_bench.BenchRmCommand):
|
|
2693
|
+
print(f"$ {event.command}", file=sys.stderr, flush=True)
|
|
2694
|
+
elif isinstance(event, core_rm_bench.BenchRmOutput):
|
|
2695
|
+
print(event.text, end="", file=sys.stderr, flush=True)
|
|
2696
|
+
elif isinstance(event, (core_rm_bench.BenchRmNotice, core_rm_bench.BenchRmStep)):
|
|
2697
|
+
text = getattr(event, "text", None) or getattr(event, "label", "")
|
|
2698
|
+
print(text, file=sys.stderr, flush=True)
|
|
2699
|
+
elif isinstance(event, core_rm_bench.BenchRmWarning):
|
|
2700
|
+
print(f"Warning: {event.text}", file=sys.stderr, flush=True)
|
|
2701
|
+
if event.hint:
|
|
2702
|
+
print(event.hint, file=sys.stderr, flush=True)
|
|
2703
|
+
|
|
2704
|
+
|
|
2705
|
+
@app.command("rm-bench")
|
|
2706
|
+
def axi_rm_bench(
|
|
2707
|
+
project: str = typer.Argument(..., help="The Docker Compose project name."),
|
|
2708
|
+
bench: str = typer.Option(
|
|
2709
|
+
None, "--bench", help="Which bench to remove: numeric index or label (see 'axi benches')."
|
|
2710
|
+
),
|
|
2711
|
+
db_root_password: str = typer.Option(
|
|
2712
|
+
"123", "--db-root-password", help="MariaDB root password used by 'bench drop-site'."
|
|
2713
|
+
),
|
|
2714
|
+
yes: bool = typer.Option(
|
|
2715
|
+
False,
|
|
2716
|
+
"--yes",
|
|
2717
|
+
help="REQUIRED consent to permanently remove this bench and drop its sites.",
|
|
2718
|
+
),
|
|
2719
|
+
) -> None:
|
|
2720
|
+
"""Permanently remove ONE bench; emit the outcome as TOON (never prompts).
|
|
2721
|
+
|
|
2722
|
+
The bench-scoped sibling of `axi rm` (whole instance) and `axi rm-site` (one
|
|
2723
|
+
site). A bench has no dedicated container or named volume - it is a directory
|
|
2724
|
+
on the instance's shared workspace bind mount whose sites live in the shared
|
|
2725
|
+
database - so this backs up and drops every site on the bench (deleting each
|
|
2726
|
+
site's database and files) and then deletes the bench directory. Every OTHER
|
|
2727
|
+
bench and every container keep running; it never touches the instance's
|
|
2728
|
+
containers, named volumes, or project directory.
|
|
2729
|
+
|
|
2730
|
+
`--yes` is REQUIRED: without it this is a usage error naming the flag, never
|
|
2731
|
+
a prompt. NO auto-start: a stopped project is a usage error naming
|
|
2732
|
+
`cwcli start`, exactly as every other bench-scoped axi verb refuses it.
|
|
2733
|
+
|
|
2734
|
+
A bench that is still running is refused (exit 1) naming
|
|
2735
|
+
`cwcli stop <project> --bench <selector>`: a live bench is never deleted out
|
|
2736
|
+
from under its own running code.
|
|
2737
|
+
|
|
2738
|
+
Each site is dropped through the same verified copy-out `axi rm-site` uses:
|
|
2739
|
+
`bench drop-site` backs the site up, and cwcli copies that archive out to the
|
|
2740
|
+
managed host location (`archived_host_paths` in the report) and verifies it on
|
|
2741
|
+
disk before the bench directory is deleted. If any site's archive could not be
|
|
2742
|
+
copied out, the directory is kept (to preserve that trapped archive), the site
|
|
2743
|
+
is reported in `sites_failed`, and `ok: false` with a non-zero exit.
|
|
2744
|
+
|
|
2745
|
+
Each `bench drop-site`'s own output goes to stderr in full and unparsed.
|
|
2746
|
+
"""
|
|
2747
|
+
if not yes:
|
|
2748
|
+
emit_axi_error(
|
|
2749
|
+
CwcliError(
|
|
2750
|
+
ErrorKind.USAGE,
|
|
2751
|
+
"rm_bench.consent_required",
|
|
2752
|
+
f"Refusing to remove a bench from '{project}' without explicit consent.",
|
|
2753
|
+
hint=(
|
|
2754
|
+
"re-run with --yes to permanently remove this bench and drop its "
|
|
2755
|
+
"sites' databases and files"
|
|
2756
|
+
),
|
|
2757
|
+
)
|
|
2758
|
+
)
|
|
2759
|
+
raise typer.Exit(exit_for(ErrorKind.USAGE))
|
|
2760
|
+
|
|
2761
|
+
try:
|
|
2762
|
+
result = core_rm_bench.remove_bench(
|
|
2763
|
+
project,
|
|
2764
|
+
bench=bench,
|
|
2765
|
+
consent=yes,
|
|
2766
|
+
db_root_password=db_root_password,
|
|
2767
|
+
on_event=_rm_bench_narrate,
|
|
2768
|
+
)
|
|
2769
|
+
except CwcliError as error:
|
|
2770
|
+
emit_axi_error(error)
|
|
2771
|
+
raise typer.Exit(exit_for(error.kind)) from None
|
|
2772
|
+
|
|
2773
|
+
if result.status is CoreStatus.NEEDS_CHOICE:
|
|
2774
|
+
assert result.choice is not None # NEEDS_CHOICE always carries a Choice
|
|
2775
|
+
emit_axi_choice_as_usage_error(result.choice)
|
|
2776
|
+
raise typer.Exit(2)
|
|
2777
|
+
|
|
2778
|
+
assert result.data is not None # OK/WARNING always carries a BenchRemovalOutcome
|
|
2779
|
+
outcome = result.data
|
|
2780
|
+
|
|
2781
|
+
# Removing a bench changes the instance's bench set, so refresh the cache.
|
|
2782
|
+
# A failed recache is a stderr warning, NOT a non-zero exit: the removal
|
|
2783
|
+
# already happened, and failing here would make an agent retry a bench that
|
|
2784
|
+
# is already gone.
|
|
2785
|
+
if not cache.recache_project(project):
|
|
2786
|
+
print(
|
|
2787
|
+
f"Warning: bench removed, but re-caching '{project}' failed; "
|
|
2788
|
+
"run 'cwcli inspect --update' to refresh.",
|
|
2789
|
+
file=sys.stderr,
|
|
2790
|
+
flush=True,
|
|
2791
|
+
)
|
|
2792
|
+
|
|
2793
|
+
emit_result(outcome, warnings=result.warnings)
|
|
2794
|
+
# ok, NOT result.status: a partial removal (a site's backup trapped in the
|
|
2795
|
+
# container) is a WARNING-shaped envelope, which maps to exit 0 everywhere
|
|
2796
|
+
# else and would report a green exit for an incomplete removal.
|
|
2797
|
+
raise typer.Exit(0 if outcome.ok else 1)
|
|
2798
|
+
|
|
2799
|
+
|
|
2676
2800
|
# ------------------------------------------------------------------------ self-update
|
|
2677
2801
|
|
|
2678
2802
|
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""``cwcli rm-bench`` - the human frontend over ``core.remove_bench``.
|
|
2
|
+
|
|
3
|
+
A dedicated, noun-scoped verb (not a ``--bench`` flag on ``rm``): removing ONE
|
|
4
|
+
bench drops its sites and deletes its directory while every sibling bench and
|
|
5
|
+
container keeps running, whereas ``cwcli rm`` destroys the whole instance, and
|
|
6
|
+
hiding that difference behind a flag on the bigger command is a footgun. See
|
|
7
|
+
``core/rm_bench.py``'s module docstring for the full rationale, the "refuse while
|
|
8
|
+
running" gate, and the per-site backup-then-drop the deletion is built from.
|
|
9
|
+
|
|
10
|
+
This module only prompts, resolves the bench, streams each site drop's own
|
|
11
|
+
output, and renders the outcome; the resolution, the running gate, the
|
|
12
|
+
``bench drop-site`` fan-out, and the directory deletion live in the core.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
import typer
|
|
18
|
+
|
|
19
|
+
from ..core import rm_bench as core_rm_bench
|
|
20
|
+
from ..core.envelope import Status
|
|
21
|
+
from ..core.errors import CwcliError
|
|
22
|
+
from ..core.rm_bench import (
|
|
23
|
+
BenchRmCommand,
|
|
24
|
+
BenchRmNotice,
|
|
25
|
+
BenchRmOutput,
|
|
26
|
+
BenchRmStep,
|
|
27
|
+
BenchRmWarning,
|
|
28
|
+
)
|
|
29
|
+
from ..utils import cache
|
|
30
|
+
from ..utils.completion_utils import complete_project_names
|
|
31
|
+
from ..utils.console import console, stderr_console
|
|
32
|
+
from ..utils.docker_utils import handle_docker_errors
|
|
33
|
+
from .utils import confirm_or_exit, ensure_containers_running, resolve_bench_path
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _make_renderer(*, verbose: bool):
|
|
37
|
+
def on_event(event) -> None:
|
|
38
|
+
if isinstance(event, BenchRmStep):
|
|
39
|
+
if verbose:
|
|
40
|
+
stderr_console.print(f"[dim]{event.label}[/dim]")
|
|
41
|
+
elif isinstance(event, BenchRmCommand):
|
|
42
|
+
if verbose:
|
|
43
|
+
stderr_console.print(f"[dim]$ {event.command}[/dim]")
|
|
44
|
+
elif isinstance(event, BenchRmOutput):
|
|
45
|
+
sys.stdout.write(event.text)
|
|
46
|
+
sys.stdout.flush()
|
|
47
|
+
elif isinstance(event, BenchRmNotice):
|
|
48
|
+
console.print(f"[dim]{event.text}[/dim]")
|
|
49
|
+
elif isinstance(event, BenchRmWarning):
|
|
50
|
+
stderr_console.print(f"[yellow]Warning:[/yellow] {event.text}")
|
|
51
|
+
if event.hint:
|
|
52
|
+
stderr_console.print(f"[dim]{event.hint}[/dim]")
|
|
53
|
+
|
|
54
|
+
return on_event
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _exit_on_error(e: CwcliError):
|
|
58
|
+
stderr_console.print(f"[bold red]Error:[/bold red] {e.message}")
|
|
59
|
+
if e.hint:
|
|
60
|
+
stderr_console.print(f"[dim]{e.hint}[/dim]")
|
|
61
|
+
raise typer.Exit(code=1) from e
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@handle_docker_errors
|
|
65
|
+
def rm_bench(
|
|
66
|
+
project_name: str = typer.Argument(
|
|
67
|
+
..., help="The Docker Compose project name.", autocompletion=complete_project_names
|
|
68
|
+
),
|
|
69
|
+
bench: str = typer.Option(
|
|
70
|
+
None, "--bench", help="Which bench to remove: its numeric index or label."
|
|
71
|
+
),
|
|
72
|
+
bench_path: str = typer.Option(
|
|
73
|
+
None, "--path", "-p", help="Explicit bench directory (lower-level alternative to --bench)."
|
|
74
|
+
),
|
|
75
|
+
db_root_password: str = typer.Option(
|
|
76
|
+
"123", "--db-root-password", help="MariaDB root password used by 'bench drop-site'."
|
|
77
|
+
),
|
|
78
|
+
yes: bool = typer.Option(
|
|
79
|
+
False,
|
|
80
|
+
"--yes",
|
|
81
|
+
"-y",
|
|
82
|
+
help="Skip the destructive confirmation and auto-start the container.",
|
|
83
|
+
),
|
|
84
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose output."),
|
|
85
|
+
):
|
|
86
|
+
"""Permanently remove ONE bench from an instance (destructive).
|
|
87
|
+
|
|
88
|
+
Backs up and drops every site on the target bench (deleting each site's
|
|
89
|
+
database and files), then deletes the bench directory. Every OTHER bench and
|
|
90
|
+
every container keep running - this never touches the instance's containers,
|
|
91
|
+
named volumes, or the project directory (use 'cwcli rm' to remove the whole
|
|
92
|
+
instance).
|
|
93
|
+
|
|
94
|
+
A bench that is still running is refused: stop it first with
|
|
95
|
+
'cwcli stop <project> --bench <selector>'. Each site's backup is copied out
|
|
96
|
+
to ~/.cwcli/archive/<project>_dropped_sites/ (or $CWCLI_HOME/archive/... if
|
|
97
|
+
that override is set) and verified on disk before the bench directory is
|
|
98
|
+
deleted; if a backup cannot be copied out, the directory is kept so the
|
|
99
|
+
backup is never destroyed, and this command exits non-zero.
|
|
100
|
+
|
|
101
|
+
Examples:
|
|
102
|
+
cwcli rm-bench my-project --bench 2
|
|
103
|
+
cwcli rm-bench my-project --bench staging --yes
|
|
104
|
+
"""
|
|
105
|
+
ensure_containers_running(project_name, require_running=True, verbose=verbose, auto_start=yes)
|
|
106
|
+
|
|
107
|
+
resolved = resolve_bench_path(project_name, bench, bench_path, verbose=verbose)
|
|
108
|
+
if resolved:
|
|
109
|
+
bench_path = resolved
|
|
110
|
+
if verbose:
|
|
111
|
+
stderr_console.print(f"[dim]Using bench path: {bench_path}[/dim]")
|
|
112
|
+
|
|
113
|
+
on_event = _make_renderer(verbose=verbose)
|
|
114
|
+
|
|
115
|
+
def _remove(consent: bool):
|
|
116
|
+
try:
|
|
117
|
+
return core_rm_bench.remove_bench(
|
|
118
|
+
project_name,
|
|
119
|
+
bench_path=bench_path,
|
|
120
|
+
consent=consent,
|
|
121
|
+
db_root_password=db_root_password,
|
|
122
|
+
on_event=on_event,
|
|
123
|
+
)
|
|
124
|
+
except CwcliError as e:
|
|
125
|
+
_exit_on_error(e)
|
|
126
|
+
|
|
127
|
+
result = _remove(False)
|
|
128
|
+
|
|
129
|
+
if (
|
|
130
|
+
result.status is Status.NEEDS_CHOICE
|
|
131
|
+
and result.choice is not None
|
|
132
|
+
and result.choice.kind == "confirm_remove_bench"
|
|
133
|
+
):
|
|
134
|
+
confirm_or_exit(
|
|
135
|
+
result.choice.prompt,
|
|
136
|
+
assume_yes=yes,
|
|
137
|
+
refuse_message=(
|
|
138
|
+
"Removing a bench is destructive and no confirmation was given. "
|
|
139
|
+
"Pass --yes to proceed."
|
|
140
|
+
),
|
|
141
|
+
)
|
|
142
|
+
console.print(f"[bold red]Removing bench '{bench_path}'...[/bold red]")
|
|
143
|
+
result = _remove(True)
|
|
144
|
+
|
|
145
|
+
if result.status is Status.NEEDS_CHOICE and result.choice is not None:
|
|
146
|
+
# A resolver fork not handled above (e.g. select_bench, if the cache
|
|
147
|
+
# changed under us): report rather than guess.
|
|
148
|
+
stderr_console.print(f"[bold red]Error:[/bold red] {result.choice.prompt}")
|
|
149
|
+
raise typer.Exit(code=1)
|
|
150
|
+
|
|
151
|
+
outcome = result.data
|
|
152
|
+
assert outcome is not None # OK/WARNING always carries a BenchRemovalOutcome
|
|
153
|
+
|
|
154
|
+
# Removing a bench changes the instance's bench set, so refresh the cache.
|
|
155
|
+
# A failed recache is a warning, not a non-zero exit: the removal already
|
|
156
|
+
# happened, and failing here would make a retry act on a bench that is gone.
|
|
157
|
+
if not cache.recache_project(project_name, verbose=verbose):
|
|
158
|
+
stderr_console.print(
|
|
159
|
+
f"[yellow]Warning:[/yellow] bench removed, but re-caching '{project_name}' failed; "
|
|
160
|
+
"run 'cwcli inspect --update' to refresh."
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
for warning in result.warnings:
|
|
164
|
+
stderr_console.print(f"[yellow]Warning:[/yellow] {warning.text}")
|
|
165
|
+
|
|
166
|
+
if outcome.dir_removed:
|
|
167
|
+
dropped = len(outcome.sites_dropped)
|
|
168
|
+
console.print(
|
|
169
|
+
f"[bold green]✓[/bold green] Bench '{outcome.bench_path}' removed "
|
|
170
|
+
f"({dropped} site(s) backed up and dropped)."
|
|
171
|
+
)
|
|
172
|
+
if outcome.archived_host_paths:
|
|
173
|
+
console.print(
|
|
174
|
+
"[dim]Each dropped site's backup archive was copied to "
|
|
175
|
+
f"{outcome.archived_host_paths[0].rsplit('/', 1)[0]}/.[/dim]"
|
|
176
|
+
)
|
|
177
|
+
else:
|
|
178
|
+
stderr_console.print(
|
|
179
|
+
f"[yellow]Bench '{outcome.bench_path}' was not fully removed; "
|
|
180
|
+
"see the warnings above.[/yellow]"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if not outcome.ok:
|
|
184
|
+
raise typer.Exit(code=1)
|