mirror of
https://scm.tikali.ai/tikali/applications/monky/monky-deployd.git
synced 2026-09-18 05:36:15 +00:00
feat: pull private images without a hand docker login; surface the pull error
Design merged first: monky-design-docs !225 (doc 24 §4a). Pairs with monky-tenancy!40, which copies the estate-wide read-only Harbor robot into each environment's own prefix and marks the manifest entry `use: registry-auth`. - That entry is not an env var (it would otherwise land in .env and therefore in every container's environment). The agent parses it — JSON, or `username:password` with the new `registry_host` — and writes `<state_dir>/docker/config.json` 0600 in a directory it owns, with an explicit DOCKER_CONFIG pointing the docker CLI at it. The unit runs as monky-deployd, so a `docker login` by a human or by root is invisible to the agent: that is what made env-dev-01 look like it had no credential at all after the operator had just logged in. - `compose pull` failures now carry the registry's own message ("no basic auth credentials", "manifest unknown", DNS) into the journal and the report instead of `rc=1`. - Tests: both credential shapes, the refusals, 0600/0700 modes, idempotent rewrite, and that the runner never silently falls back to a human's $HOME. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KLB7jieMNRkTsJ2epr4Ds1
This commit is contained in:
@@ -41,9 +41,13 @@ class Container:
|
||||
|
||||
|
||||
class Docker:
|
||||
def __init__(self, docker_bin: str = "docker", timeout_s: int = 600):
|
||||
def __init__(self, docker_bin: str = "docker", timeout_s: int = 600, docker_config: str | None = None):
|
||||
self.bin = docker_bin
|
||||
self.timeout_s = timeout_s
|
||||
# doc 24 §4a: registry credentials live in a directory the AGENT owns, named explicitly
|
||||
# rather than inherited from $HOME. The unit runs as `monky-deployd`, so a `docker login`
|
||||
# by a human or by root is invisible here — which is exactly what cost env-dev-01 an hour.
|
||||
self.docker_config = docker_config
|
||||
|
||||
def available(self) -> bool:
|
||||
return shutil.which(self.bin) is not None
|
||||
@@ -59,7 +63,7 @@ class Docker:
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout or self.timeout_s,
|
||||
env={**os.environ, "COMPOSE_INTERACTIVE_NO_CLI": "1"},
|
||||
env=self._env(),
|
||||
)
|
||||
except FileNotFoundError as exc:
|
||||
raise ComposeError(what, 127, f"{self.bin} not found") from exc
|
||||
@@ -70,6 +74,12 @@ class Docker:
|
||||
raise ComposeError(what, p.returncode, out.strip())
|
||||
return out
|
||||
|
||||
def _env(self) -> dict:
|
||||
env = {**os.environ, "COMPOSE_INTERACTIVE_NO_CLI": "1"}
|
||||
if self.docker_config:
|
||||
env["DOCKER_CONFIG"] = self.docker_config
|
||||
return env
|
||||
|
||||
# -- facts ------------------------------------------------------------------------------------
|
||||
def version(self) -> str | None:
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user