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)
|
||||
|
||||
env_file = stage_dir / "agent.env"
|
||||
args_file = stage_dir / "docker-args"
|
||||
prompt_file = stage_dir / "prompt.txt"
|
||||
prompt_file.write_text("")
|
||||
prompt_file.chmod(0o600)
|
||||
@@ -124,7 +123,7 @@ class DockerBottleBackend(BottleBackend["DockerBottlePlan", "DockerBottleCleanup
|
||||
# so the value never lands on argv or in env_file.
|
||||
os.environ["CLAUDE_CODE_OAUTH_TOKEN"] = os.environ["CLAUDE_BOTTLE_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)
|
||||
|
||||
allowlist_summary = pipelock.pipelock_allowlist_summary(bottle)
|
||||
@@ -140,21 +139,18 @@ class DockerBottleBackend(BottleBackend["DockerBottlePlan", "DockerBottleCleanup
|
||||
derived_image=derived_image,
|
||||
runtime_image=runtime_image,
|
||||
env_file=env_file,
|
||||
args_file=args_file,
|
||||
forwarded_env=tuple(resolved.forwarded),
|
||||
prompt_file=prompt_file,
|
||||
proxy_plan=proxy_plan,
|
||||
allowlist_summary=allowlist_summary,
|
||||
use_runsc=use_runsc,
|
||||
)
|
||||
|
||||
def _write_env_files(
|
||||
self, resolved: ResolvedEnv, env_file: Path, args_file: Path
|
||||
) -> None:
|
||||
"""Serialize a ResolvedEnv into the two on-disk formats the launch
|
||||
step consumes: `--env-file` syntax for literals (NAME=VALUE per
|
||||
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)."""
|
||||
def _write_env_file(self, resolved: ResolvedEnv, env_file: Path) -> None:
|
||||
"""Serialize the literal portion of a ResolvedEnv into docker's
|
||||
`--env-file` syntax (NAME=VALUE per line, mode 600 since the
|
||||
file may carry verbatim values from the manifest). Forwarded
|
||||
names ride on the plan as a structured tuple instead."""
|
||||
env_lines: list[str] = []
|
||||
for name, value in resolved.literals.items():
|
||||
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.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
|
||||
def launch(self, plan: DockerBottlePlan) -> Iterator[DockerBottle]:
|
||||
"""Build, launch, and provision a Docker bottle. Teardown on exit."""
|
||||
@@ -229,20 +222,8 @@ class DockerBottleBackend(BottleBackend["DockerBottlePlan", "DockerBottleCleanup
|
||||
docker_args.extend(["--runtime", "runsc"])
|
||||
if plan.env_file.stat().st_size > 0:
|
||||
docker_args.extend(["--env-file", str(plan.env_file)])
|
||||
|
||||
# ARGS_FILE pairs (-e, NAME) line-by-line.
|
||||
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])
|
||||
for name in plan.forwarded_env:
|
||||
docker_args.extend(["-e", name])
|
||||
|
||||
docker_args.extend([plan.runtime_image, "sleep", "infinity"])
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ class DockerBottlePlan(BottlePlan):
|
||||
image: str
|
||||
derived_image: str # "" -> no derived image
|
||||
runtime_image: str # image actually launched (derived or base)
|
||||
env_file: Path
|
||||
args_file: Path
|
||||
env_file: Path # docker --env-file: NAME=VALUE literals
|
||||
forwarded_env: tuple[str, ...] # docker -e <NAME>: forwarded by-name
|
||||
prompt_file: Path
|
||||
proxy_plan: PipelockProxyPlan
|
||||
allowlist_summary: str
|
||||
|
||||
Reference in New Issue
Block a user