diff --git a/deploy/fleet-ctl.sh b/deploy/fleet-ctl.sh index 88136df..4139a4c 100755 --- a/deploy/fleet-ctl.sh +++ b/deploy/fleet-ctl.sh @@ -84,7 +84,10 @@ on_node() { # this script, run on the node over ssh ssh "$NODE_SSH" "FLEET_CTL_ON_NODE=1 FLEET_PREFIX=$NODE_PREFIX bash -s -- $flag $(printf '%q ' "$@")" < "$SELF" } -compose() { run docker compose --env-file "$PREFIX/compose.env" -p zkm-fleet -f "$PREFIX/deploy/docker-compose.yml" "$@"; } +# The release's compose file, then this site's changes to it, if any: upgrades replace only the release's. +COMPOSE=(docker compose --env-file "$PREFIX/compose.env" -p zkm-fleet -f "$PREFIX/deploy/docker-compose.yml") +[ ! -f "$PREFIX/deploy/docker-compose.override.yml" ] || COMPOSE+=(-f "$PREFIX/deploy/docker-compose.override.yml") +compose() { run "${COMPOSE[@]}" "$@"; } # Naming a profiled service starts it, so recreate MCP only where install.sh enabled it. control_services() { echo scheduler; grep -q '^COMPOSE_PROFILES=.*mcp' "$PREFIX/compose.env" 2>/dev/null && echo mcp || true; } scheduler_healthy() { @@ -228,7 +231,7 @@ status() { return fi echo "== control host: healthz $(curl -fs http://127.0.0.1:9191/healthz || echo FAILING)" - docker compose --env-file "$PREFIX/compose.env" -p zkm-fleet -f "$PREFIX/deploy/docker-compose.yml" ps || true + "${COMPOSE[@]}" ps || true "$PREFIX/bin/fleetctl" node || true "$PREFIX/bin/fleetctl" app || true if [ -n "$NODE_SSH" ]; then on_node status; fi diff --git a/deploy/tests/test_fleet_ctl.py b/deploy/tests/test_fleet_ctl.py new file mode 100644 index 0000000..997a608 --- /dev/null +++ b/deploy/tests/test_fleet_ctl.py @@ -0,0 +1,43 @@ +"""Exercise fleet-ctl.sh's compose calls with stand-in docker, curl and fleetctl.""" +import os +from pathlib import Path +import subprocess + +ROOT = Path(__file__).resolve().parents[2] + + +def executable(path, body): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("#!/bin/bash\n" + body) + path.chmod(0o755) + + +def status(tmp_path): + """`fleet-ctl.sh status` on a control prefix; returns the docker command lines it ran.""" + prefix, commands, log = tmp_path / "fleet", tmp_path / "commands", tmp_path / "docker.log" + (prefix / "deploy").mkdir(parents=True, exist_ok=True) + (prefix / "deploy/docker-compose.yml").write_text("services: {}\n") + (prefix / "compose.env").write_text("") + executable(commands / "docker", f'echo "$*" >> {log}\n') + executable(commands / "curl", "exit 1\n") + executable(prefix / "bin/fleetctl", "exit 0\n") + environment = {**os.environ, "PATH": f"{commands}:{os.environ['PATH']}", "FLEET_PREFIX": str(prefix), + "FLEET_CTL_ENV": str(tmp_path / "none.env"), "FLEET_NODE_SSH": ""} + result = subprocess.run(["bash", str(ROOT / "deploy/fleet-ctl.sh"), "status"], + env=environment, capture_output=True, text=True, timeout=20) + assert result.returncode == 0, result.stderr + return prefix, log.read_text().splitlines() + + +def test_compose_uses_the_release_file_alone(tmp_path): + prefix, calls = status(tmp_path) + assert calls == [f"compose --env-file {prefix}/compose.env -p zkm-fleet -f {prefix}/deploy/docker-compose.yml ps"] + + +def test_compose_applies_the_site_override_after_the_release_file(tmp_path): + override = tmp_path / "fleet/deploy/docker-compose.override.yml" + override.parent.mkdir(parents=True) + override.write_text("services: {}\n") + prefix, calls = status(tmp_path) + assert calls == [f"compose --env-file {prefix}/compose.env -p zkm-fleet -f {prefix}/deploy/docker-compose.yml " + f"-f {override} ps"] diff --git a/docs/maintenance.md b/docs/maintenance.md index 5cf4227..0994351 100644 --- a/docs/maintenance.md +++ b/docs/maintenance.md @@ -210,7 +210,10 @@ deploy/fleet-ctl.sh app cleanup APP-0 # leftovers of a failed app sta An upgrade copies what it replaces to `backup/` first, then waits for the scheduler's health check or the node's reconnection. Upgrade the scheduler and nodes together when a release changes the protocol. The scheduler and MCP are managed with Docker Compose; the -node uses `sudo systemctl`, and `FLEET_NODE_UNIT` names a non-default unit. +node uses `sudo systemctl`, and `FLEET_NODE_UNIT` names a non-default unit. Keep a site's +changes to the release's compose file, such as another gateway or an existing Postgres +volume, in `deploy/docker-compose.override.yml`: an upgrade replaces only +`docker-compose.yml`, and every compose call applies the override after it. A third `node upgrade` argument also installs `fleet-proof-app` and restarts the app endpoint units listed in `FLEET_PROOF_APP_UNITS`.