Files
catz/config.go
Ask Bjørn Hansen 1f2f39f40c 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.
2026-02-28 16:13:58 -08:00

79 lines
1.7 KiB
Go

package main
import (
"fmt"
"os"
"strings"
"gopkg.in/yaml.v3"
)
// CatalogConfig holds the configuration for a single catalog zone.
type CatalogConfig struct {
Zone string `yaml:"zone"`
}
// SOAConfig holds the configurable SOA fields.
type SOAConfig struct {
Mname string `yaml:"mname"`
Rname string `yaml:"rname"`
}
// Config holds the parsed catz.yaml configuration.
type Config struct {
Catalogs map[string]CatalogConfig `yaml:"catalogs"`
SOA SOAConfig `yaml:"soa"`
}
func loadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading config: %w", err)
}
var cfg Config
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parsing config: %w", err)
}
if err := validateConfig(&cfg); err != nil {
return nil, err
}
// Normalize zone FQDNs
for name, cat := range cfg.Catalogs {
cat.Zone = normalizeFQDN(cat.Zone)
cfg.Catalogs[name] = cat
}
cfg.SOA.Mname = normalizeFQDN(cfg.SOA.Mname)
cfg.SOA.Rname = normalizeFQDN(cfg.SOA.Rname)
return &cfg, nil
}
func validateConfig(cfg *Config) error {
if len(cfg.Catalogs) == 0 {
return fmt.Errorf("config: no catalogs defined")
}
for name, cat := range cfg.Catalogs {
if cat.Zone == "" {
return fmt.Errorf("config: catalog %q has no zone defined", name)
}
}
if cfg.SOA.Mname == "" {
return fmt.Errorf("config: soa.mname is required")
}
if cfg.SOA.Rname == "" {
return fmt.Errorf("config: soa.rname is required")
}
return nil
}
func normalizeFQDN(name string) string {
name = strings.ToLower(strings.TrimSpace(name))
if name != "" && !strings.HasSuffix(name, ".") {
name += "."
}
return name
}