Add catalog-zone-gen tool

Generate RFC 9432 DNS catalog zone files from a declarative input file.
Parses zone-to-catalog assignments with optional group and coo properties,
produces deterministic BIND-format output with automatic SOA serial
management and change detection.
This commit is contained in:
2026-02-28 16:13:58 -08:00
parent 5f230676d7
commit 1f2f39f40c
14 changed files with 1944 additions and 0 deletions

76
main.go Normal file
View File

@@ -0,0 +1,76 @@
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"sort"
"time"
)
func main() {
configPath := flag.String("config", "", "path to YAML config (default: catz.yaml next to input file)")
outputDir := flag.String("output-dir", "", "directory for output zone files (default: same as input file)")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: catalog-zone-gen [--config path] [--output-dir path] <input-file>\n")
flag.PrintDefaults()
}
flag.Parse()
if flag.NArg() != 1 {
flag.Usage()
os.Exit(1)
}
inputFile := flag.Arg(0)
inputDir := filepath.Dir(inputFile)
if *configPath == "" {
*configPath = filepath.Join(inputDir, "catz.yaml")
}
if *outputDir == "" {
*outputDir = inputDir
}
cfg, err := loadConfig(*configPath)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %s\n", err)
os.Exit(1)
}
members, err := parseInput(inputFile, cfg)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
now := time.Now().UTC()
// Process catalogs in sorted order for deterministic output
catNames := make([]string, 0, len(members))
for name := range members {
catNames = append(catNames, name)
}
sort.Strings(catNames)
hasErrors := false
for _, catName := range catNames {
changed, err := processCatalog(catName, cfg, members[catName], *outputDir, now)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
hasErrors = true
continue
}
catZone := cfg.Catalogs[catName].Zone
if changed {
fmt.Fprintf(os.Stderr, "%s%s: updated\n", catZone, "zone")
} else {
fmt.Fprintf(os.Stderr, "%s%s: unchanged\n", catZone, "zone")
}
}
if hasErrors {
os.Exit(1)
}
}