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.
187 lines
4.2 KiB
Go
187 lines
4.2 KiB
Go
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package mangling
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
type (
|
|
lexemKind uint8
|
|
|
|
nameLexem struct {
|
|
original string
|
|
matchedInitialism string
|
|
kind lexemKind
|
|
}
|
|
)
|
|
|
|
const (
|
|
lexemKindCasualName lexemKind = iota
|
|
lexemKindInitialismName
|
|
)
|
|
|
|
func newInitialismNameLexem(original, matchedInitialism string) nameLexem {
|
|
return nameLexem{
|
|
kind: lexemKindInitialismName,
|
|
original: original,
|
|
matchedInitialism: matchedInitialism,
|
|
}
|
|
}
|
|
|
|
func newCasualNameLexem(original string) nameLexem {
|
|
return nameLexem{
|
|
kind: lexemKindCasualName,
|
|
original: trim(original), // TODO: save on calls to trim
|
|
}
|
|
}
|
|
|
|
// WriteTitleized writes the titleized lexeme to a bytes.Buffer.
|
|
//
|
|
// If the first letter cannot be capitalized, it doesn't write anything and return false,
|
|
// so the caller may attempt some workaround strategy.
|
|
func (l nameLexem) WriteTitleized(w *bytes.Buffer, alwaysUpper bool) bool {
|
|
if l.kind == lexemKindInitialismName {
|
|
w.WriteString(l.matchedInitialism)
|
|
|
|
return true
|
|
}
|
|
|
|
if len(l.original) == 0 {
|
|
return true
|
|
}
|
|
|
|
if len(l.original) == 1 {
|
|
// identifier is too short: casing will depend on the context
|
|
firstByte := l.original[0]
|
|
switch {
|
|
case 'A' <= firstByte && firstByte <= 'Z':
|
|
// safe
|
|
w.WriteByte(firstByte)
|
|
|
|
return true
|
|
case alwaysUpper && 'a' <= firstByte && firstByte <= 'z':
|
|
w.WriteByte(firstByte - 'a' + 'A')
|
|
|
|
return true
|
|
default:
|
|
|
|
// not a letter: skip and let the caller decide
|
|
return false
|
|
}
|
|
}
|
|
|
|
if firstByte := l.original[0]; firstByte < utf8.RuneSelf {
|
|
// ASCII
|
|
switch {
|
|
case 'A' <= firstByte && firstByte <= 'Z':
|
|
// already an upper case letter
|
|
w.WriteString(l.original)
|
|
|
|
return true
|
|
case 'a' <= firstByte && firstByte <= 'z':
|
|
w.WriteByte(firstByte - 'a' + 'A')
|
|
w.WriteString(l.original[1:])
|
|
|
|
return true
|
|
default:
|
|
// not a good candidate: doesn't start with a letter
|
|
return false
|
|
}
|
|
}
|
|
|
|
// unicode
|
|
firstRune, idx := utf8.DecodeRuneInString(l.original)
|
|
if !unicode.IsLetter(firstRune) || !unicode.IsUpper(unicode.ToUpper(firstRune)) {
|
|
// not a good candidate: doesn't start with a letter
|
|
// or a rune for which case doesn't make sense (e.g. East-Asian runes etc)
|
|
return false
|
|
}
|
|
|
|
rest := l.original[idx:]
|
|
w.WriteRune(unicode.ToUpper(firstRune))
|
|
w.WriteString(strings.ToLower(rest))
|
|
|
|
return true
|
|
}
|
|
|
|
// WriteLower is like write titleized but it writes a lower-case version of the lexeme.
|
|
//
|
|
// Similarly, there is no writing if the casing of the first rune doesn't make sense.
|
|
func (l nameLexem) WriteLower(w *bytes.Buffer, alwaysLower bool) bool {
|
|
if l.kind == lexemKindInitialismName {
|
|
w.WriteString(lower(l.matchedInitialism))
|
|
|
|
return true
|
|
}
|
|
|
|
if len(l.original) == 0 {
|
|
return true
|
|
}
|
|
|
|
if len(l.original) == 1 {
|
|
// identifier is too short: casing will depend on the context
|
|
firstByte := l.original[0]
|
|
switch {
|
|
case 'a' <= firstByte && firstByte <= 'z':
|
|
// safe
|
|
w.WriteByte(firstByte)
|
|
|
|
return true
|
|
case alwaysLower && 'A' <= firstByte && firstByte <= 'Z':
|
|
w.WriteByte(firstByte - 'A' + 'a')
|
|
|
|
return true
|
|
default:
|
|
|
|
// not a letter: skip and let the caller decide
|
|
return false
|
|
}
|
|
}
|
|
|
|
if firstByte := l.original[0]; firstByte < utf8.RuneSelf {
|
|
// ASCII
|
|
switch {
|
|
case 'a' <= firstByte && firstByte <= 'z':
|
|
// already a lower case letter
|
|
w.WriteString(l.original)
|
|
|
|
return true
|
|
case 'A' <= firstByte && firstByte <= 'Z':
|
|
w.WriteByte(firstByte - 'A' + 'a')
|
|
w.WriteString(l.original[1:])
|
|
|
|
return true
|
|
default:
|
|
// not a good candidate: doesn't start with a letter
|
|
return false
|
|
}
|
|
}
|
|
|
|
// unicode
|
|
firstRune, idx := utf8.DecodeRuneInString(l.original)
|
|
if !unicode.IsLetter(firstRune) || !unicode.IsLower(unicode.ToLower(firstRune)) {
|
|
// not a good candidate: doesn't start with a letter
|
|
// or a rune for which case doesn't make sense (e.g. East-Asian runes etc)
|
|
return false
|
|
}
|
|
|
|
rest := l.original[idx:]
|
|
w.WriteRune(unicode.ToLower(firstRune))
|
|
w.WriteString(rest)
|
|
|
|
return true
|
|
}
|
|
|
|
func (l nameLexem) GetOriginal() string {
|
|
return l.original
|
|
}
|
|
|
|
func (l nameLexem) IsInitialism() bool {
|
|
return l.kind == lexemKindInitialismName
|
|
}
|