Skip to content

Commit 13b287b

Browse files
committed
Added a new CI job that validates all built-in default images exist
1 parent e1ca81c commit 13b287b

3 files changed

Lines changed: 146 additions & 32 deletions

File tree

.github/workflows/ci.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,26 @@ on:
55
pull_request:
66

77
jobs:
8+
default-images:
9+
runs-on: ubuntu-latest
10+
11+
steps:
12+
- name: Check out repository
13+
uses: actions/checkout@v4
14+
15+
- name: Set up Python
16+
uses: actions/setup-python@v5
17+
with:
18+
python-version: "3.12"
19+
20+
- name: Install dependencies
21+
run: |
22+
python -m pip install --upgrade pip
23+
python -m pip install -e ".[dev]"
24+
25+
- name: Validate default images
26+
run: python scripts/check_default_images.py
27+
828
tests:
929
runs-on: ubuntu-latest
1030
strategy:

scripts/check_default_images.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/usr/bin/env python3
2+
"""Validate that built-in default container images exist in registries."""
3+
4+
from __future__ import annotations
5+
6+
import os
7+
import shutil
8+
import subprocess
9+
import sys
10+
11+
MANIFEST_CHECK_TIMEOUT_SECONDS = 30
12+
13+
14+
def _unset_image_env_overrides(image_override_env_keys: tuple[str, ...]) -> None:
15+
"""Force canonical built-in defaults by clearing image override env vars."""
16+
for key in image_override_env_keys:
17+
os.environ.pop(key, None)
18+
19+
20+
def _check_image_exists(image: str) -> tuple[bool, str]:
21+
try:
22+
proc = subprocess.run(
23+
["docker", "manifest", "inspect", image],
24+
capture_output=True,
25+
text=True,
26+
check=False,
27+
timeout=MANIFEST_CHECK_TIMEOUT_SECONDS,
28+
)
29+
except subprocess.TimeoutExpired:
30+
return (
31+
False,
32+
(
33+
"timed out after "
34+
f"{MANIFEST_CHECK_TIMEOUT_SECONDS}s while checking docker manifest"
35+
),
36+
)
37+
if proc.returncode == 0:
38+
return True, ""
39+
error = proc.stderr.strip() or proc.stdout.strip() or "unknown error"
40+
return False, error
41+
42+
43+
def main() -> int:
44+
if shutil.which("docker") is None:
45+
print("Error: docker CLI is required but not found in PATH.", file=sys.stderr)
46+
return 1
47+
48+
from vibepod.constants import IMAGE_OVERRIDE_ENV_KEYS, get_default_images
49+
50+
_unset_image_env_overrides(IMAGE_OVERRIDE_ENV_KEYS)
51+
default_images = get_default_images()
52+
53+
failures: list[str] = []
54+
55+
for name in sorted(default_images):
56+
image = default_images[name]
57+
print(f"Checking default image for {name}: {image}")
58+
ok, error = _check_image_exists(image)
59+
if ok:
60+
continue
61+
failures.append(f"- {name}: {image}\n {error}")
62+
63+
if failures:
64+
print("\nDefault image validation failed:\n" + "\n".join(failures), file=sys.stderr)
65+
return 1
66+
67+
print("\nAll default images are resolvable.")
68+
return 0
69+
70+
71+
if __name__ == "__main__":
72+
raise SystemExit(main())

src/vibepod/constants.py

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -38,38 +38,60 @@
3838
"x": "codex",
3939
}
4040

41-
DEFAULT_IMAGES: dict[str, str] = {
42-
"claude": os.environ.get(
43-
"VP_IMAGE_CLAUDE",
44-
f"{os.environ.get('VP_IMAGE_NAMESPACE', 'vibepod')}/claude:latest",
45-
),
46-
"gemini": os.environ.get(
47-
"VP_IMAGE_GEMINI",
48-
f"{os.environ.get('VP_IMAGE_NAMESPACE', 'nezhar')}/gemini-container:latest",
49-
),
50-
"opencode": os.environ.get(
51-
"VP_IMAGE_OPENCODE", f"{os.environ.get('VP_IMAGE_NAMESPACE', 'nezhar')}/opencode-cli:latest"
52-
),
53-
"devstral": os.environ.get(
54-
"VP_IMAGE_DEVSTRAL", f"{os.environ.get('VP_IMAGE_NAMESPACE', 'nezhar')}/devstral-cli:latest"
55-
),
56-
"auggie": os.environ.get(
57-
"VP_IMAGE_AUGGIE", f"{os.environ.get('VP_IMAGE_NAMESPACE', 'nezhar')}/auggie-cli:latest"
58-
),
59-
"copilot": os.environ.get(
60-
"VP_IMAGE_COPILOT",
61-
f"{os.environ.get('VP_IMAGE_NAMESPACE', 'nezhar')}/copilot-cli:latest",
62-
),
63-
"codex": os.environ.get(
64-
"VP_IMAGE_CODEX", f"{os.environ.get('VP_IMAGE_NAMESPACE', 'vibepod')}/codex:latest"
65-
),
66-
"datasette": os.environ.get(
67-
"VP_DATASETTE_IMAGE", f"{os.environ.get('VP_IMAGE_NAMESPACE', 'vibepod')}/datasette:latest"
68-
),
69-
"proxy": os.environ.get(
70-
"VP_PROXY_IMAGE", f"{os.environ.get('VP_IMAGE_NAMESPACE', 'vibepod')}/proxy:latest"
71-
),
72-
}
41+
IMAGE_OVERRIDE_ENV_KEYS: tuple[str, ...] = (
42+
"VP_IMAGE_NAMESPACE",
43+
"VP_IMAGE_CLAUDE",
44+
"VP_IMAGE_GEMINI",
45+
"VP_IMAGE_OPENCODE",
46+
"VP_IMAGE_DEVSTRAL",
47+
"VP_IMAGE_AUGGIE",
48+
"VP_IMAGE_COPILOT",
49+
"VP_IMAGE_CODEX",
50+
"VP_DATASETTE_IMAGE",
51+
"VP_PROXY_IMAGE",
52+
)
53+
54+
55+
def get_default_images() -> dict[str, str]:
56+
return {
57+
"claude": os.environ.get(
58+
"VP_IMAGE_CLAUDE",
59+
f"{os.environ.get('VP_IMAGE_NAMESPACE', 'vibepod')}/claude:latest",
60+
),
61+
"gemini": os.environ.get(
62+
"VP_IMAGE_GEMINI",
63+
f"{os.environ.get('VP_IMAGE_NAMESPACE', 'nezhar')}/gemini-container:latest",
64+
),
65+
"opencode": os.environ.get(
66+
"VP_IMAGE_OPENCODE",
67+
f"{os.environ.get('VP_IMAGE_NAMESPACE', 'nezhar')}/opencode-cli:latest",
68+
),
69+
"devstral": os.environ.get(
70+
"VP_IMAGE_DEVSTRAL",
71+
f"{os.environ.get('VP_IMAGE_NAMESPACE', 'nezhar')}/devstral-cli:latest",
72+
),
73+
"auggie": os.environ.get(
74+
"VP_IMAGE_AUGGIE",
75+
f"{os.environ.get('VP_IMAGE_NAMESPACE', 'nezhar')}/auggie-cli:latest",
76+
),
77+
"copilot": os.environ.get(
78+
"VP_IMAGE_COPILOT",
79+
f"{os.environ.get('VP_IMAGE_NAMESPACE', 'nezhar')}/copilot-cli:latest",
80+
),
81+
"codex": os.environ.get(
82+
"VP_IMAGE_CODEX", f"{os.environ.get('VP_IMAGE_NAMESPACE', 'vibepod')}/codex:latest"
83+
),
84+
"datasette": os.environ.get(
85+
"VP_DATASETTE_IMAGE",
86+
f"{os.environ.get('VP_IMAGE_NAMESPACE', 'vibepod')}/datasette:latest",
87+
),
88+
"proxy": os.environ.get(
89+
"VP_PROXY_IMAGE", f"{os.environ.get('VP_IMAGE_NAMESPACE', 'vibepod')}/proxy:latest"
90+
),
91+
}
92+
93+
94+
DEFAULT_IMAGES: dict[str, str] = get_default_images()
7395

7496
DEFAULT_ALIASES: dict[str, str] = {
7597
**{shortcut: f"run {agent}" for shortcut, agent in AGENT_SHORTCUTS.items()},

0 commit comments

Comments
 (0)