Implement autobackup-operator (PVC + CNPG controllers, tests, CI) #1
Reference in New Issue
Block a user
Delete Branch "benvin/initial-operator"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Annotation-driven backups (backups.unkin.net/schedule + destination) for PVCs and CloudNativePG Clusters need their S3 bucket and backup-schedule resources provisioned consistently, with no new CRDs. This adds the initial operator.
How:
Notes:
PVCs and CloudNativePG Clusters need S3 buckets and backup schedules provisioned consistently. This operator watches the backups.unkin.net/{schedule,destination} annotations on those objects and provisions everything needed to back them up, with no new CRDs. - Add a PVC controller that provisions cephrgw ObjectStoreUser/Bucket/BucketAccess, auto-generates a restic repo-password Secret and creates a k8up Schedule scoped to the PVC via spec.backup.volumes[].persistentVolumeClaim.claimName. - Add a CNPG Cluster controller that provisions the same bucket stack, idempotently patches spec.backup.barmanObjectStore (leaving a user-set destinationPath alone with a Warning event) and creates a ScheduledBackup. - Resolve destinations through a ConfigMap lookup table; requeue until the BucketAccess is Ready before creating schedule resources; own-reference created resources and retain bucket data by default. - Add schedule-mapping helpers (k8up 5-field/shortcut pass-through, CNPG 6-field seconds-first) and deterministic, length-bounded name derivation. - Add unit tests (schedule mapping, name derivation, destination resolution) and envtest controller tests for both paths, wiring the external CRDs into envtest. - Add kubebuilder-generated RBAC, a Dockerfile (distroless/nonroot), Woodpecker lint/test/build pipelines and a tag-triggered image push to the artifactapi docker-internal registry, plus a version-bump Makefile and deploy manifests.Review (advisory; not merging). Code is correct against the real target CRDs (k8up Schedule backend.s3/repoPasswordSecretRef/backup.volumes[].persistentVolumeClaim.claimName; CNPG barmanObjectStore + retentionPolicy at spec.backup.retentionPolicy; ScheduledBackup cluster.name/schedule/method/backupOwnerReference all verified against schemas), build+vet clean, and unit + envtest controller tests pass and genuinely assert the wiring (incl. requeue-until-Ready gating and the non-clobber destinationPath conflict path with event). A few findings:
HIGH — leader election will crash on startup.
config/manager/manager.yamlruns with--leader-elect(LeaderElectionIDautobackup-operator), but the RBAC grants nocoordination.k8s.io/leasespermission and there is no leader-election Role anywhere. controller-runtime`s default lease lock will be forbidden creating/updating its Lease and the manager will exit. Add the standard kubebuilder leader-election Role (leases: get;list;watch;create;update;patch;delete + events) bound to the SA in the operator namespace, or add those verbs to the ClusterRole.MED — Go toolchain mismatch. go.mod says
go 1.26.5but the CI steps usegolang:1.25and Dockerfile.operator usesgolang:1.25-alpine. With GOTOOLCHAIN=auto this silently downloads the 1.26.5 toolchain at build/test time (breaking the Dockerfile`s explicit "no network needed" hermetic claim and making the image pins moot); with no egress it fails. Bump the CI + Dockerfile base images to golang:1.26, or relax the go directive.MED — tag release push has no registry credentials.
.woodpecker/docker.yamlpushes to artifactapi.k8s.syd1.au.unkin.net/docker-internal with no username/password/secret. Confirm anonymous in-cluster push is allowed, otherwise wire a Vault/woodpecker push secret.LOW — PVC controller has no annotation predicate, so every unannotated PVC in the cluster triggers a reconcile + a NotFound-ignored Schedule delete each resync (minor churn). Cron validator also rejects alphabetic month/day names (MON/JAN) that k8up + robfig/cron accept.
Atomicity: fine as a single bootstrap — both controllers share the bucket-stack/destinations/naming machinery and cant be meaningfully split; the huge diff is almost entirely vendor/. Not a split candidate.
Pushed fixes for the review + acceptance findings (head
a3339a3):coordination.k8s.io/leases+ events) and RoleBinding to the operator SA (config/rbac/leader_election_role*.yaml). Verified in a kind cluster with--leader-electon: lease acquired, both controllersStarting workers, no moreleases ... forbidden.Dockerfile.operator→golang:1.26-alpine, CI images →golang:1.26to matchgo.mod(go 1.26.5), plusGOTOOLCHAIN=localso the image build is hermetic.docker build -f Dockerfile.operator .succeeds with no toolchain download.docker-internalregistry (in-cluster runner has push access). Kept the approach; added a comment noting the precedent.backups.unkin.net/scheduleenqueue; teardown-on-removal still fires. Unit test added.MON,JAN,MON-FRI) that k8up/robfig accept, still rejecting unknown names; tests added for both the k8up and CNPG paths.config/kustomization.yaml(Namespace first);kubectl apply -k confignow works clean. Documented in README.go build/go vet/gofmt/go test -race ./...(incl. envtest) all green.