WIP: feat(docker): move a pinned image to a newer version #9

Closed
thisilike wants to merge 2 commits from feat/docker-pinned-versions into feat/docker-jobs-and-status
Owner

Last of four; stacked on #4.

A pinned tag cannot be updated by pulling — nginx:1.25 is nginx:1.25 forever — so the module reported "up to date" for images with years of releases behind them.

  • tags.ts parses versions and offers what exists at each component, scoped by OPSDECK_MOD_DOCKER_UPGRADE_SCOPE (patch by default, because applying one rewrites a file).
  • The pin route rewrites the one line and commits just that line. The file is the operators and may hold work in progress, so git commit <file> is not an option — it commits the working tree copy and would sweep their edits in. commit.ts applies a one-hunk patch to the index instead, serialised per repository, and declines with a reason if the index is already dirty.

Ordered to fail safely: validate the tag against the registry first, keep the original text in hand, prove the edited file still parses before anything is started, put the original back if it does not.

This is why the containers write permission now covers the host mount, which docs/security.md states rather than leaving implied.

Verified in the rig end to end: chooser → apply → compose rewrite → chore(heavy): python:3.12-slim -> python:3.13-slim committed with a clean work tree → pull → recreate → the container runs the new image.

Last of four; stacked on #4. A pinned tag cannot be updated by pulling — `nginx:1.25` is `nginx:1.25` forever — so the module reported "up to date" for images with years of releases behind them. - `tags.ts` parses versions and offers what exists at each component, scoped by `OPSDECK_MOD_DOCKER_UPGRADE_SCOPE` (patch by default, because applying one rewrites a file). - The pin route rewrites the one line and commits **just** that line. The file is the operators and may hold work in progress, so `git commit <file>` is not an option — it commits the working tree copy and would sweep their edits in. `commit.ts` applies a one-hunk patch to the index instead, serialised per repository, and declines with a reason if the index is already dirty. Ordered to fail safely: validate the tag against the registry first, keep the original text in hand, prove the edited file still parses before anything is started, put the original back if it does not. This is why the containers write permission now covers the host mount, which docs/security.md states rather than leaving implied. Verified in the rig end to end: chooser → apply → compose rewrite → `chore(heavy): python:3.12-slim -> python:3.13-slim` committed with a clean work tree → pull → recreate → the container runs the new image.
A pinned tag cannot be updated by pulling — `nginx:1.25` is `nginx:1.25`
forever — so the module reported "up to date" for images with years of
releases behind them. Answering "is there a newer version" means reading the
registry's tag list, and applying the answer means editing the operator's
compose file.

Both halves are here:

- `tags.ts` parses versions and offers what exists at each component, scoped
  by OPSDECK_MOD_DOCKER_UPGRADE_SCOPE (patch by default, because applying one
  rewrites a file). `updates.ts` lists tags once per repository, paginated,
  and only for images whose tag looks like a version at all.
- The pin route rewrites the one line and commits just that line. The file is
  the operator's and may hold work in progress, so `git commit <file>` is not
  an option: it commits the working tree copy and would sweep their edits in.
  `commit.ts` applies a one-hunk patch to the index instead, serialised per
  repository, and declines with a reason if the index is already dirty.

The route is ordered to fail safely: validate the tag against the registry
first, keep the original text in hand, prove the edited file still parses
before anything is started, and put the original back if it does not. The
chooser is per image rather than one button — a database may be worth a patch
and nothing more while a sidecar can take the major — and its rules live in
`pin_model.ts`, where they can be tested without clicking.

This is why the container's write permission now covers the host mount, which
docs/security.md states rather than leaving implied.
The container pin route put files back with a swallowed write and then
told the operator "files restored" either way. Its twin, the stack pin
route, already did this properly — collecting the files it could not
restore, logging each, and appending " — COULD NOT RESTORE: …" to the
error — with a comment recording exactly why: a read-only mount or an
ownership change has to be said out loud, or the file keeps a tag
compose rejected and the next `compose up` uses it.

That logic is now one helper both routes call, rather than one good copy
and one bad one, with the keep-the-FIRST-text rule that the stack route
depends on: a second service edited in the same file reads text that
already holds our first edit, and restoring that leaves the file half
applied.

Five tests, on a path that had none — including the one that matters,
where the restore write itself fails.
Author
Owner

Superseded by #10, which merges this branch together with the rest of the stack and resolves the collisions between them. Closing rather than merging in place: replaying these merges one PR at a time would reproduce the same conflicts and lose the resolutions. The branch is untouched.

Superseded by #10, which merges this branch together with the rest of the stack and resolves the collisions between them. Closing rather than merging in place: replaying these merges one PR at a time would reproduce the same conflicts and lose the resolutions. The branch is untouched.
thisilike closed this pull request 2026-08-09 23:33:05 +02:00

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
OpsDeck/core!9
No description provided.