Skip to content

Commit ac20d1e

Browse files
committed
Pull latest datasette image and wait for readiness before opening browser
1 parent 0c2d487 commit ac20d1e

2 files changed

Lines changed: 57 additions & 1 deletion

File tree

src/vibepod/commands/logs.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
from __future__ import annotations
44

5+
import time
6+
import urllib.error
7+
import urllib.request
58
import webbrowser
69
from pathlib import Path
710
from typing import Annotated
@@ -16,6 +19,24 @@
1619
app = typer.Typer(help="View logs and traffic UI")
1720

1821

22+
_HEALTH_TIMEOUT = 30
23+
_HEALTH_INTERVAL = 0.5
24+
25+
26+
def _wait_for_datasette(port: int) -> None:
27+
url = f"http://localhost:{port}/-/healthz"
28+
deadline = time.monotonic() + _HEALTH_TIMEOUT
29+
while time.monotonic() < deadline:
30+
try:
31+
urllib.request.urlopen(url, timeout=2) # noqa: S310
32+
return
33+
except urllib.error.HTTPError:
34+
return # server responded — healthy enough
35+
except (urllib.error.URLError, OSError):
36+
time.sleep(_HEALTH_INTERVAL)
37+
warning("Datasette did not become healthy in time — opening browser anyway")
38+
39+
1940
@app.command("start")
2041
def logs_start(
2142
port: Annotated[int | None, typer.Option("--port", help="Datasette host port")] = None,
@@ -39,17 +60,27 @@ def logs_start(
3960
error(str(exc))
4061
raise typer.Exit(EXIT_DOCKER_NOT_RUNNING) from exc
4162

63+
info("Checking for datasette image updates…")
64+
updated = manager.pull_if_newer(datasette_image)
65+
if updated:
66+
info("New image available — restarting datasette")
67+
existing = manager.find_datasette()
68+
if existing:
69+
existing.remove(force=True)
70+
4271
info(f"Starting Datasette on http://localhost:{datasette_port}")
4372
manager.ensure_datasette(
4473
image=datasette_image,
4574
logs_db_path=logs_db_path,
4675
proxy_db_path=proxy_db_path,
4776
port=datasette_port,
4877
)
78+
79+
_wait_for_datasette(datasette_port)
4980
success("Datasette is ready")
5081

5182
if not no_open:
52-
webbrowser.open(f"http://localhost:{datasette_port}")
83+
webbrowser.open(f"http://localhost:{datasette_port}/-/dashboards")
5384

5485

5586
@app.command("stop")

src/vibepod/core/docker.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,31 @@ def pull_image(self, image: str) -> None:
7070
except APIError as exc:
7171
raise DockerClientError(f"Failed to pull image {image}: {exc}") from exc
7272

73+
def pull_if_newer(self, image: str) -> bool:
74+
"""Pull *image* and return True if the local image was updated.
75+
76+
Returns False when the image is already up to date, when the pull
77+
fails (e.g. no network / private registry), or when the image only
78+
exists locally and cannot be found on a registry.
79+
"""
80+
try:
81+
old_id: str | None
82+
try:
83+
old_id = self.client.images.get(image).id
84+
except NotFound:
85+
old_id = None
86+
87+
self.client.images.pull(image)
88+
89+
try:
90+
new_id = self.client.images.get(image).id
91+
except NotFound:
92+
return False
93+
94+
return bool(old_id != new_id)
95+
except APIError:
96+
return False
97+
7398
def ensure_network(self, name: str) -> None:
7499
try:
75100
self.client.networks.get(name)

0 commit comments

Comments
 (0)