refactor(docker): hand forwarded env names through the plan, not a file
Previously prepare wrote two on-disk artifacts that launch consumed: agent.env (NAME=VALUE) and docker-args (paired -e\nNAME\n lines), with launch parsing the second back into argv. Docker requires the literals file on disk for --env-file, but the args-file round-trip was a pure serialize/deserialize trip with hand-rolled line pairing logic. Drop docker-args entirely. Pass forwarded names as a structured tuple[str, ...] field on DockerBottlePlan; launch iterates it directly to extend docker_args. _write_env_files becomes _write_env_file (only the literals file remains).
This commit is contained in:
@@ -112,7 +112,6 @@ class DockerBottleBackend(BottleBackend["DockerBottlePlan", "DockerBottleCleanup
|
|||||||
self.validate_ssh_entries(bottle.ssh)
|
self.validate_ssh_entries(bottle.ssh)
|
||||||
|
|
||||||
env_file = stage_dir / "agent.env"
|
env_file = stage_dir / "agent.env"
|
||||||
args_file = stage_dir / "docker-args"
|
|
||||||
prompt_file = stage_dir / "prompt.txt"
|
prompt_file = stage_dir / "prompt.txt"
|
||||||
prompt_file.write_text("")
|
prompt_file.write_text("")
|
||||||
prompt_file.chmod(0o600)
|
prompt_file.chmod(0o600)
|
||||||
@@ -124,7 +123,7 @@ class DockerBottleBackend(BottleBackend["DockerBottlePlan", "DockerBottleCleanup
|
|||||||
# so the value never lands on argv or in env_file.
|
# so the value never lands on argv or in env_file.
|
||||||
os.environ["CLAUDE_CODE_OAUTH_TOKEN"] = os.environ["CLAUDE_BOTTLE_OAUTH_TOKEN"]
|
os.environ["CLAUDE_CODE_OAUTH_TOKEN"] = os.environ["CLAUDE_BOTTLE_OAUTH_TOKEN"]
|
||||||
resolved.forwarded.append("CLAUDE_CODE_OAUTH_TOKEN")
|
resolved.forwarded.append("CLAUDE_CODE_OAUTH_TOKEN")
|
||||||
self._write_env_files(resolved, env_file, args_file)
|
self._write_env_file(resolved, env_file)
|
||||||
prompt_file.write_text(agent.prompt)
|
prompt_file.write_text(agent.prompt)
|
||||||
|
|
||||||
allowlist_summary = pipelock.pipelock_allowlist_summary(bottle)
|
allowlist_summary = pipelock.pipelock_allowlist_summary(bottle)
|
||||||
@@ -140,21 +139,18 @@ class DockerBottleBackend(BottleBackend["DockerBottlePlan", "DockerBottleCleanup
|
|||||||
derived_image=derived_image,
|
derived_image=derived_image,
|
||||||
runtime_image=runtime_image,
|
runtime_image=runtime_image,
|
||||||
env_file=env_file,
|
env_file=env_file,
|
||||||
args_file=args_file,
|
forwarded_env=tuple(resolved.forwarded),
|
||||||
prompt_file=prompt_file,
|
prompt_file=prompt_file,
|
||||||
proxy_plan=proxy_plan,
|
proxy_plan=proxy_plan,
|
||||||
allowlist_summary=allowlist_summary,
|
allowlist_summary=allowlist_summary,
|
||||||
use_runsc=use_runsc,
|
use_runsc=use_runsc,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _write_env_files(
|
def _write_env_file(self, resolved: ResolvedEnv, env_file: Path) -> None:
|
||||||
self, resolved: ResolvedEnv, env_file: Path, args_file: Path
|
"""Serialize the literal portion of a ResolvedEnv into docker's
|
||||||
) -> None:
|
`--env-file` syntax (NAME=VALUE per line, mode 600 since the
|
||||||
"""Serialize a ResolvedEnv into the two on-disk formats the launch
|
file may carry verbatim values from the manifest). Forwarded
|
||||||
step consumes: `--env-file` syntax for literals (NAME=VALUE per
|
names ride on the plan as a structured tuple instead."""
|
||||||
line) and a paired `-e\\nNAME\\n` stream for forwarded names.
|
|
||||||
Both files are created here (mode 600 on the literals file,
|
|
||||||
which may carry sensitive verbatim values from the manifest)."""
|
|
||||||
env_lines: list[str] = []
|
env_lines: list[str] = []
|
||||||
for name, value in resolved.literals.items():
|
for name, value in resolved.literals.items():
|
||||||
if "\n" in value:
|
if "\n" in value:
|
||||||
@@ -166,9 +162,6 @@ class DockerBottleBackend(BottleBackend["DockerBottlePlan", "DockerBottleCleanup
|
|||||||
env_file.write_text("\n".join(env_lines) + ("\n" if env_lines else ""))
|
env_file.write_text("\n".join(env_lines) + ("\n" if env_lines else ""))
|
||||||
env_file.chmod(0o600)
|
env_file.chmod(0o600)
|
||||||
|
|
||||||
args_lines = [f"-e\n{name}" for name in resolved.forwarded]
|
|
||||||
args_file.write_text("\n".join(args_lines) + ("\n" if args_lines else ""))
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def launch(self, plan: DockerBottlePlan) -> Iterator[DockerBottle]:
|
def launch(self, plan: DockerBottlePlan) -> Iterator[DockerBottle]:
|
||||||
"""Build, launch, and provision a Docker bottle. Teardown on exit."""
|
"""Build, launch, and provision a Docker bottle. Teardown on exit."""
|
||||||
@@ -229,20 +222,8 @@ class DockerBottleBackend(BottleBackend["DockerBottlePlan", "DockerBottleCleanup
|
|||||||
docker_args.extend(["--runtime", "runsc"])
|
docker_args.extend(["--runtime", "runsc"])
|
||||||
if plan.env_file.stat().st_size > 0:
|
if plan.env_file.stat().st_size > 0:
|
||||||
docker_args.extend(["--env-file", str(plan.env_file)])
|
docker_args.extend(["--env-file", str(plan.env_file)])
|
||||||
|
for name in plan.forwarded_env:
|
||||||
# ARGS_FILE pairs (-e, NAME) line-by-line.
|
docker_args.extend(["-e", name])
|
||||||
args_lines = plan.args_file.read_text().splitlines()
|
|
||||||
i = 0
|
|
||||||
while i < len(args_lines):
|
|
||||||
flag = args_lines[i]
|
|
||||||
i += 1
|
|
||||||
if not flag:
|
|
||||||
continue
|
|
||||||
if i >= len(args_lines):
|
|
||||||
break
|
|
||||||
vname = args_lines[i]
|
|
||||||
i += 1
|
|
||||||
docker_args.extend([flag, vname])
|
|
||||||
|
|
||||||
docker_args.extend([plan.runtime_image, "sleep", "infinity"])
|
docker_args.extend([plan.runtime_image, "sleep", "infinity"])
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ class DockerBottlePlan(BottlePlan):
|
|||||||
image: str
|
image: str
|
||||||
derived_image: str # "" -> no derived image
|
derived_image: str # "" -> no derived image
|
||||||
runtime_image: str # image actually launched (derived or base)
|
runtime_image: str # image actually launched (derived or base)
|
||||||
env_file: Path
|
env_file: Path # docker --env-file: NAME=VALUE literals
|
||||||
args_file: Path
|
forwarded_env: tuple[str, ...] # docker -e <NAME>: forwarded by-name
|
||||||
prompt_file: Path
|
prompt_file: Path
|
||||||
proxy_plan: PipelockProxyPlan
|
proxy_plan: PipelockProxyPlan
|
||||||
allowlist_summary: str
|
allowlist_summary: str
|
||||||
|
|||||||
Reference in New Issue
Block a user