Files
bind-operator/internal/controller/util.go
T
unkin-agent 70e5b0ed55
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful
apply config changes via rndc reconfig instead of restarting pods
- symlink named.conf to the projected file so kubelet updates reach named
- drop the config hash from the pod template; hash only the entrypoint
- reconfig each pod once its mounted config matches, tracked in status
- surface reconfig failures with checkconf detail on ConfigApplied
2026-10-16 23:30:51 +11:00

116 lines
3.4 KiB
Go

package controller
import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
ctrl "sigs.k8s.io/controller-runtime"
bindv1alpha1 "git.unkin.net/unkin/bind-operator/api/v1alpha1"
"git.unkin.net/unkin/bind-operator/internal/bind"
)
func intstrFromInt(i int) intstr.IntOrString { return intstr.FromInt(i) }
func podReady(pod *corev1.Pod) bool {
for _, c := range pod.Status.Conditions {
if c.Type == corev1.PodReady {
return c.Status == corev1.ConditionTrue
}
}
return false
}
// entrypointScript links the primary or secondary named.conf (by StatefulSet
// ordinal) and launches named in the foreground. A symlink, not a copy, so rndc
// reconfig reads the projected file kubelet keeps up to date.
func entrypointScript() string {
return fmt.Sprintf(`#!/bin/sh
set -eu
ORD="${HOSTNAME##*-}"
if [ "$ORD" = "0" ]; then
ln -sf %[1]s %[3]s
else
ln -sf %[2]s %[3]s
fi
mkdir -p %[4]s/zones %[4]s/catalog
exec %[5]s -g -c %[3]s
`, bind.NamedConfPrimary, bind.NamedConfSecondary, bind.NamedConfRun, bind.DataDir, bind.NamedBin)
}
func (r *BindClusterReconciler) upsertService(ctx context.Context, c *bindv1alpha1.BindCluster, desired *corev1.Service) error {
if err := ctrl.SetControllerReference(c, desired, r.Scheme); err != nil {
return err
}
var existing corev1.Service
err := r.Get(ctx, types.NamespacedName{Namespace: desired.Namespace, Name: desired.Name}, &existing)
if apierrors.IsNotFound(err) {
return r.Create(ctx, desired)
}
if err != nil {
return err
}
existing.Spec.Ports = desired.Spec.Ports
existing.Spec.Selector = desired.Spec.Selector
existing.Spec.Type = desired.Spec.Type
existing.Spec.LoadBalancerIP = desired.Spec.LoadBalancerIP
existing.Spec.ExternalTrafficPolicy = desired.Spec.ExternalTrafficPolicy
if desired.Annotations != nil {
if existing.Annotations == nil {
existing.Annotations = map[string]string{}
}
for k, v := range desired.Annotations {
existing.Annotations[k] = v
}
}
return r.Update(ctx, &existing)
}
func (r *BindClusterReconciler) upsertConfigMap(ctx context.Context, c *bindv1alpha1.BindCluster, name string, data map[string]string) error {
desired := &corev1.ConfigMap{}
desired.Name = name
desired.Namespace = c.Namespace
desired.Labels = commonLabels(c.Name)
desired.Data = data
if err := ctrl.SetControllerReference(c, desired, r.Scheme); err != nil {
return err
}
var existing corev1.ConfigMap
err := r.Get(ctx, types.NamespacedName{Namespace: c.Namespace, Name: name}, &existing)
if apierrors.IsNotFound(err) {
return r.Create(ctx, desired)
}
if err != nil {
return err
}
existing.Data = data
existing.Labels = commonLabels(c.Name)
return r.Update(ctx, &existing)
}
func (r *BindClusterReconciler) upsertSecret(ctx context.Context, c *bindv1alpha1.BindCluster, name string, data map[string][]byte) error {
desired := &corev1.Secret{}
desired.Name = name
desired.Namespace = c.Namespace
desired.Labels = commonLabels(c.Name)
desired.Data = data
if err := ctrl.SetControllerReference(c, desired, r.Scheme); err != nil {
return err
}
var existing corev1.Secret
err := r.Get(ctx, types.NamespacedName{Namespace: c.Namespace, Name: name}, &existing)
if apierrors.IsNotFound(err) {
return r.Create(ctx, desired)
}
if err != nil {
return err
}
existing.Data = data
existing.Labels = commonLabels(c.Name)
return r.Update(ctx, &existing)
}