mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-09 14:02:56 +09:00
RabbitMQ's cluster operator ships no backup CRD and RabbitMQ has no
consistent online export of message payloads, so back up broker
DEFINITIONS (vhosts, users, permissions, queues, exchanges, bindings,
policies, parameters) over the management HTTP API and store them in the
system bucket. Message payloads and PITR are out of scope - a full-data
backup is a Velero volume snapshot, a separate strategy.
Add a first-class strategy.backups.cozystack.io/Rabbitmq driver, following
the Altinity pattern (a dedicated driver that renders a one-shot Job for an
HTTP-API engine):
- reconcileRabbitmq / reconcileRabbitmqRestore render the strategy's
PodTemplateSpec (curl GET/POST /api/definitions, push/pull to S3 with
--aws-sigv4) as a batch/v1.Job, for both in-place and to-copy restore.
- A Go-read precondition gates each run on the RabbitmqCluster reporting
AllReplicasReady (via the rabbitmqtypes partial type), surfacing a
precise Ready=False wait instead of a Job that starts and fails.
- The produced Backup records the object location on status.artifact.uri
(rendered from spec.artifactURITemplate; the Backup CRD has no status
subresource, so it persists on Create, as in the Etcd driver).
Wire dispatch in both controllers + supportedBackupStrategyKinds, register
rabbitmqtypes in the scheme, and grant read on rabbitmqclusters. Ship
cozy-default-rabbitmq (a $bucketName-gated Rabbitmq strategy) and route
apps.cozystack.io/RabbitMQ to it in the cozy-default BackupClass. The
application chart is untouched.
Add unit tests for the driver's pure helpers and the precondition gate, a
runnable example, and an honest-roundtrip Chainsaw e2e that seeds a
sentinel vhost, backs it up, drops it, and asserts it round-trips through
object storage on both restore flows.
The Job pod reuses the platform-migrations image (curl + ca-certificates)
under a new rabbitmqBackupClientImage key, stamped in lockstep with
chBackupClientImage so the repository stays pinned at one digest; a
dedicated slim image is a follow-up.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Andrey Kolkov <androndo@gmail.com>
99 lines
4.5 KiB
Bash
99 lines
4.5 KiB
Bash
#!/usr/bin/env bats
|
|
# Asserts that no first-party image repository is pinned at more than one
|
|
# digest across the committed tree.
|
|
#
|
|
# Promotion retags by digest: hack/promote-retag.sh copies every collected
|
|
# <repo>@<digest> to <repo>:<stable-version>. Two different digests under one
|
|
# repository therefore produce two copies competing for the same destination
|
|
# tag. The first wins, the second hits the write-once guard, and the promotion
|
|
# fails — on release day, in a workflow that runs once per release.
|
|
#
|
|
# This is not hypothetical. platform-migrations was pinned twice: once in
|
|
# packages/core/platform/values.yaml (stamped by its Makefile) and once as
|
|
# .backupStrategyController.chBackupClientImage, which had no producer at all
|
|
# and so froze at v1.4.0-rc.2 while the other advanced to v1.5.0. v1.6.0 is the
|
|
# first release cut through the promote path rather than a full rebuild, so it
|
|
# would have been the first to hit it.
|
|
#
|
|
# The general invariant is cheaper to hold than the specific one: a duplicate
|
|
# pin can arise from any package reusing another's image, which the tree does
|
|
# deliberately (backupstrategy-controller reuses platform-migrations as a
|
|
# curl+jq runner rather than shipping a second one-binary tag).
|
|
#
|
|
# Harness note: the CI path is hack/cozytest.sh, NOT real bats. There is no
|
|
# `run`, `$status`, `$output`, `skip`, or setup()/teardown(); each test runs as
|
|
# a shell function under `set -eu -x`, so a non-zero exit is the failure.
|
|
# Paths are repo-root-relative: BATS_TEST_DIRNAME is unset and would abort the
|
|
# whole suite under `set -u`.
|
|
#
|
|
# Run with: hack/cozytest.sh hack/image-pin-consistency.bats
|
|
|
|
@test "no image repository is pinned at more than one digest" {
|
|
tmp=$(mktemp -d)
|
|
|
|
# Drive the real promotion selector rather than a reimplementation of it, so
|
|
# this tracks whatever set promotion actually acts on. `env -u REGISTRY`:
|
|
# the CI workflow exports REGISTRY=<OCIR build registry> for every job, but
|
|
# the committed tree vendors its digests under the script's default
|
|
# ghcr.io/cozystack/cozystack — inheriting the ambient value would filter for
|
|
# the wrong registry and match nothing.
|
|
rc=0
|
|
env -u REGISTRY hack/promote-retag.sh v9.9.9 --dry-run \
|
|
>"$tmp/out" 2>"$tmp/err" || rc=$?
|
|
if [ "$rc" -ne 0 ]; then
|
|
echo "promote-retag.sh exited $rc" >&2
|
|
cat "$tmp/err" >&2
|
|
rm -rf "$tmp"
|
|
return "$rc"
|
|
fi
|
|
|
|
# Every planned copy ends in docker://<repo>:v9.9.9. A repository appearing
|
|
# twice means two source digests aimed at one destination tag.
|
|
sed -n 's|.*docker://\(.*\):v9\.9\.9$|\1|p' "$tmp/out" | sort > "$tmp/dests"
|
|
dupes=$(uniq -d < "$tmp/dests")
|
|
|
|
if [ -n "$dupes" ]; then
|
|
echo "these repositories are pinned at more than one digest, so promotion" >&2
|
|
echo "would try to retag several digests to the same stable tag and fail:" >&2
|
|
printf '%s\n' "$dupes" >&2
|
|
echo >&2
|
|
echo "Offending pins:" >&2
|
|
for repo in $dupes; do
|
|
grep -rn --include='*.yaml' --include='*.tag' --exclude-dir=charts \
|
|
-- "${repo#ghcr.io/cozystack/cozystack/}" packages/ >&2 || true
|
|
done
|
|
echo >&2
|
|
echo "Give the duplicate key a producer that stamps the same ref (see" >&2
|
|
echo "packages/core/platform/Makefile), or drop the duplicate pin." >&2
|
|
rm -rf "$tmp"
|
|
return 1
|
|
fi
|
|
rm -rf "$tmp"
|
|
}
|
|
|
|
@test "platform-migrations is pinned identically in both consumers" {
|
|
# The specific instance, pinned separately so a regression names its cause
|
|
# rather than only tripping the general check above. The two must match
|
|
# exactly, not merely resolve to the same digest: backupstrategy-controller
|
|
# renders its copy into a Pod spec, so a stale tag string there is what an
|
|
# operator reads when asking what is running.
|
|
a=$(yq -r '.migrations.image' packages/core/platform/values.yaml)
|
|
b=$(yq -r '.backupStrategyController.chBackupClientImage' \
|
|
packages/system/backupstrategy-controller/values.yaml)
|
|
c=$(yq -r '.backupStrategyController.rabbitmqBackupClientImage' \
|
|
packages/system/backupstrategy-controller/values.yaml)
|
|
|
|
if [ "$a" != "$b" ] || [ "$a" != "$c" ]; then
|
|
echo "platform-migrations pins have drifted:" >&2
|
|
echo " packages/core/platform/values.yaml .migrations.image" >&2
|
|
echo " $a" >&2
|
|
echo " backupstrategy-controller/values.yaml .chBackupClientImage" >&2
|
|
echo " $b" >&2
|
|
echo " backupstrategy-controller/values.yaml .rabbitmqBackupClientImage" >&2
|
|
echo " $c" >&2
|
|
echo >&2
|
|
echo "packages/core/platform/Makefile stamps all three; do not hand-edit any." >&2
|
|
return 1
|
|
fi
|
|
}
|