Skip to content

[1.4] Fix extension update wiping the running version when pull fails - #4338

Draft
joaoantoniocardoso wants to merge 5 commits into
bluerobotics:1.4-devfrom
joaoantoniocardoso:fix/1.4/kraken-failed-extension-update
Draft

[1.4] Fix extension update wiping the running version when pull fails#4338
joaoantoniocardoso wants to merge 5 commits into
bluerobotics:1.4-devfrom
joaoantoniocardoso:fix/1.4/kraken-failed-extension-update

Conversation

@joaoantoniocardoso

@joaoantoniocardoso joaoantoniocardoso commented Aug 27, 2026

Copy link
Copy Markdown
Member

Summary

  • Treat a failed Docker image pull as an install/update failure instead of success, so Kraken no longer uninstalls the running tag and leaves a version with no local image.
  • Run extension update() and v2 tagged/from_latest install atomically (default). A failed pull restores a copy of prior settings for that tag (including same-tag reinstall) and re-enables the previously running sibling, without images.delete of a possible alias.
  • Inspect docker:tag for what start() actually runs. Only Docker 404 means the image is absent. Any other inspect/daemon error refuses the local-image fallback and rolls back — it does not report success.
  • Reject a retag of the running sibling's image as the requested version on the failed-pull path. If the sibling image cannot be inspected (including Docker 404), refuse the local-image fallback. After a clean pull, do not apply that sibling-Id guard.
  • A pull stream is not success unless it includes Downloaded newer image or Image is up to date. Disconnect or empty completion rolls back instead of purging the running tag.
  • Retag from the catalog platform digest when that ref is already local. Do not scan leftover images of the same repo.
  • If the pull fails but a usable local docker:tag already exists and is not the sibling's image, keep that tag and do not purge other tags.
  • Yield the pull-stream error line before failing, log local-image fallback at warning, and cap start-attempt backoff with min instead of max (was ~600s after the first failure).

Closes #1643

Test plan

  • Reproduced on DUT 192.168.0.147 (1.4-dev): update_to_version Cockpit v1.18.2v1.19.0-beta.9 with Docker Hub blocked returned HTTP 200 with an empty body, replaced settings with the new tag, deleted the old image, and the watchdog then failed with No such image.
  • Radius 43 passed / 0 failed on every install() / update() HTTP caller plus watchdog start(), overlay confirmed (do not docker restart blueos-core on this DUT). Includes:
    • pull/digest/404 helper table in-process
    • v1/v2 custom install pull-fail (fragment + reason, not listed)
    • same-tag update while running
    • v1 update_to_version Hub blocked, target not local (settings + image + container stay)
    • watchdog start() with image deleted and Hub blocked (Image not found locally + Failed to start, no No such image; retry ~15s with min backoff)
    • local-image update keeps the old image and disabled old tag
    • v2 PUT local tag
    • wrong local tag (retag of the running image) does not look like a successful update; alias docker tag is not untagged
    • leftover unique digest-only image of the same repo is not promoted; running tag is restored
    • catalog platform digest without :tag is retagged and update succeeds
    • v2 tagged and from_latest install never-installed, Hub blocked
    • v2 PUT not-local restores the running sibling
  • Review DUT checks after sibling-404 and pull success-status: untag running docker:A, alias docker:B, Hub blocked update_to_versionFailed to pull, A stays enabled. Empty pull stream (Pulling fs layer only) → pull finished without a success status, A kept, B not enabled. 14/14.
  • journey_http --extension-lifecycle --allow-mutating --identifier bluerobotics.cockpit --tag v1.18.2 --assert-unknown: 181 passed, 0 failed; DUT restored to baseline (blueos.major_tom only).

@joaoantoniocardoso
joaoantoniocardoso force-pushed the fix/1.4/kraken-failed-extension-update branch 3 times, most recently from 0ba998c to d8db3f9 Compare August 28, 2026 00:17
@joaoantoniocardoso joaoantoniocardoso linked an issue Aug 28, 2026 that may be closed by this pull request
@joaoantoniocardoso
joaoantoniocardoso force-pushed the fix/1.4/kraken-failed-extension-update branch 8 times, most recently from 8776fcf to 209e1cb Compare August 28, 2026 18:26
@joaoantoniocardoso
joaoantoniocardoso marked this pull request as ready for review August 28, 2026 18:28
@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown

Automated PR Review

0. Summary

  • Verdict: LGTM :shipit:

Fixes a nasty install/update path in Kraken where a failed Docker pull could delete the running image and leave the extension pointing at a version with no local backing. Introduces an atomic rollback (restoring the prior ExtensionSettings and re-enabling the running sibling), guards the local-image fallback against unintentionally retagging the sibling's image, treats a pull stream that ends without a Downloaded newer image / Image is up to date status as failure, and fixes the start-attempt backoff which used max(...) and effectively pinned the delay at the 600 s ceiling from attempt one. Refactors install()/start() around a shared _pull_docker_image and _ensure_tagged_local_image helper.

No further comments, nice job 👍

Generated by PR Review Bot. This is advisory, a human reviewer must still approve.

@joaoantoniocardoso
joaoantoniocardoso marked this pull request as draft August 28, 2026 18:38
@joaoantoniocardoso
joaoantoniocardoso force-pushed the fix/1.4/kraken-failed-extension-update branch 2 times, most recently from 4ccd3a8 to c758d98 Compare August 28, 2026 19:02
@joaoantoniocardoso
joaoantoniocardoso marked this pull request as ready for review August 28, 2026 21:44
@joaoantoniocardoso
joaoantoniocardoso marked this pull request as draft August 28, 2026 21:45
@joaoantoniocardoso
joaoantoniocardoso marked this pull request as ready for review August 28, 2026 22:20
@joaoantoniocardoso
joaoantoniocardoso requested a review from a team August 28, 2026 22:23
joaomariolago and others added 5 commits August 28, 2026 21:07
* Reduce code complexity of install function by spliting in multiple
  parts
max() floored every retry at 600s after the first failed start.
update() and v2 from_latest/tagged install used the non-atomic default,
so a failed pull was treated as success and purged the running tag.
Failed pulls uninstalled the new tag and could delete the running image.
Restore prior settings, refuse a sibling-image alias, and require a
Docker success status on the install pull stream.
…tatus

start() used a non-streaming pull that ignored in-band Docker errors.
@joaoantoniocardoso
joaoantoniocardoso force-pushed the fix/1.4/kraken-failed-extension-update branch from c758d98 to 02dea1d Compare August 29, 2026 00:07
docker_auth = f"{self.source.auth.username}:{self.source.auth.password}"
return base64.b64encode(docker_auth.encode("utf-8")).decode("utf-8")

async def _image_is_available_locally(self) -> bool:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we avoid moving this function around ? it just add unnecessary diff

if self.digest:
await client.images.tag(tag, f"{self.source.docker}:{self.tag}")
# First we should make sure no other tag is running
running_ext = await self._disable_running_extension()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are doing nothing with this variable, is this expected ?

user_permissions=self.source.user_permissions,
)
# Save in settings first, if the image fails to install it will try to fetch after in main kraken check loop
# Atomic failure rolls this entry back and re-enables the previously running sibling.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be in anoter commit ?

Comment on lines +192 to +197
# start() and a successful pull run docker:tag. Catalog platform digests are not
# stored in RepoDigests after `docker pull repo:tag` (that records the index
# digest), so a digest match against the catalog cannot be required.
# sibling_image_id is only for the failed-pull fallback: a retag of the running
# sibling onto the new name is not the requested version. Do not pass it after a
# clean pull -- two tags can share an image Id (aliases) and still be the pull.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

This is not friendly at all.
It took so much time to understand, and I'm still not sure.


We get an image from DockerHub, their "catalog" ID does not match with what we receive, is that right ?

sibling_image_id is the image that is used as base from a failed attempt ? Or is the image that has different "repo:tag" ?

except Exception as rollback_error:
logger.warning(f"Failed to roll back {self.identifier}:{self.tag} after pull failure: {rollback_error}")
if not running_ext:
return

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we have a log message here ?

@joaoantoniocardoso
joaoantoniocardoso marked this pull request as draft September 3, 2026 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Kraken: (new) issue updating extensions:

3 participants