Skip to content

Route the LINBO image management operations - #33

Open
TomlDev wants to merge 1 commit into
linuxmuster:lmn74from
edulution-io:linbo-image-management
Open

Route the LINBO image management operations#33
TomlDev wants to merge 1 commit into
linuxmuster:lmn74from
edulution-io:linbo-image-management

Conversation

@TomlDev

@TomlDev TomlDev commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

LinboImageManager already implements list, delete, rename, duplicate, restore and save_extras, and linuxmusterTools covers them with tests. The API routed none of them, so an image could be uploaded and downloaded but never renamed, copied, rolled back or deleted. This is the last group of LINBO operations webui7 offers that the API does not.

Nine endpoints under /v1/linbo/images, RoleChecker("G") like the rest of the router. No existing code is modified.

Method Path
GET /v1/linbo/images
GET /v1/linbo/images/{image_name}/backups
DELETE /v1/linbo/images/{image_name}
DELETE /v1/linbo/images/{image_name}/diff
DELETE /v1/linbo/images/{image_name}/backups/{timestamp}
POST /v1/linbo/images/{image_name}/backups/{timestamp}/restore
POST /v1/linbo/images/{image_name}/rename
POST /v1/linbo/images/{image_name}/duplicate
PUT /v1/linbo/images/{image_name}/extras

GET /images complements /images/manifest rather than replacing it: the manifest reports what sync clients need, this reports what an image management UI needs, which is the reg, postsync and prestart contents and the backup list.

Decisions worth reviewing

The manager returns silently for an unknown group. delete, rename, restore and save_extras are all guarded by if group in self.groups: with no else, so a request for a nonexistent image would answer 200. Each endpoint resolves the group first and answers 404 itself.

An image with an unreadable .info is refused with 409. LinboImageGroup.load() returns early in that case, leaving base as None and never assigning diff_image at all, so touching group.diff_image raises AttributeError. Such a group is rejected with the recorded reason instead. It still appears in GET /images, flagged with its error, which is where it gets noticed.

Backup timestamps. LinboImageGroup keys its backups by the display date from timestamp2date (27/01/2026 11:07), and delete/restore take that form while save_extras takes the raw timestamp. The URLs use the raw YYYYMMDDhhmm form throughout and convert at the boundary, so the path segments stay free of slashes and spaces.

NameChecker.check returns a bool rather than raising, so its result is tested explicitly rather than relied on through an except ValueError branch.

info is required in the sidecar body. save_extras deletes any sidecar the body leaves out. That is fine for the optional ones, but LinboImage.load_info raises IncompleteImageInfoError without .info, so a request that omitted it would leave the image unreadable to the manifest, to LINBO and to this endpoint rather than merely editing it.

Known limitation: restore drops the common sidecars

restore moves every top-level file into the new backup directory, and backups only ever contain the image-scoped sidecars, so .reg, .postsync and .prestart are archived and never come back. After a rollback the image has lost them.

This is existing LinboImageManager.restore behaviour and webui7 reaches it through the same call, so routing it changes nothing for the worse — but it is worth fixing in linuxmuster-tools, and I would rather ask than send a patch for it. Happy to open a separate issue there, or to drop the two restore endpoints from this PR if you would prefer them held until the library side is settled.

Two smaller notes from the same testing:

  • Two restores of one image within the same minute collide, because the new backup directory is named %Y%m%d%H%M. It surfaces as a 409, so nothing is lost, but the message is not actionable.
  • NAME_RULES['linbo_image'] allows a leading dot, so a rename to .incoming would collide with the upload staging directory. Only a global-administrator can reach it, so it is a footgun rather than a hole, and the fix would be a regex change in linuxmuster-tools.

Testing

pytests/test_linbo_image_management.py, 46 tests, in the mock style of the existing test_linbo_images.py. Run on a 7.4 server against linuxmuster-tools@lmn74; the full suite shows the same failures before and after this branch, compared as sorted FAILED/ERROR id lists rather than counts.

Beyond the unit tests:

  • Every operation was run end to end against real files in a scratch tree under /srv/linbo — duplicate, rename of an image with backups, list, restore, sidecar writes for both base and backup, diff deletion and deletion — which is how the restore behaviour above was found.
  • GET /images was run against the real image tree and its response JSON-encoded, since this is the first endpoint to serialise LinboImageGroup.to_dict().
  • The parametrised route test asserts each path resolves to the intended endpoint. DELETE /images/upload/diff is genuinely ambiguous — it matches both cancel_upload_endpoint for an image named diff and delete_image_diff for one named upload — and registration order settles it in favour of the older route; that case is pinned.
  • Eleven mutations of the guards and of the timestamp conversion were each confirmed to fail at least one test.

LinboImageManager already implements list, delete, rename, duplicate,
restore and save_extras, and linuxmusterTools covers them with tests.
The API routed none of them, so an image could be uploaded and
downloaded but never renamed, copied, rolled back or deleted.

Nine endpoints under /v1/linbo/images, global-administrator only like
the rest of the router. Four points needed handling in the router
rather than the library:

- The manager returns silently when a group is unknown, so each
  endpoint resolves the group first and answers 404 itself.
- LinboImageGroup.load() returns early for an image whose .info cannot
  be read, leaving base as None and diff_image unassigned. Such a group
  is refused with 409 and the recorded reason; it stays visible in the
  listing, flagged, which is where it gets noticed.
- Backups are keyed internally by a display date, while save_extras
  takes a raw timestamp. The URLs take the raw YYYYMMDDhhmm form
  throughout and convert where the manager expects the other.
- NameChecker.check returns a bool instead of raising, so its result is
  tested explicitly rather than relied on through an except branch.

info is required in the sidecar body: save_extras deletes whatever the
body omits, and an image without .info can no longer be read by the
manifest, by LINBO or by this endpoint.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant