Files
kea-operator/internal/controller/helpers.go
T
unkin-agent e19553b183
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful
Build PR dry-run images with the CA-baked buildx plugin
2026-10-06 00:35:18 +11:00

118 lines
3.7 KiB
Go

package controller
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"sort"
"strings"
"time"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/client"
)
func intstrFromInt(i int) intstr.IntOrString { return intstr.FromInt(i) }
const (
requeueShort = 15 * time.Second
requeueLong = 2 * time.Minute
managedByLabel = "app.kubernetes.io/managed-by"
managedByValue = "kea-operator"
clusterLabel = "kea.unkin.net/cluster"
roleLabel = "kea.unkin.net/role"
// statefulSetPodNameLabel is stamped on every StatefulSet pod by Kubernetes;
// the per-pod ClusterIP Service selects a single pod through it.
statefulSetPodNameLabel = "statefulset.kubernetes.io/pod-name"
finalizer = "kea.unkin.net/finalizer"
defaultAPIImage = "artifactapi.k8s.syd1.au.unkin.net/docker-internal/kea-api:latest"
)
func headlessName(cluster string) string { return cluster + "-headless" }
func serviceName(cluster string) string { return cluster }
func configMapName(cluster string) string { return cluster + "-config" }
func stsName(cluster string) string { return cluster }
// podName is the StatefulSet pod name for an ordinal.
func podName(cluster string, ordinal int) string { return fmt.Sprintf("%s-%d", cluster, ordinal) }
// peerServiceName is the per-pod ClusterIP Service fronting one HA peer's
// ctrl-agent. Its stable ClusterIP is what the HA hook peer URL points at.
func peerServiceName(cluster string, ordinal int) string {
return fmt.Sprintf("%s-peer-%d", cluster, ordinal)
}
// peerURL builds an HA peer control-channel URL. Kea 2.6's HA hook parses the
// host as an IP literal (no DNS), so this must be a stable IP address.
func peerURL(ip string) string { return fmt.Sprintf("http://%s:%d/", ip, 8000) }
func commonLabels(cluster string) map[string]string {
return map[string]string{
managedByLabel: managedByValue,
clusterLabel: cluster,
}
}
// setReady sets the single "Ready" condition, mirroring bind-operator.
func setReady(conds *[]metav1.Condition, gen int64, ok bool, reason, msg string) {
status := metav1.ConditionFalse
if ok {
status = metav1.ConditionTrue
}
meta.SetStatusCondition(conds, metav1.Condition{
Type: "Ready",
Status: status,
ObservedGeneration: gen,
Reason: reason,
Message: msg,
})
}
// configHash returns a stable hash of the named ConfigMap's data. Pods copy
// config out of a projected volume at startup, so a ConfigMap change alone
// never reaches a running pod; stamping this hash on the pod template is what
// rolls the StatefulSet. The input must contain no pod IPs or the roll loops.
func configHash(ctx context.Context, c client.Client, ns, name string) (string, error) {
var cm corev1.ConfigMap
if err := c.Get(ctx, types.NamespacedName{Namespace: ns, Name: name}, &cm); err != nil {
return "", err
}
keys := make([]string, 0, len(cm.Data))
for k := range cm.Data {
keys = append(keys, k)
}
sort.Strings(keys)
var buf bytes.Buffer
for _, k := range keys {
buf.WriteString(k)
buf.WriteByte(0)
buf.WriteString(cm.Data[k])
buf.WriteByte(0)
}
sum := sha256.Sum256(buf.Bytes())
return hex.EncodeToString(sum[:]), nil
}
func int32ptr(i int32) *int32 { return &i }
func ptr[T any](v T) *T { return &v }
// splitPool splits a "start - end" (any spacing) or single-address pool into
// its address fields.
func splitPool(p string) []string {
if !strings.Contains(p, "-") {
return []string{strings.TrimSpace(p)}
}
parts := strings.SplitN(p, "-", 2)
return []string{strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])}
}