Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions app/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
19 changes: 19 additions & 0 deletions tests/test_socket_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Loading