fe5fbdaf6d
Implements a Kubernetes operator that manages fleets of BIND9 servers declaratively, using controller-runtime (matching forgebot conventions). - add BindCluster reconciler: StatefulSet (pod-0 primary, secondaries), headless + client Services, rendered named.conf ConfigMap, TSIG keys Secret and rndc control Secret; watches dependent CRs to re-render - add BindTSIGKey reconciler that generates key material into a Secret - add BindZone/DNSRecord reconcilers using fully-dynamic delivery (rndc addzone + TSIG nsupdate against the primary pod) - add BindCatalogZone reconciler so secondaries auto-provision zones - add BindPolicy (RPZ), BindDNSSECPolicy, BindView, BindACL reconcilers - render primary/secondary named.conf variants selected by pod ordinal - generate CRDs, deepcopy and RBAC; add samples mapping the three Puppet roles (authoritative/resolver/external-dns) to three BindClusters - add Makefile, Dockerfile.operator, Woodpecker CI and kind manifests
89 lines
2.2 KiB
Go
89 lines
2.2 KiB
Go
package controller
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
|
bindv1alpha1 "git.unkin.net/unkin/bind-operator/api/v1alpha1"
|
|
"git.unkin.net/unkin/bind-operator/internal/bind"
|
|
)
|
|
|
|
func isPrimaryType(t bindv1alpha1.ZoneType) bool {
|
|
return t == bindv1alpha1.ZonePrimary || t == ""
|
|
}
|
|
|
|
// catalogEnabled reports whether a primary zone should be registered in the
|
|
// cluster catalog zone.
|
|
func catalogEnabled(zone *bindv1alpha1.BindZone) bool {
|
|
if !isPrimaryType(zone.Spec.Type) {
|
|
return false
|
|
}
|
|
if zone.Spec.Catalog == nil {
|
|
return true
|
|
}
|
|
return *zone.Spec.Catalog
|
|
}
|
|
|
|
// fqdn resolves a record owner name relative to a zone origin.
|
|
func fqdn(name, zone string) string {
|
|
zone = strings.TrimSuffix(zone, ".") + "."
|
|
if name == "" || name == "@" {
|
|
return zone
|
|
}
|
|
if strings.HasSuffix(name, ".") {
|
|
return name
|
|
}
|
|
return name + "." + zone
|
|
}
|
|
|
|
func recordsToUpdates(zone string, records []bindv1alpha1.Record, defaultTTL int32) []bind.RecordUpdate {
|
|
updates := make([]bind.RecordUpdate, 0, len(records))
|
|
for _, rec := range records {
|
|
ttl := defaultTTL
|
|
if rec.TTL != nil {
|
|
ttl = *rec.TTL
|
|
}
|
|
updates = append(updates, bind.RecordUpdate{
|
|
FQDN: fqdn(rec.Name, zone),
|
|
Type: rec.Type,
|
|
TTL: ttl,
|
|
Values: rec.Values,
|
|
})
|
|
}
|
|
return updates
|
|
}
|
|
|
|
// updateKeyName returns the TSIG key name (as used in named.conf) for a zone's
|
|
// update key, falling back to the object name.
|
|
func updateKeyName(ctx context.Context, c client.Client, zone *bindv1alpha1.BindZone) string {
|
|
ref := zone.Spec.UpdateKeyRef
|
|
if ref == "" {
|
|
return ""
|
|
}
|
|
var key bindv1alpha1.BindTSIGKey
|
|
if err := c.Get(ctx, client.ObjectKey{Namespace: zone.Namespace, Name: ref}, &key); err != nil {
|
|
return ref
|
|
}
|
|
if key.Spec.KeyName != "" {
|
|
return key.Spec.KeyName
|
|
}
|
|
return ref
|
|
}
|
|
|
|
// matchListInline renders address-match-list entries on one line.
|
|
func matchListInline(entries []string) string { return terminateInline(entries) }
|
|
|
|
func terminateInline(entries []string) string {
|
|
var parts []string
|
|
for _, e := range entries {
|
|
e = strings.TrimSpace(strings.TrimRight(e, ";"))
|
|
if e == "" {
|
|
continue
|
|
}
|
|
parts = append(parts, e+";")
|
|
}
|
|
return strings.Join(parts, " ")
|
|
}
|