9da206dc7c
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.
139 lines
3.4 KiB
Go
139 lines
3.4 KiB
Go
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package jsonname
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// DefaultJSONNameProvider is the default cache for types.
|
|
var DefaultJSONNameProvider = NewNameProvider()
|
|
|
|
// NameProvider represents an object capable of translating from go property names
|
|
// to json property names.
|
|
//
|
|
// This type is thread-safe.
|
|
//
|
|
// See [github.com/go-openapi/jsonpointer.Pointer] for an example.
|
|
type NameProvider struct {
|
|
lock *sync.Mutex
|
|
index map[reflect.Type]nameIndex
|
|
}
|
|
|
|
type nameIndex struct {
|
|
jsonNames map[string]string
|
|
goNames map[string]string
|
|
}
|
|
|
|
// NewNameProvider creates a new name provider
|
|
func NewNameProvider() *NameProvider {
|
|
return &NameProvider{
|
|
lock: &sync.Mutex{},
|
|
index: make(map[reflect.Type]nameIndex),
|
|
}
|
|
}
|
|
|
|
func buildnameIndex(tpe reflect.Type, idx, reverseIdx map[string]string) {
|
|
for i := 0; i < tpe.NumField(); i++ {
|
|
targetDes := tpe.Field(i)
|
|
|
|
if targetDes.PkgPath != "" { // unexported
|
|
continue
|
|
}
|
|
|
|
if targetDes.Anonymous { // walk embedded structures tree down first
|
|
buildnameIndex(targetDes.Type, idx, reverseIdx)
|
|
continue
|
|
}
|
|
|
|
if tag := targetDes.Tag.Get("json"); tag != "" {
|
|
|
|
parts := strings.Split(tag, ",")
|
|
if len(parts) == 0 {
|
|
continue
|
|
}
|
|
|
|
nm := parts[0]
|
|
if nm == "-" {
|
|
continue
|
|
}
|
|
if nm == "" { // empty string means we want to use the Go name
|
|
nm = targetDes.Name
|
|
}
|
|
|
|
idx[nm] = targetDes.Name
|
|
reverseIdx[targetDes.Name] = nm
|
|
}
|
|
}
|
|
}
|
|
|
|
func newNameIndex(tpe reflect.Type) nameIndex {
|
|
var idx = make(map[string]string, tpe.NumField())
|
|
var reverseIdx = make(map[string]string, tpe.NumField())
|
|
|
|
buildnameIndex(tpe, idx, reverseIdx)
|
|
return nameIndex{jsonNames: idx, goNames: reverseIdx}
|
|
}
|
|
|
|
// GetJSONNames gets all the json property names for a type
|
|
func (n *NameProvider) GetJSONNames(subject any) []string {
|
|
n.lock.Lock()
|
|
defer n.lock.Unlock()
|
|
tpe := reflect.Indirect(reflect.ValueOf(subject)).Type()
|
|
names, ok := n.index[tpe]
|
|
if !ok {
|
|
names = n.makeNameIndex(tpe)
|
|
}
|
|
|
|
res := make([]string, 0, len(names.jsonNames))
|
|
for k := range names.jsonNames {
|
|
res = append(res, k)
|
|
}
|
|
return res
|
|
}
|
|
|
|
// GetJSONName gets the json name for a go property name
|
|
func (n *NameProvider) GetJSONName(subject any, name string) (string, bool) {
|
|
tpe := reflect.Indirect(reflect.ValueOf(subject)).Type()
|
|
return n.GetJSONNameForType(tpe, name)
|
|
}
|
|
|
|
// GetJSONNameForType gets the json name for a go property name on a given type
|
|
func (n *NameProvider) GetJSONNameForType(tpe reflect.Type, name string) (string, bool) {
|
|
n.lock.Lock()
|
|
defer n.lock.Unlock()
|
|
names, ok := n.index[tpe]
|
|
if !ok {
|
|
names = n.makeNameIndex(tpe)
|
|
}
|
|
nme, ok := names.goNames[name]
|
|
return nme, ok
|
|
}
|
|
|
|
// GetGoName gets the go name for a json property name
|
|
func (n *NameProvider) GetGoName(subject any, name string) (string, bool) {
|
|
tpe := reflect.Indirect(reflect.ValueOf(subject)).Type()
|
|
return n.GetGoNameForType(tpe, name)
|
|
}
|
|
|
|
// GetGoNameForType gets the go name for a given type for a json property name
|
|
func (n *NameProvider) GetGoNameForType(tpe reflect.Type, name string) (string, bool) {
|
|
n.lock.Lock()
|
|
defer n.lock.Unlock()
|
|
names, ok := n.index[tpe]
|
|
if !ok {
|
|
names = n.makeNameIndex(tpe)
|
|
}
|
|
nme, ok := names.jsonNames[name]
|
|
return nme, ok
|
|
}
|
|
|
|
func (n *NameProvider) makeNameIndex(tpe reflect.Type) nameIndex {
|
|
names := newNameIndex(tpe)
|
|
n.index[tpe] = names
|
|
return names
|
|
}
|