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 // Short refresh/retry so a secondary that misses a NOTIFY (e.g. its pod IP // changed and the primary's also-notify was briefly stale) still converges // in minutes, not the hour a 3600s refresh would impose. minimum is the // negative-cache TTL: keep it low so a stale-secondary NXDOMAIN does not // stick in downstream resolvers for long. NOTIFY (also-notify on the // primary) remains the fast path; these are the fallback. content := fmt.Sprintf(`$TTL 3600 @ IN SOA %s hostmaster.%s ( %d ; serial 300 ; refresh 60 ; retry 1209600 ; expire 60 ) ; 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) }