From ec38458c9457fb200666f654f6c0699592259d94 Mon Sep 17 00:00:00 2001 From: codex Date: Mon, 27 Jul 2026 17:53:48 +0000 Subject: [PATCH] fix(firecracker): include copied context directories --- bot_bottle/backend/firecracker/image_builder.py | 8 ++++++-- tests/unit/test_firecracker_image_builder.py | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/bot_bottle/backend/firecracker/image_builder.py b/bot_bottle/backend/firecracker/image_builder.py index cf9585e2..57615453 100644 --- a/bot_bottle/backend/firecracker/image_builder.py +++ b/bot_bottle/backend/firecracker/image_builder.py @@ -90,7 +90,10 @@ def _context_files(dockerfile: Path) -> list[tuple[str, Path]]: matches = [p for p in root.glob(src) if p.is_file()] else: candidate = root / src - matches = [candidate] if candidate.is_file() else [] + if candidate.is_dir(): + matches = [path for path in candidate.rglob("*") if path.is_file()] + else: + matches = [candidate] if candidate.is_file() else [] for path in matches: resolved[str(path.relative_to(root))] = path return sorted(resolved.items()) @@ -269,7 +272,8 @@ def _send_build_context(private_key: Path, guest_ip: str, dockerfile: Path, ctx: root = resources.build_root() rels = [rel for rel, _ in files] tar = subprocess.Popen( - ["tar", "-C", str(root), "-cf", "-", *rels], stdout=subprocess.PIPE, + ["tar", "-C", str(root), "-cf", "-", "--", *rels], + stdout=subprocess.PIPE, ) try: proc = subprocess.run( diff --git a/tests/unit/test_firecracker_image_builder.py b/tests/unit/test_firecracker_image_builder.py index 4bcb5c88..b3087fbd 100644 --- a/tests/unit/test_firecracker_image_builder.py +++ b/tests/unit/test_firecracker_image_builder.py @@ -137,6 +137,20 @@ class TestBuildContext(unittest.TestCase): ["bot_bottle/contrib/codex/codex-package_SHA256SUMS"], ) + def test_context_files_recurses_into_directory_sources(self): + nested = self.root / "assets" / "nested" + nested.mkdir(parents=True) + (self.root / "assets" / "one").write_text("one") + (nested / "two").write_text("two") + df = self.root / "Directory" + df.write_text("FROM x\nCOPY assets /opt/assets\n") + with self._patch_root(): + files = image_builder._context_files(df) + self.assertEqual( + [rel for rel, _ in files], + ["assets/nested/two", "assets/one"], + ) + def test_context_files_drops_missing_and_traversal(self): df = self.root / "Bad" df.write_text("FROM x\nCOPY ../escape /d\nCOPY does/not/exist /d\n") @@ -172,6 +186,7 @@ class TestBuildContext(unittest.TestCase): Path("/k"), "10.0.0.1", self.dockerfile, "/tmp/c") tar_argv = popen.call_args.args[0] self.assertEqual(tar_argv[:3], ["tar", "-C", str(self.root)]) + self.assertIn("--", tar_argv) self.assertIn( "bot_bottle/contrib/codex/codex-package_SHA256SUMS", tar_argv) self.assertIn("tar -C /tmp/c/ctx -xf -", run.call_args.args[0][-1]) @@ -200,7 +215,6 @@ class TestSmokeTest(unittest.TestCase): ssh.assert_not_called() def test_failed_smoke_dies(self): - import subprocess result = subprocess.CompletedProcess([], 1, stdout="broken", stderr="") with patch.object(image_builder, "_ssh", return_value=result), \ self.assertRaises(SystemExit):