Merge readExistingSerial and readExistingContent into a single readExisting function to eliminate duplicate file I/O. Extract dateBase helper to deduplicate serial formula between defaultSerial and bumpSerial. Cache hash results during collision check to avoid recomputing per member. Normalize error prefixes (remove "error:" from fmt.Errorf, add uniformly at print sites). Use filepath.Join instead of manual "/" concatenation. Replace trivial containsStr wrapper with strings.Contains. Simplify tokenize to a single return. Use writeTestFile and fixedTime helpers consistently in tests.
128 lines
3.3 KiB
Go
128 lines
3.3 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// ZoneEntry represents a parsed line from the input file.
|
|
type ZoneEntry struct {
|
|
Zone string // Normalized FQDN
|
|
Catalogs []string // Catalog names (bare names from input)
|
|
Group string // Optional RFC 9432 group property
|
|
COO string // Optional RFC 9432 change-of-ownership FQDN
|
|
File string // Source file
|
|
Line int // Source line number
|
|
}
|
|
|
|
// CatalogMembers groups zone entries by catalog name.
|
|
type CatalogMembers map[string][]ZoneEntry
|
|
|
|
func parseInput(path string, cfg *Config) (CatalogMembers, error) {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("opening input: %w", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
var entries []ZoneEntry
|
|
scanner := bufio.NewScanner(f)
|
|
lineNum := 0
|
|
for scanner.Scan() {
|
|
lineNum++
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
|
|
entry, err := parseLine(line, path, lineNum)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
entries = append(entries, entry)
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, fmt.Errorf("reading %s: %w", path, err)
|
|
}
|
|
|
|
return buildCatalogMembers(entries, cfg)
|
|
}
|
|
|
|
func parseLine(line, file string, lineNum int) (ZoneEntry, error) {
|
|
// Split on whitespace first, then handle comma separation within tokens
|
|
tokens := tokenize(line)
|
|
if len(tokens) < 2 {
|
|
return ZoneEntry{}, fmt.Errorf("%s:%d: expected zone name followed by at least one catalog name", file, lineNum)
|
|
}
|
|
|
|
entry := ZoneEntry{
|
|
Zone: normalizeFQDN(tokens[0]),
|
|
File: file,
|
|
Line: lineNum,
|
|
}
|
|
|
|
for _, tok := range tokens[1:] {
|
|
if strings.Contains(tok, "=") {
|
|
key, value, _ := strings.Cut(tok, "=")
|
|
switch strings.ToLower(key) {
|
|
case "group":
|
|
if value == "" {
|
|
return ZoneEntry{}, fmt.Errorf("%s:%d: empty group value", file, lineNum)
|
|
}
|
|
entry.Group = value
|
|
case "coo":
|
|
if value == "" {
|
|
return ZoneEntry{}, fmt.Errorf("%s:%d: empty coo value", file, lineNum)
|
|
}
|
|
entry.COO = normalizeFQDN(value)
|
|
default:
|
|
return ZoneEntry{}, fmt.Errorf("%s:%d: unknown property %q", file, lineNum, key)
|
|
}
|
|
} else {
|
|
// Bare name = catalog assignment
|
|
entry.Catalogs = append(entry.Catalogs, tok)
|
|
}
|
|
}
|
|
|
|
if len(entry.Catalogs) == 0 {
|
|
return ZoneEntry{}, fmt.Errorf("%s:%d: no catalog assignment for zone %s", file, lineNum, entry.Zone)
|
|
}
|
|
|
|
return entry, nil
|
|
}
|
|
|
|
// tokenize splits a line on whitespace and commas, stripping empty tokens.
|
|
func tokenize(line string) []string {
|
|
return strings.Fields(strings.ReplaceAll(line, ",", " "))
|
|
}
|
|
|
|
func buildCatalogMembers(entries []ZoneEntry, cfg *Config) (CatalogMembers, error) {
|
|
members := make(CatalogMembers)
|
|
|
|
// Track duplicates: catalog -> zone -> line
|
|
seen := make(map[string]map[string]int)
|
|
|
|
for _, entry := range entries {
|
|
for _, catName := range entry.Catalogs {
|
|
if _, ok := cfg.Catalogs[catName]; !ok {
|
|
return nil, fmt.Errorf("%s:%d: unknown catalog %q", entry.File, entry.Line, catName)
|
|
}
|
|
|
|
if seen[catName] == nil {
|
|
seen[catName] = make(map[string]int)
|
|
}
|
|
if prevLine, dup := seen[catName][entry.Zone]; dup {
|
|
return nil, fmt.Errorf("%s:%d: zone %s already assigned to catalog %q at line %d",
|
|
entry.File, entry.Line, entry.Zone, catName, prevLine)
|
|
}
|
|
seen[catName][entry.Zone] = entry.Line
|
|
|
|
members[catName] = append(members[catName], entry)
|
|
}
|
|
}
|
|
|
|
return members, nil
|
|
}
|