package controller import ( "context" "fmt" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" bindv1alpha1 "git.unkin.net/unkin/bind-operator/api/v1alpha1" "git.unkin.net/unkin/bind-operator/internal/bind" ) // BindCatalogZoneReconciler creates and maintains the catalog zone on a cluster // primary so secondaries auto-provision member zones. type BindCatalogZoneReconciler struct { client.Client Scheme *runtime.Scheme Exec *bind.Executor } // +kubebuilder:rbac:groups=bind.unkin.net,resources=bindcatalogzones,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=bind.unkin.net,resources=bindcatalogzones/status,verbs=get;update;patch // +kubebuilder:rbac:groups=bind.unkin.net,resources=bindzones;bindtsigkeys,verbs=get;list;watch func (r *BindCatalogZoneReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { logger := log.FromContext(ctx) var catalog bindv1alpha1.BindCatalogZone if err := r.Get(ctx, req.NamespacedName, &catalog); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } cluster, err := getCluster(ctx, r.Client, catalog.Namespace, catalog.Spec.ClusterRef) if err != nil { return r.fail(ctx, &catalog, "ClusterMissing", err.Error()) } primaryPod := primaryPodName(cluster.Name) if !primaryReady(ctx, r.Client, cluster) || r.Exec == nil { return r.fail(ctx, &catalog, "PrimaryNotReady", "waiting for cluster primary") } creds, err := resolveTSIG(ctx, r.Client, catalog.Namespace, catalog.Spec.TransferKeyRef) if err != nil { return r.fail(ctx, &catalog, "NoTransferKey", err.Error()) } // Ensure the catalog zone exists on the primary. if !r.Exec.ZoneExists(ctx, catalog.Namespace, primaryPod, catalog.Spec.ZoneName, "") { if err := r.Exec.WriteSeedZone(ctx, catalog.Namespace, primaryPod, catalog.Spec.ZoneName, bind.CatalogFilePath(catalog.Spec.ZoneName), "", 1); err != nil { return r.fail(ctx, &catalog, "SeedFailed", err.Error()) } } zoneConfig := fmt.Sprintf("{ type primary; file \"%s\"; allow-transfer { key \"%s\"; }; allow-update { key \"%s\"; }; };", bind.CatalogFilePath(catalog.Spec.ZoneName), catalog.Spec.TransferKeyRef, catalog.Spec.TransferKeyRef) if err := r.Exec.AddZone(ctx, catalog.Namespace, primaryPod, catalog.Spec.ZoneName, "", zoneConfig); err != nil { return r.fail(ctx, &catalog, "AddZoneFailed", err.Error()) } // Catalog zones must advertise their schema version (RFC 9432: "2"). versionUpdate := bind.RecordUpdate{ FQDN: "version." + catalog.Spec.ZoneName + ".", Type: "TXT", TTL: 3600, Values: []string{"\"2\""}, } if err := r.Exec.NSUpdate(ctx, catalog.Namespace, primaryPod, catalog.Spec.ZoneName, creds, []bind.RecordUpdate{versionUpdate}); err != nil { return r.fail(ctx, &catalog, "VersionUpdateFailed", err.Error()) } // Count member zones for status. var zones bindv1alpha1.BindZoneList members := int32(0) if err := r.List(ctx, &zones, client.InNamespace(catalog.Namespace)); err == nil { for i := range zones.Items { z := &zones.Items[i] if z.Spec.ClusterRef == cluster.Name && catalogEnabled(z) { members++ } } } catalog.Status.Ready = true catalog.Status.MemberCount = members catalog.Status.ObservedGeneration = catalog.Generation setReady(&catalog.Status.Conditions, catalog.Generation, true, "Ready", "catalog zone provisioned") if err := r.Status().Update(ctx, &catalog); err != nil { return ctrl.Result{}, err } logger.Info("catalog zone reconciled", "zone", catalog.Spec.ZoneName, "members", members) return ctrl.Result{RequeueAfter: requeueLong}, nil } func (r *BindCatalogZoneReconciler) fail(ctx context.Context, catalog *bindv1alpha1.BindCatalogZone, reason, msg string) (ctrl.Result, error) { catalog.Status.Ready = false catalog.Status.ObservedGeneration = catalog.Generation setReady(&catalog.Status.Conditions, catalog.Generation, false, reason, msg) if err := r.Status().Update(ctx, catalog); err != nil { return ctrl.Result{}, err } return ctrl.Result{RequeueAfter: requeueShort}, nil } func (r *BindCatalogZoneReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&bindv1alpha1.BindCatalogZone{}). Complete(r) }