Files
bind-operator/internal/bind/seed.go
T
unkinben fb103a9e95
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
Fix zone provisioning: seed glue + IP primaries
Two bugs made every provisioned zone fail to load:

1. The seed zone's apex NS (ns1.<zone>) is in-zone but had no address
   record, so BIND check-integrity refused to load it and rndc addzone
   reverted. Add a glue A record pointing at the primary pod IP.
2. Secondaries rendered primaries/default-primaries with the primary's
   DNS name, but BIND only accepts IP addresses there (it read the name
   as a remote-servers list and failed config load, crash-looping the
   secondary). Render the primary pod IP instead, and watch Pods so the
   config re-renders when that IP appears or changes.

- bind.WriteSeedZone writes 'ns1 IN A <primaryIP>' glue
- controllers resolve primaryPodIP and pass it to the seed (requeue if
  the primary has no IP yet)
- BindCluster renders PrimaryAddress from pod-0's IP and watches Pods
- render omits catalog primaries when the IP is unknown (no empty list)
2026-07-03 21:33:31 +10:00

77 lines
2.8 KiB
Go

package bind
import (
"context"
"fmt"
"strings"
)
// ZoneFilePath returns the on-pod path of a zone database file.
func ZoneFilePath(zone string) string {
return fmt.Sprintf("%s/zones/db.%s", DataDir, strings.TrimSuffix(zone, "."))
}
// CatalogFilePath returns the on-pod path of a catalog zone database file.
func CatalogFilePath(zone string) string {
return fmt.Sprintf("%s/catalog/db.%s", DataDir, strings.TrimSuffix(zone, "."))
}
// ZoneExists reports whether a zone is currently loaded on the pod.
func (e *Executor) ZoneExists(ctx context.Context, namespace, pod, zone, view string) bool {
args := []string{"zonestatus", zone}
if view != "" {
args = append(args, "in", view)
}
_, err := e.Rndc(ctx, namespace, pod, args...)
return err == nil
}
// WriteSeedZone writes a minimal loadable zone file (SOA + apex NS + glue) to
// path, creating parent directories. The apex NS is the in-zone name ns1, and a
// glue A record pointing at primaryIP is included so BIND's check-integrity
// accepts the zone (an in-zone NS without an address record is a load error).
// It is only safe to call when creating a zone, as it overwrites any existing
// file. This is a placeholder that is replaced once real records are loaded.
func (e *Executor) WriteSeedZone(ctx context.Context, namespace, pod, zone, path, primaryIP string, serial int64) error {
origin := dot(zone)
ns := "ns1." + origin
content := fmt.Sprintf(`$TTL 3600
@ IN SOA %s hostmaster.%s (
%d ; serial
3600 ; refresh
900 ; retry
1209600 ; expire
300 ) ; minimum
@ IN NS %s
ns1 IN A %s
`, ns, origin, serial, ns, primaryIP)
cmd := []string{"sh", "-c", fmt.Sprintf("mkdir -p \"$(dirname '%s')\" && cat > '%s'", path, path)}
if out, err := e.Exec(ctx, namespace, pod, cmd, content); err != nil {
return fmt.Errorf("seed zone %s: %w (out: %s)", zone, err, out)
}
return nil
}
// AddCatalogMember registers a member zone in a catalog zone by adding the
// catalog PTR record, so secondaries auto-provision it.
func (e *Executor) AddCatalogMember(ctx context.Context, namespace, pod, catalogZone, memberZone string, creds TSIGCreds) error {
hash := catalogHash(memberZone)
owner := fmt.Sprintf("%s.zones.%s", hash, dot(catalogZone))
updates := []RecordUpdate{{
FQDN: owner,
Type: "PTR",
TTL: 3600,
Values: []string{dot(memberZone)},
}}
return e.NSUpdate(ctx, namespace, pod, catalogZone, creds, updates)
}
// RemoveCatalogMember deregisters a member zone from a catalog zone.
func (e *Executor) RemoveCatalogMember(ctx context.Context, namespace, pod, catalogZone, memberZone string, creds TSIGCreds) error {
hash := catalogHash(memberZone)
owner := fmt.Sprintf("%s.zones.%s", hash, dot(catalogZone))
updates := []RecordUpdate{{FQDN: owner, Type: "PTR", Delete: true}}
return e.NSUpdate(ctx, namespace, pod, catalogZone, creds, updates)
}