Skip to content

Make Cirros mirrorable to the object store - #1273

Draft
ideaship wants to merge 1 commit into
mainfrom
cirros-mirror
Draft

ideaship wants to merge 1 commit into
mainfrom
cirros-mirror

Conversation

@ideaship

@ideaship ideaship commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Cirros is the only image definition without a mirror_url, so no mirrored copy
of it exists in the object store and there is no way to serve it from a local
mirror. An air-gapped site therefore has no route to the image most commonly
used to check that a cloud works at all.

Why three changes in one commit

They cannot land separately, because contrib/mirror.py:

  • skips an image that has no shortname (:380-381),
  • skips a version that has no mirror_url (:399-400), and
  • derives the upload destination from mirror_url (:60).

The definition is thus the input to mirroring rather than its output: the
destination has to be declared before the tool can place anything there. So this
adds shortname: cirros, a mirror_url per pinned version, and cirros to the
shortname allow-list.

Destinations

Derived by calling the tool's own mirror_paths() rather than composed by hand,
so the declared and computed paths cannot disagree:

0.6.2 -> openstack-images/cirros/0.6.2-cirros.qcow2
0.6.3 -> openstack-images/cirros/0.6.3-cirros.qcow2

Both are flat, with no version directory. mirror_paths() adds one only for
flatcar, or when the source extension is .bz2/.zip/.xz/.gz; Cirros
publishes an uncompressed .img.

url is deliberately left untouched — it is what sets the image_source
property, so upstream provenance stays recorded even when the bytes are fetched
from a mirror.

Follow-up

This change only makes the upload possible. Performing it is a separate
credentialed step (tox -e mirror), and until that runs the new mirror_urls
point at objects that do not exist yet. Worth landing before the next mirror
pass so Cirros rides it.

Precedent for the allow-list entry, which also states the general rule that
registering a handler is not sufficient on its own:

Testing

  • tox -- --check-only passes (schema).
  • tox -e test -- test/unit passes, 74 tests.
  • yamllint, python-black, flake8, mypy clean over the changed files.

🤖 Generated with Claude Code

@ideaship

ideaship commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

openstack-image-manager-integration-test fails, and it is expected to until
the mirror upload happens. Recording the cause so it is not mistaken for a
defect in the change.

Processing image 'Cirros 0.6.2'
ERROR  Tested URL https://nbg1.your-objectstorage.com/osism/openstack-images/cirros/0.6.2-cirros.qcow2: 403
ERROR  Skipping 'Cirros 0.6.2' due to HTTP status code 403
manage: exit 1

The job runs main.py --filter Cirros, so it resolves the newly declared
mirror_url and gets a 403 for an object that has not been uploaded yet.

This is why catalogue changes normally merge before the mirror runs, and why
this one cannot: the integration test exercises Cirros specifically, so a
dangling Cirros mirror_url is precisely what it catches. Other images gaining
a mirror_url are not touched by it.

The ordering is a genuine pinch — mirror.py needs mirror_url declared to
know where to upload, but the check needs the object to exist before the
declaration can merge. So the mirror has to run from this branch:

  1. check out cirros-mirror and run tox -e mirror with object-store
    credentials; the tool reads this branch's definition and uploads to the two
    declared destinations,
  2. recheck,
  3. merge.

Alternatives considered and rejected: landing shortname and the allow-list
without mirror_url uploads nothing, since versions without a mirror_url are
skipped; disabling the image to quiet the check breaks the same job a different
way; and making a 403 non-fatal would remove a check that is doing its job
correctly here.

Every other job passes: flake8, mypy, tox, yamllint, python-black, DCO
and validate-configuration.

@ideaship ideaship self-assigned this Sep 7, 2026
Cirros is the only image definition without a mirror_url, so no mirrored
copy of it exists in the object store and there is no way to serve it
from a local mirror. An air-gapped site therefore has no route to the
image most commonly used to check that a cloud works at all.

Three things block mirroring it, and they have to land together before
any upload, because contrib/mirror.py:

  - skips an image that has no shortname,
  - skips a version that has no mirror_url, and
  - derives the upload destination *from* mirror_url.

The definition is thus the input to mirroring rather than its output:
the destination has to be declared before the tool can place anything
there. Add shortname: cirros, a mirror_url for each pinned version, and
"cirros" to the shortname allow-list.

The two destinations were derived with the tool's own mirror_paths()
instead of composed by hand, so the declared and computed paths cannot
disagree. Both are flat, with no version directory: mirror_paths() adds
one only for flatcar, or when the source extension is compressed, and
Cirros publishes an uncompressed .img.

url is deliberately left untouched. It is what sets the image_source
property, so upstream provenance stays recorded even when the bytes are
fetched from a mirror.

This change only makes the upload possible; performing it is a separate
credentialed step (tox -e mirror).

Assisted-by: Claude:claude-opus-5
Signed-off-by: Roger Luethi <luethi@osism.tech>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

2 participants