Skip to content

fix(cli): move docker check off the --dry path#126

Merged
swinney merged 5 commits into
devfrom
fix/issue-112-dry-run-docker-check
Jul 20, 2026
Merged

fix(cli): move docker check off the --dry path#126
swinney merged 5 commits into
devfrom
fix/issue-112-dry-run-docker-check

Conversation

@swinney

@swinney swinney commented Jul 20, 2026

Copy link
Copy Markdown
Member

Summary

  • archi create --dry no longer requires a container runtime — the Docker-availability preflight now runs only after the --dry early return, immediately before the first real container operation.
  • Non-dry create still raises the identical ClickException when Docker is unavailable and --podman was not passed; the --podman short-circuit is unchanged.
  • Narrowed the module-level pytest.mark.skipif in tests/unit/test_cli_create_dev_smoke.py (it blanket-skipped the whole file without docker/podman on PATH) — all five smoke tests now run and pass in a runtime-less shell.
  • Added regression coverage for both halves of the contract: test_dry_run_succeeds_without_docker, test_non_dry_create_requires_docker, test_non_dry_create_with_podman_skips_docker_check.

closes #112

This fixes the environmental flake seen on PR #111 (head eeef7f7a, a docs/shell-only diff), where two gate runs on the same commit disagreed:

Test plan

  • python -m pytest tests/unit/test_cli_create_dev_smoke.py -v — 5 passed, verified in a shell with no docker/podman on PATH
  • python -m pytest tests/unit/ — 906 passed, 1 unrelated pre-existing xfail
  • bash scripts/gate.sh — exit 0, 100% diff coverage on src/cli/cli_main.py
  • Confirmed restart and evaluate expose no --dry option and are untouched

Ralph Loop added 4 commits July 20, 2026 02:03
Add test_dry_run_succeeds_without_docker to test_cli_create_dev_smoke.py:
monkeypatch check_docker_available (as bound in cli_main) to False and
assert `create --dry` exits 0 without the "Docker is not available"
message. Confirmed against the current implementation (with a fake
docker on PATH to clear the module skip): the check at cli_main.py:141
runs before the --dry early return, so the test fails with exit_code=1
and the ClickException in the output. Marked xfail(strict=True) so the
red phase is visible without failing the gate; task 2.1/2.2 relocates
the check after the dry-run summary and removes the marker.

Ralph-Task: 1.2 Run `python -m pytest tests/unit/test_cli_create_dev_smoke.py -v` in a shell with a runtime present and confirm the new test FAILS on the `ClickException`. Record the failure output. Do not write implementation before seeing red.
Relocate the Docker-availability preflight in create() to run only after
the --dry early return, so `archi create --dry` no longer requires a
container runtime. Non-dry create still raises the identical
ClickException, and --podman still short-circuits the check. Pin both
halves of the contract with new regression tests, and narrow the
module-level pytest skip in test_cli_create_dev_smoke.py so the dry-run
smoke tests actually execute in runtime-less environments (loop
container, CI) instead of silently skipping.

Also repoints the stale tasks.md symlink (still pointing at the
already-merged fix-issue-84-strip-orphan-think change) at this change's
tasks.md.

Ralph-Task: 2.1 In `src/cli/cli_main.py`, delete the Docker-availability block at lines 140-146
Ralph-Task: 6.3 Open the PR: gh pr create --repo fasrc/archi --base dev

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 612732e308

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/cli/cli_main.py Outdated
Comment on lines +225 to +226
# Check if Docker is available when --podman is not specified
if not other_flags.get("podman", False) and not check_docker_available():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Run Docker preflight before forced cleanup

For archi create --force with an existing deployment and no Docker, this relocated preflight now runs after handle_existing_deployment() has already executed. In the non-dry force branch that helper constructs DeploymentManager and calls delete_deployment(..., remove_files=True), which removes the deployment directory even if the compose stop fails because Docker is unavailable; the old placement failed before any cleanup. Please keep the Docker check off the --dry path but run it before forced cleanup on the real deployment path.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Confirmed — fixed in 7f4c59b.

Verified the path you describe: with --force and no Docker, handle_existing_deployment() (cli_main.py:148) takes the non-dry force branch and calls delete_deployment(..., remove_files=True). In deployment_manager.py:174-177 the failed stop_deployment() is swallowed with a warning, and shutil.rmtree(deployment_dir) at :201 then runs unconditionally. So the deployment directory is destroyed on exactly the host that cannot bring it back up — a strictly worse outcome than the pre-existing behavior.

Worth noting this also violated the spec delta already in this PR, which says the preflight must fail "before any deployment work is performed" — a forced cleanup is deployment work.

Fix: the preflight now sits before the try block, guarded by not dry (cli_main.py:140-155). That keeps it off the --dry path while restoring it ahead of the forced cleanup. New regression test test_force_create_without_docker_keeps_existing_deployment asserts delete_deployment is never called and the existing directory survives; it failed before the fix and passes now.

Comment on lines +146 to +147
result = runner.invoke(
cli_main.create,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Mock Podman deployment work in the unit test

In environments where Podman is installed, this non-dry --podman test does not stop after proving the Docker check was skipped: it continues into the real create path, which writes deployment files, creates volumes, stages local files, and may run podman compose up. That makes a unit test depend on and mutate the host container runtime; monkeypatch the later deployment/volume operations or force a controlled failure immediately after the preflight instead.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Confirmed, and it was worse than "may" — fixed in 7f4c59b.

I ran that single test on a Podman-equipped host and diffed podman volume ls before/after. It created three real volumes:

archi-data-smoke
archi-pg-smoke
archi-smoke

So the unit gate was mutating host container state on every run. (I removed the strays.)

While confirming, I found a second reason this was unsafe. ARCHI_DIR is resolved into a module-level constant at import time (cli_main.py:38), so the per-test monkeypatch.setenv("ARCHI_DIR", ...) only takes effect for whichever test imports cli_main first. Every later test inherited a stale path — meaning the volume/mkdir work above was not reliably sandboxed to tmp_path and could land in the operator real ~/.archi. It also made tests order-dependent: my new --force regression test passed in a full-file run and failed in isolation, because the polluted ARCHI_DIR meant the deployment directory it created was never the one the CLI looked at.

Two fixes:

  • The --podman test now patches cli_main.TemplateManager to raise a sentinel, halting the run at the first post-preflight step, and asserts check_docker_available is never called — a tighter statement of the actual contract than "the message is absent". Nothing reaches VolumeManager/DeploymentManager.
  • New archi_home fixture sets the env var and patches the module-level ARCHI_DIR, so each test gets a genuinely isolated deployment root. Applied to all non-dry tests in the file.

Verified: the full tests/unit/ suite (908 passed) now leaves podman volume ls byte-identical.

Comment thread src/cli/cli_main.py Outdated
return

# Check if Docker is available when --podman is not specified
if not other_flags.get("podman", False) and not check_docker_available():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Re-raise Docker preflight under verbose logging

When non-dry create is run with --verbosity 4 and Docker is unavailable, this moved ClickException is now caught by the broad except below; that verbose branch only prints the traceback and then returns, so Click treats the command as successful. Before this change the Docker check was outside the try, so the same preflight exited non-zero regardless of verbosity; keep it outside the swallowing handler or re-raise after printing.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Confirmed — fixed in 7f4c59b.

Reproduced before fixing: archi create --verbosity 4 with no Docker exited 0 while printing the ClickException as a traceback. The except Exception handler at cli_main.py:268-272 only re-raises when verbosity < 4; at 4 it prints and falls through, so Click sees a clean return.

Fix is the one you suggested — the preflight is back outside the swallowing handler (cli_main.py:140-155), which also resolves the P1 above, since both regressions came from the same relocation into the try. Regression test test_non_dry_create_requires_docker_under_verbose_logging pins the exit code at --verbosity 4.

I deliberately left the broad handler itself alone: swallowing at verbosity 4 is pre-existing behavior across other commands too, and widening this PR to change it would exceed the scope of the --dry fix. Happy to file a follow-up issue if you think that handler is worth revisiting on its own.

…wallowing handler

the preflight moved inside the try block, which introduced two regressions:
--force tore down an existing deployment before discovering docker was
missing, and --verbosity 4 swallowed the ClickException and exited 0.

also make the create smoke tests hermetic: the --podman test was reaching
VolumeManager and creating real volumes on the host, and ARCHI_DIR is a
module-level constant so setenv alone left tests leaking into each other.
@swinney
swinney merged commit 9ec8be6 into dev Jul 20, 2026
6 checks passed
@swinney
swinney deleted the fix/issue-112-dry-run-docker-check branch July 20, 2026 13:15
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.

Move create's Docker-availability check off the --dry path (flaky gate on Docker-less runners)

1 participant