mirror of
https://github.com/cozystack/cozystack.git
synced 2026-10-28 23:57:08 +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>
84 lines
2.1 KiB
Go
84 lines
2.1 KiB
Go
package template
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
tmpl "text/template"
|
|
|
|
sprig "github.com/go-task/slim-sprig/v3"
|
|
)
|
|
|
|
func Template[T any](obj *T, templateContext map[string]any) (*T, error) {
|
|
b, err := json.Marshal(obj)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var unstructured any
|
|
err = json.Unmarshal(b, &unstructured)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
templateFunc := func(in string) string {
|
|
out, err := template(in, templateContext)
|
|
if err != nil {
|
|
return in
|
|
}
|
|
return out
|
|
}
|
|
unstructured = mapAtStrings(unstructured, templateFunc)
|
|
b, err = json.Marshal(unstructured)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var out T
|
|
err = json.Unmarshal(b, &out)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &out, nil
|
|
}
|
|
|
|
// String renders a single template string against the context and surfaces any
|
|
// parse or execute error. Template walks a struct's string leaves and, by
|
|
// design, leaves a leaf unchanged when its template fails; a caller that gates
|
|
// on the rendered value — a strategy precondition, an artifact URI — needs the
|
|
// error instead, so it can fail loud rather than act on a half-rendered string.
|
|
func String(in string, templateContext map[string]any) (string, error) {
|
|
return template(in, templateContext)
|
|
}
|
|
|
|
func mapAtStrings(v any, f func(string) string) any {
|
|
switch x := v.(type) {
|
|
case map[string]any:
|
|
for k, val := range x {
|
|
x[k] = mapAtStrings(val, f)
|
|
}
|
|
return x
|
|
case []any:
|
|
for i, val := range x {
|
|
x[i] = mapAtStrings(val, f)
|
|
}
|
|
return x
|
|
case string:
|
|
return f(x)
|
|
default:
|
|
return v
|
|
}
|
|
}
|
|
|
|
func template(in string, templateContext map[string]any) (string, error) {
|
|
// FuncMap from slim-sprig is text-template-only (no HTML escaping, which
|
|
// is what we want for Pod spec strings) and excludes network/OS/crypto
|
|
// helpers - the subset matches what strategy authors actually reach for
|
|
// (default, required, quote, printf wrappers, dict/list/index helpers).
|
|
tpl, err := tmpl.New("this").Funcs(sprig.FuncMap()).Parse(in)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
var buf bytes.Buffer
|
|
if err := tpl.Execute(&buf, templateContext); err != nil {
|
|
return "", err
|
|
}
|
|
return buf.String(), nil
|
|
}
|