diff --git a/app/orchestrator.py b/app/orchestrator.py index 4c096f99..c1e9b6c4 100644 --- a/app/orchestrator.py +++ b/app/orchestrator.py @@ -1907,10 +1907,17 @@ def _check_proxy_binds(working_dir: str, extra_paths: list[str]) -> None: """Refuse, with the line to paste, an update the proxy would not let bind its folders.""" compose_dir = os.getenv("CASHPILOT_COMPOSE_DIR", "").strip().rstrip("/") if compose_dir != working_dir.rstrip("/"): + # working_dir is the path Compose itself ran in. Under a webhook or a + # deployer container (Portainer and the like) that path is inside the + # deployer and may not exist on this host; the helper binds it as is, + # so no other value would work either: the deployer has to update. raise SelfUpdateUnavailable( - "The update helper needs your compose folder, and the Docker socket proxy lets " - "containers bind only the folders it was told about. Add this line to the .env " - f"next to your compose file, then run docker compose up -d once: " + f"The update helper needs your compose folder, which Docker Compose recorded as " + f"{working_dir}, and the Docker socket proxy lets containers bind only the folders " + "it was told about. If Compose runs inside another container for you (a webhook, " + "Portainer or another deployer), that path is inside it, not on this host: update " + "the stack from your deployer instead. Otherwise add this line to the .env next to " + "your compose file, then run docker compose up -d once: " f"{COMPOSE_DIR_ENV}={working_dir}" ) roots = _proxy_bind_roots() diff --git a/tests/test_socket_proxy.py b/tests/test_socket_proxy.py index b0e24499..ba45cd62 100644 --- a/tests/test_socket_proxy.py +++ b/tests/test_socket_proxy.py @@ -611,6 +611,25 @@ def test_proxied_a_compose_folder_the_proxy_does_not_know_is_refused_with_the_li client.containers.run.assert_not_called() +def test_proxied_under_a_deployer_container_the_refusal_does_not_pass_its_path_off_as_the_hosts(monkeypatch): + """Compose run by a webhook or Portainer records the deployer's own path + (/data/compose/12), which need not exist on the host. The advice must say + so and send the operator to the deployer, not only to that path.""" + _proxied_env(monkeypatch, compose_dir="") + labels = {**tuf._COMPOSE_LABELS, "com.docker.compose.project.working_dir": "/data/compose/12"} + client = tuf._fake_docker(me=tuf._fake_container(labels=labels)) + tuf._wire_runs(client) + with ( + patch.object(orchestrator, "_get_client", return_value=client), + pytest.raises(orchestrator.SelfUpdateUnavailable) as refused, + ): + orchestrator.spawn_self_update() + message = str(refused.value) + assert "recorded as /data/compose/12" in message + assert "not on this host" in message and "update the stack from your deployer" in message + client.containers.run.assert_not_called() + + def test_proxied_a_trailing_slash_is_the_same_folder(monkeypatch): _proxied_env(monkeypatch, compose_dir="/srv/cashpilot/") client, result = _run_update()