From 260824035b90b2c0e3dc0a7a80def812e3ec86a9 Mon Sep 17 00:00:00 2001 From: Tomasz Mazur Date: Sun, 30 Aug 2026 20:53:41 +0200 Subject: [PATCH] fix: honor --dry-run everywhere it can create or delete --dry-run was gated inside cmd_up only. The bare `orbx`, `orbx shell` and `orbx run` all parse the flag, then call ensure_up with nothing checking it -- so they create and fully provision a machine. `orbx down --dry-run` deletes one. Only `orbx up --dry-run` did what --help promises ("Print the orb command that would run, then exit"). Caught by running `orbx --dry-run --name testname123` while inspecting behavior, per AGENTS.md, and finding a provisioned VM afterwards. The orb stub confirms it: list -q create -c .../templates/default.yaml --isolated --mount ... testname123 -m testname123 -u root cat /etc/sandbox-status <- already polling That matters most for agents: AGENTS.md tells them --dry-run inspects behavior "without touching OrbStack", so following the docs silently leaves machines behind. The gate moves into orbx::dry_run_plan, called by every path that would mutate OrbStack. It deliberately probes nothing -- no `orb list`, no machine state -- so a dry run cannot touch OrbStack even to look, which is what the existing `[ ! -s "$ORB_STUB_LOG" ]` assertion pins. down prints the `orb delete -f ` it would have run. Verified against the real CLI: three dry runs across the bare form, run, and down leave `orb list` empty. --- AGENTS.md | 5 ++++- bin/orbx | 20 ++++++++++++++++---- test/create.bats | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f33abfc..9f560cf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -97,4 +97,7 @@ the `TAP_TOKEN` secret). Bumping the version means editing it in **both** - Subcommand flags starting with `-` need a `--` separator, e.g. `orbx run -- bin/rails -e production` (see `orbx::parse_flags`). - `--dry-run` prints the `orb create` command and exits — use it to inspect - behavior without touching OrbStack. + behavior without touching OrbStack. Honored by every command that would + mutate OrbStack (`up`, `shell`, `run`, and the bare form); `down --dry-run` + prints the `orb delete` it would run. The gate is `orbx::dry_run_plan`, which + deliberately probes nothing, so a dry run never even calls `orb list`. diff --git a/bin/orbx b/bin/orbx index d59ced5..21c1cc4 100755 --- a/bin/orbx +++ b/bin/orbx @@ -282,13 +282,19 @@ orbx::main() { esac } +# --dry-run for every path that would bring a machine up. Deliberately probes +# nothing -- no `orb list`, no machine state -- so it cannot touch OrbStack at +# all. Callers stop here: `if orbx::dry_run_plan; then return 0; fi`. +orbx::dry_run_plan() { + [[ "$ORBX_DRYRUN" == "1" ]] || return 1 + printf '%s ' "${ORBX_CREATE_CMD[@]}"; printf '\n' + return 0 +} + orbx::cmd_up() { orbx::resolve_context orbx::build_create_cmd - if [[ "$ORBX_DRYRUN" == "1" ]]; then - printf '%s ' "${ORBX_CREATE_CMD[@]}"; printf '\n' - return 0 - fi + if orbx::dry_run_plan; then return 0; fi orbx::ensure_up # implemented in Task 10 } @@ -356,6 +362,7 @@ orbx::host_terminfo() { orbx::cmd_shell() { orbx::resolve_context orbx::build_create_cmd + if orbx::dry_run_plan; then return 0; fi orbx::ensure_up local ti; ti="$(orbx::host_terminfo)" if [[ "$ORBX_MOUNT_ON" == "true" ]]; then @@ -374,6 +381,7 @@ orbx::cmd_run() { [[ $# -gt 0 ]] || orbx::die "run needs a command (e.g. orbx run bin/dev)" orbx::resolve_context orbx::build_create_cmd + if orbx::dry_run_plan; then return 0; fi orbx::ensure_up local ti; ti="$(orbx::host_terminfo)" if [[ "$ORBX_MOUNT_ON" == "true" ]]; then @@ -391,6 +399,10 @@ orbx::cmd_logs() { orbx::resolve_context; orb -m "$ORBX_NAME" tail -f /var/log/ orbx::cmd_down() { orbx::resolve_context + if [[ "$ORBX_DRYRUN" == "1" ]]; then + printf 'orb delete -f %s\n' "$ORBX_NAME" + return 0 + fi if [[ "$ORBX_YES" != "1" ]]; then [[ -t 0 ]] || orbx::die "refusing to delete non-interactively; pass --yes" printf 'Delete sandbox %q? Data inside the VM is lost (your folder is safe). [y/N] ' "$ORBX_NAME" >&2 diff --git a/test/create.bats b/test/create.bats index 6d24da2..c8cac2a 100644 --- a/test/create.bats +++ b/test/create.bats @@ -39,3 +39,48 @@ load helpers/test_helper [[ "$output" == *"--mount $PROJECT_DIR:/home/tomasz/code/korelo"* ]] [[ "$output" == *"--mount /host/data:/vm/data"* ]] } + +# --dry-run used to be honored only by `orbx up`: the bare command, `run` and +# `shell` parsed the flag and then created and provisioned a machine anyway, +# and `down` deleted one. The gate lives in orbx::dry_run_plan now, so every +# path that mutates OrbStack stops before it does. + +@test "bare orbx --dry-run creates nothing" { + orbx_source + mkdir -p "$HOME/.orbx/templates"; touch "$HOME/.orbx/templates/default.yaml" + cd "$PROJECT_DIR" + run orbx_run --dry-run + [ "$status" -eq 0 ] + [[ "$output" == *"orb create -c"* ]] + [ ! -s "$ORB_STUB_LOG" ] +} + +@test "orbx run --dry-run creates nothing and runs no command" { + orbx_source + mkdir -p "$HOME/.orbx/templates"; touch "$HOME/.orbx/templates/default.yaml" + cd "$PROJECT_DIR" + run orbx_run run --dry-run -- bin/rails -v + [ "$status" -eq 0 ] + [[ "$output" == *"orb create -c"* ]] + [ ! -s "$ORB_STUB_LOG" ] +} + +@test "orbx shell --dry-run creates nothing" { + orbx_source + mkdir -p "$HOME/.orbx/templates"; touch "$HOME/.orbx/templates/default.yaml" + cd "$PROJECT_DIR" + run orbx_run shell --dry-run + [ "$status" -eq 0 ] + [[ "$output" == *"orb create -c"* ]] + [ ! -s "$ORB_STUB_LOG" ] +} + +@test "orbx down --dry-run deletes nothing" { + orbx_source + mkdir -p "$HOME/.orbx/templates"; touch "$HOME/.orbx/templates/default.yaml" + cd "$PROJECT_DIR" + run orbx_run down --dry-run --yes + [ "$status" -eq 0 ] + [[ "$output" == *"orb delete -f korelo"* ]] + [ ! -s "$ORB_STUB_LOG" ] +}