package engine import ( "fmt" "slices" "strings" "jarl/internal/model" ) // ViewSet is what one view has for a name and type. type ViewSet struct { View string `json:"view"` ZoneID int64 `json:"zone_id"` Records []string `json:"records"` // data (with TTL and "(disabled)" when set), sorted IDs []int64 `json:"ids"` // the records, in the same order Linked string `json:"linked,omitempty"` // shared or local, in a linked zone } // CompareRow is one name and type across views. type CompareRow struct { Name string `json:"name"` // relative to the zone FQDN string `json:"fqdn"` Type string `json:"type"` Same bool `json:"same"` Views []ViewSet `json:"views"` // in the order of the zones given } // CompareZones lines up the records of zones of the same name in // different views (docs/views.md#linked-zones), one row per name and type, // sorted like a zone file. func CompareZones(st *model.State, zones []*model.Zone) []CompareRow { type key struct{ name, typ string } sets := make([]map[key][]*model.Record, len(zones)) var keys []key seen := map[key]bool{} var all []*model.Record for i, z := range zones { sets[i] = map[key][]*model.Record{} for _, r := range st.ZoneRecords(z.ID) { k := key{r.Name, r.Type} sets[i][k] = append(sets[i][k], r) if !seen[k] { seen[k] = true keys = append(keys, k) all = append(all, r) } } } if len(zones) > 0 { // Zone file order: the apex first, then by name and type. model.SortRecords(all, zones[0]) keys = keys[:0] for _, r := range all { keys = append(keys, key{r.Name, r.Type}) } } var out []CompareRow for _, k := range keys { row := CompareRow{FQDN: k.name, Type: k.typ, Same: true} if len(zones) > 0 { row.Name = relName(k.name, zones[0].Name) } var first string for i, z := range zones { vs := ViewSet{View: st.ViewName(z), ZoneID: z.ID, Records: []string{}, IDs: []int64{}} rs := slices.Clone(sets[i][k]) slices.SortFunc(rs, func(a, b *model.Record) int { return strings.Compare(strings.ToLower(a.Data), strings.ToLower(b.Data)) }) for _, r := range rs { d := r.Data if r.TTL != nil { d = fmt.Sprintf("%s (TTL %d)", d, *r.TTL) } if r.Disabled { d += " (disabled)" } vs.Records = append(vs.Records, d) vs.IDs = append(vs.IDs, r.ID) } if z.LinkGroup != 0 && len(rs) > 0 { vs.Linked = "shared" if rs[0].SharedID == 0 { vs.Linked = "local" } } sig := strings.Join(vs.Records, "\n") if i == 0 { first = sig } else if sig != first { row.Same = false } row.Views = append(row.Views, vs) } out = append(out, row) } return out } func relName(name, zone string) string { if name == zone { return "@" } return strings.TrimSuffix(name, "."+zone) }