package localzone import ( "fmt" "strings" ) // RenderZone renders a complete zone from declared records, for sources that // own a zone outright (a Kubernetes DNSZone) rather than editing a file in // place. It runs the same checks the console writer does: every record goes // through newRecord, a delegation may not shadow data, and the rendering is // re-parsed and compared with the records before it is returned, so a renderer // bug surfaces as an error here and not as a zone that silently stopped // answering. // // serial is the SOA serial; 0 means the writer's default. A declarative source // should pass something that changes exactly when the spec does, so two // replicas rendering the same object produce the same bytes. func RenderZone(origin string, def ZoneDefaults, recs []Record, serial uint32) (string, error) { origin, err := NormalizeOrigin(origin) if err != nil { return "", fmt.Errorf("zone name: %w", err) } f, err := newFileState(origin, def) if err != nil { return "", err } for _, in := range recs { rec, err := newRecord(origin, in.Name, in.Type, ttlOr(in.TTL, f.ttl), in.Value) if err != nil { return "", err } for _, have := range f.recs { if have.Key() == rec.Key() && have.Value == rec.Value { return "", fmt.Errorf("%s %s is declared twice", rec.Name, rec.Type) } } f.recs = append(f.recs, rec) } if err := f.finish(); err != nil { return "", err } if serial != 0 { f.soa.Serial = serial } text := f.render() check, err := parseFileState(origin, text) if err != nil { return "", fmt.Errorf("the zone file for %s did not render correctly: %w", origin, err) } if err := sameRecords(check.recs, f.recs); err != nil { return "", fmt.Errorf("the zone file for %s did not render correctly: %w", origin, err) } return strings.Replace(text, FileHeader, ZoneSourceHeader, 1), nil } // ZoneSourceHeader tops a rendered zone that an external object owns. const ZoneSourceHeader = `; fancy-dns zone rendered from a Kubernetes DNSZone object. ; ; Regenerated whenever the object changes; edit the DNSZone, not this file. `