From 2d53696dafadc7d8e16f7b9dbbe8e69a735cb341 Mon Sep 17 00:00:00 2001 From: Micah Morefield Date: Tue, 29 Sep 2026 12:03:07 -0700 Subject: [PATCH] fix(sat): Add docker image automatic updates Added docker image automatic updates by attempting to pull the image on every run. Previously, _running_container only pulled the image when no copy existed locally, so once a machine had an image, it never updated. The scan, record and signal-report tools now call pull_image() every run. When the local copy already matches the registry, Docker compares the manifest digest and downloads nothing; otherwise it fetches only the changed layers and moves :latest to the new build. Signed-off-by: Micah Morefield --- README.md | 8 ++++---- src/hubblenetwork/sat.py | 9 +++++++-- tests/test_sat.py | 9 ++++++--- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 4b8fcd1..63c6ff9 100644 --- a/README.md +++ b/README.md @@ -232,10 +232,10 @@ key cannot decrypt are hidden unless `--show-failed-decryption` is given. `sat scan` handles the Docker container ([`ghcr.io/hubblenetwork/sdr-docker`](https://ghcr.io/hubblenetwork/sdr-docker)) for -you: it verifies Docker, pulls the image if it isn't cached, starts the container -privileged so it can reach USB, waits for the receiver API and the SDR, polls the -container's HTTP API and deduplicates by device ID and sequence number, then stops and -removes the container on exit. +you: it verifies Docker, pulls the latest image (only changed layers are downloaded +when a copy is already cached), starts the container privileged so it can reach USB, +waits for the receiver API and the SDR, polls the container's HTTP API and deduplicates +by device ID and sequence number, then stops and removes the container on exit. ### One-shot capture (`record` / `signal-report`) diff --git a/src/hubblenetwork/sat.py b/src/hubblenetwork/sat.py index d6a96fc..37aa956 100644 --- a/src/hubblenetwork/sat.py +++ b/src/hubblenetwork/sat.py @@ -135,6 +135,9 @@ def _image_exists_locally(image: str) -> bool: def pull_image(image: str = DOCKER_IMAGE) -> None: """Pull *image*, ensuring the latest version is fetched. + + If the latest version is already stored locally, the image + is not re-downloaded. """ import docker @@ -375,11 +378,13 @@ def _running_container( ensure_docker_available() + # Always pull so a cached image picks up new receiver builds; the + # message only tells the user whether to expect a full download. if _image_exists_locally(image): - _emit(f"Using local image {image}...") + _emit(f"Checking for updates to {image}...") else: _emit("Pulling Docker image...") - pull_image(image) + pull_image(image) _emit("Starting container...") container_id = start_container( diff --git a/tests/test_sat.py b/tests/test_sat.py index 19220c0..70b678f 100644 --- a/tests/test_sat.py +++ b/tests/test_sat.py @@ -472,11 +472,12 @@ def test_pulls_starts_and_stops( @patch("hubblenetwork.sat.start_container") @patch("hubblenetwork.sat.pull_image") @patch("hubblenetwork.sat.ensure_docker_available") - def test_skips_pull_when_image_exists_locally( + def test_pulls_even_when_image_exists_locally( self, mock_ensure, mock_pull, mock_start, mock_wait, mock_wait_sdr, mock_stop, mock_image_exists, ): mock_start.return_value = "container789" + messages = [] with sat._running_container( port=sat.API_PORT, @@ -485,12 +486,14 @@ def test_skips_pull_when_image_exists_locally( privileged=True, name=sat.CONTAINER_NAME, wait_for_sdr=True, - on_status=None, + on_status=messages.append, ): pass mock_image_exists.assert_called_once_with("sdr-docker:dev") - mock_pull.assert_not_called() + mock_pull.assert_called_once_with("sdr-docker:dev") + assert "Checking for updates to sdr-docker:dev..." in messages + assert "Pulling Docker image..." not in messages @patch("hubblenetwork.sat._image_exists_locally", return_value=False) @patch("hubblenetwork.sat.stop_container")