fix(git): mount git-gate known hosts
test / unit (push) Successful in 36s
test / integration (push) Successful in 57s
test / unit (pull_request) Successful in 32s
test / integration (pull_request) Successful in 59s

This commit was merged in pull request #91.
This commit is contained in:
2026-05-28 19:59:37 -04:00
parent f86349ca92
commit c854db87c6
5 changed files with 67 additions and 1 deletions
+5
View File
@@ -212,6 +212,11 @@ def _sidecar_bundle_service(plan: DockerBottlePlan) -> dict[str, Any]:
keypath,
f"{GIT_GATE_CREDS_DIR_IN_CONTAINER}/{u.name}-key",
))
if u.known_hosts_file:
volumes.append(_bind(
u.known_hosts_file,
f"{GIT_GATE_CREDS_DIR_IN_CONTAINER}/{u.name}-known_hosts",
))
extra_map = git_gate_aggregate_extra_hosts(gp.upstreams)
extra_hosts = [f"{host}:{ip}" for host, ip in sorted(extra_map.items())]
@@ -366,6 +366,12 @@ def _bundle_launch_spec(
f"{GIT_GATE_CREDS_DIR_IN_CONTAINER}/{u.name}-key",
True,
))
if u.known_hosts_file:
volumes.append((
str(u.known_hosts_file),
f"{GIT_GATE_CREDS_DIR_IN_CONTAINER}/{u.name}-known_hosts",
True,
))
# --- supervise --------------------------------------------
sp = plan.supervise_plan
+25 -1
View File
@@ -71,6 +71,7 @@ class GitGateUpstream:
upstream_port: str
identity_file: str
known_host_key: str
known_hosts_file: Path = Path()
extra_hosts: Mapping[str, str] = field(default_factory=_empty_str_map)
@@ -408,10 +409,33 @@ class GitGate(ABC):
# not via `sh`, so the script needs the x bit. docker cp
# preserves source mode into the container.
access_hook.chmod(0o700)
upstreams_with_files: list[GitGateUpstream] = []
for u in upstreams:
known_hosts_file = Path()
if u.known_host_key:
known_hosts_file = stage_dir / f"{u.name}-known_hosts"
known_hosts_file.write_text(
git_gate_known_hosts_line(
u.upstream_host, u.upstream_port, u.known_host_key,
)
)
known_hosts_file.chmod(0o600)
upstreams_with_files.append(
GitGateUpstream(
name=u.name,
upstream_url=u.upstream_url,
upstream_host=u.upstream_host,
upstream_port=u.upstream_port,
identity_file=u.identity_file,
known_host_key=u.known_host_key,
known_hosts_file=known_hosts_file,
extra_hosts=dict(u.extra_hosts),
)
)
return GitGatePlan(
slug=slug,
entrypoint_script=entrypoint,
hook_script=hook,
access_hook_script=access_hook,
upstreams=upstreams,
upstreams=tuple(upstreams_with_files),
)
+2
View File
@@ -148,6 +148,7 @@ def _plan(
upstream_port="22",
identity_file="/etc/hostname",
known_host_key="",
known_hosts_file=STATE / "git-gate" / "upstream-known_hosts",
extra_hosts={"example.com": "10.0.0.1"},
),)
routes: tuple[EgressRoute, ...] = ()
@@ -408,6 +409,7 @@ class TestSidecarBundleShape(unittest.TestCase):
self.assertIn("/etc/pipelock.yaml", targets)
self.assertIn("/etc/egress/routes.yaml", targets)
self.assertIn("/git-gate-entrypoint.sh", targets)
self.assertIn("/git-gate/creds/upstream-known_hosts", targets)
# supervise queue dir target = QUEUE_DIR_IN_CONTAINER
self.assertTrue(any("supervise/queue" in t or t.startswith("/run/supervise")
for t in targets))
+29
View File
@@ -257,6 +257,35 @@ class TestPrepare(unittest.TestCase):
self.assertEqual("", plan.internal_network)
self.assertEqual("", plan.egress_network)
def test_prepare_writes_known_hosts_file(self):
plan = _StubGate().prepare(
fixture_with_git().bottles["dev"], "demo", self.stage
)
upstream = plan.upstreams[0]
self.assertEqual(self.stage / "bot-bottle-known_hosts",
upstream.known_hosts_file)
self.assertEqual(
"[gitea.dideric.is]:30009 ssh-ed25519 AAAA...\n",
upstream.known_hosts_file.read_text(),
)
self.assertEqual(0o600, os.stat(upstream.known_hosts_file).st_mode & 0o777)
def test_prepare_skips_known_hosts_file_when_key_missing(self):
manifest = Manifest.from_json_obj({
"bottles": {"dev": {"git": {"remotes": {
"github.com": {
"Name": "foo",
"Upstream": "ssh://git@github.com/didericis/foo.git",
"IdentityFile": "/dev/null",
},
}}}},
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
})
plan = _StubGate().prepare(
manifest.bottles["dev"], "demo", self.stage
)
self.assertEqual(Path(), plan.upstreams[0].known_hosts_file)
def test_prepare_with_no_git_writes_minimal_script(self):
plan = _StubGate().prepare(
fixture_minimal().bottles["dev"], "demo", self.stage