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" finalizer = "kea.unkin.net/finalizer" defaultOperatorImage = "git.unkin.net/unkin/kea-operator:latest" defaultAPIImage = "git.unkin.net/unkin/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 } func peerDNS(cluster, ns string, ordinal int) string { return fmt.Sprintf("http://%s-%d.%s.%s:%d/", cluster, ordinal, headlessName(cluster), ns, 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])} }