github.com/teknogeek/dnscontrol/v2@v2.10.1-0.20200227202244-ae299b55ba42/providers/octodns/octoyaml/write.go (about) 1 package octoyaml 2 3 import ( 4 "fmt" 5 "io" 6 "sort" 7 "strings" 8 9 "github.com/StackExchange/dnscontrol/v2/models" 10 "github.com/miekg/dns/dnsutil" 11 yaml "gopkg.in/yaml.v2" 12 ) 13 14 // WriteYaml outputs a yaml version of a list of RecordConfig. 15 func WriteYaml(w io.Writer, records models.Records, origin string) error { 16 if len(records) == 0 { 17 return nil 18 } 19 20 // Pick the most common TTL as the default so we can 21 // write the fewest "ttl:" lines. 22 defaultTTL := mostCommonTTL(records) 23 24 // Make a copy of the records, since we want to sort and muck with them. 25 recsCopy := models.Records{} 26 for _, r := range records { 27 recsCopy = append(recsCopy, r) 28 } 29 for _, r := range recsCopy { 30 if r.GetLabel() == "@" { 31 //r.Name = "" 32 r.UnsafeSetLabelNull() 33 } 34 } 35 36 z := &genYamlData{ 37 Origin: dnsutil.AddOrigin(origin, "."), 38 DefaultTTL: defaultTTL, 39 Records: recsCopy, 40 } 41 42 // Sort in the weird order that OctoDNS expects: 43 sort.Sort(z) 44 45 // Generate the YAML: 46 fmt.Fprintln(w, "---") 47 yb, err := yaml.Marshal(z.genInterfaceList(w)) 48 if err != nil { 49 return err 50 } 51 _, err = w.Write(yb) 52 53 return err 54 } 55 56 // genInterfaceList outputs YAML ordered slices for the entire zone. 57 // Each item in the list is an interface that will MarshallYAML to 58 // the desired output. 59 func (z *genYamlData) genInterfaceList(w io.Writer) yaml.MapSlice { 60 var yam yaml.MapSlice 61 // Group the records by label. 62 order, groups := z.Records.GroupedByLabel() 63 // For each group, generate the YAML. 64 for _, label := range order { 65 group := groups[label] 66 // Within the group, sort the similar Types together: 67 sort.SliceStable(group, func(i, j int) bool { return zoneRrtypeLess(group[i].Type, group[j].Type) }) 68 // Generate the YAML records: 69 yam = append(yam, oneLabel(group)) 70 } 71 return yam 72 } 73 74 // "simple" records are when a label has a single rtype. 75 // It may have a single (simple) or multiple (many) values. 76 77 // Used to generate: 78 // label: 79 // type: A 80 // value: 1.2.3.4 81 type simple struct { 82 TTL uint32 `yaml:"ttl,omitempty"` 83 Type string `yaml:"type"` 84 Value string `yaml:"value"` 85 } 86 87 // Used to generate: 88 // label: 89 // type: A 90 // values: 91 // - 1.2.3.4 92 // - 2.3.4.5 93 type many struct { 94 TTL uint32 `yaml:"ttl,omitempty"` 95 Type string `yaml:"type"` 96 Values []string `yaml:"values"` 97 } 98 99 // complexItems are when a single label has multiple rtypes 100 // associated with it. For example, a label with both an A and MX record. 101 type complexItems []interface{} 102 103 // Used to generate a complex item with either a single value or multiple values: 104 // 'thing': >> complexVals 105 // - type: CNAME 106 // value: newplace.example.com. << value 107 // 'www': 108 // - type: A 109 // values: 110 // - 1.2.3.4 << values 111 // - 1.2.3.5 << values 112 // - type: MX 113 // values: 114 // - priority: 10 << fields 115 // value: mx1.example.com. << fields 116 // - priority: 10 << fields 117 // value: mx2.example.com. << fields 118 type complexVals struct { 119 TTL uint32 `yaml:"ttl,omitempty"` 120 Type string `yaml:"type"` 121 Value string `yaml:"value,omitempty"` 122 Values []string `yaml:"values,omitempty"` 123 } 124 125 // Used to generate rtypes like MX rand SRV ecords, which have multiple 126 // fields within the rtype. 127 type complexFields struct { 128 TTL uint32 `yaml:"ttl,omitempty"` 129 Type string `yaml:"type"` 130 Fields []fields `yaml:"values,omitempty"` 131 } 132 133 // Used to generate the fields themselves: 134 type fields struct { 135 Priority uint16 `yaml:"priority,omitempty"` 136 SrvWeight uint16 `yaml:"weight,omitempty"` 137 SrvPort uint16 `yaml:"port,omitempty"` 138 Value string `yaml:"value,omitempty"` 139 } 140 141 // FIXME(tlim): An MX record with .Priority=0 will not output the priority. 142 143 // sameType returns true if all records have the same type. 144 func sameType(records models.Records) bool { 145 t := records[0].Type 146 for _, r := range records { 147 if r.Type != t { 148 return false 149 } 150 } 151 return true 152 } 153 154 // oneLabel handles all the DNS records associated with a single label. 155 // It dispatches the right code whether the label is simple, many, or complex. 156 func oneLabel(records models.Records) yaml.MapItem { 157 item := yaml.MapItem{ 158 // a yaml.MapItem is a YAML map that retains the key order. 159 Key: records[0].GetLabel(), 160 } 161 // Special case labels with a single record: 162 if len(records) == 1 { 163 switch rtype := records[0].Type; rtype { 164 case "A", "CNAME", "NS", "PTR", "TXT": 165 v := simple{ 166 Type: rtype, 167 Value: records[0].GetTargetField(), 168 TTL: records[0].TTL, 169 } 170 if v.Type == "TXT" { 171 v.Value = strings.Replace(models.StripQuotes(v.Value), `;`, `\;`, -1) 172 } 173 //fmt.Printf("yamlwrite:oneLabel: simple ttl=%d\n", v.TTL) 174 item.Value = v 175 //fmt.Printf("yamlwrite:oneLabel: SIMPLE=%v\n", item) 176 return item 177 case "MX", "SRV": 178 // Always processed as a complex{} 179 default: 180 panic(fmt.Errorf("yamlwrite:oneLabel:len1 rtype not implemented: %s", rtype)) 181 } 182 } 183 184 // Special case labels with many records, all the same rType: 185 if sameType(records) { 186 switch rtype := records[0].Type; rtype { 187 case "A", "CNAME", "NS": 188 v := many{ 189 Type: rtype, 190 TTL: records[0].TTL, 191 } 192 for _, rec := range records { 193 v.Values = append(v.Values, rec.GetTargetField()) 194 } 195 item.Value = v 196 //fmt.Printf("SIMPLE=%v\n", item) 197 return item 198 case "MX", "SRV": 199 // Always processed as a complex{} 200 default: 201 panic(fmt.Errorf("oneLabel:many rtype not implemented: %s", rtype)) 202 } 203 } 204 205 // All other labels are complexItems 206 207 var low int // First index of a run. 208 var lst complexItems 209 var last = records[0].Type 210 for i := range records { 211 if records[i].Type != last { 212 //fmt.Printf("yamlwrite:oneLabel: Calling oneType( [%d:%d] ) last=%s type=%s\n", low, i, last, records[0].Type) 213 lst = append(lst, oneType(records[low:i])) 214 low = i // Current is the first of a run. 215 last = records[i].Type 216 } 217 if i == (len(records) - 1) { 218 // we are on the last element. 219 //fmt.Printf("yamlwrite:oneLabel: Calling oneType( [%d:%d] ) last=%s type=%s\n", low, i+1, last, records[0].Type) 220 lst = append(lst, oneType(records[low:i+1])) 221 } 222 } 223 item.Value = lst 224 225 return item 226 } 227 228 // oneType returns interfaces that will MarshalYAML properly for a label with 229 // one or more records, all the same rtype. 230 func oneType(records models.Records) interface{} { 231 //fmt.Printf("yamlwrite:oneType len=%d type=%s\n", len(records), records[0].Type) 232 rtype := records[0].Type 233 switch rtype { 234 case "A", "AAAA", "NS": 235 vv := complexVals{ 236 Type: rtype, 237 TTL: records[0].TTL, 238 } 239 if len(records) == 1 { 240 vv.Value = records[0].GetTargetField() 241 } else { 242 for _, rc := range records { 243 vv.Values = append(vv.Values, rc.GetTargetCombined()) 244 } 245 } 246 return vv 247 case "MX": 248 vv := complexFields{ 249 Type: rtype, 250 TTL: records[0].TTL, 251 } 252 for _, rc := range records { 253 vv.Fields = append(vv.Fields, fields{ 254 Value: rc.GetTargetField(), 255 Priority: rc.MxPreference, 256 }) 257 } 258 return vv 259 case "SRV": 260 vv := complexFields{ 261 Type: rtype, 262 TTL: records[0].TTL, 263 } 264 for _, rc := range records { 265 vv.Fields = append(vv.Fields, fields{ 266 Value: rc.GetTargetField(), 267 Priority: rc.SrvPriority, 268 SrvWeight: rc.SrvWeight, 269 SrvPort: rc.SrvPort, 270 }) 271 } 272 return vv 273 case "TXT": 274 vv := complexVals{ 275 Type: rtype, 276 TTL: records[0].TTL, 277 } 278 if len(records) == 1 { 279 vv.Value = strings.Replace(models.StripQuotes(records[0].GetTargetField()), `;`, `\;`, -1) 280 } else { 281 for _, rc := range records { 282 vv.Values = append(vv.Values, models.StripQuotes(rc.GetTargetCombined())) 283 } 284 } 285 return vv 286 287 default: 288 panic(fmt.Errorf("yamlwrite:oneType rtype=%s not implemented", rtype)) 289 } 290 } 291 292 // mostCommonTTL returns the most common TTL in a set of records. If there is 293 // a tie, the highest TTL is selected. This makes the results consistent. 294 // NS records are not included in the analysis because Tom said so. 295 func mostCommonTTL(records models.Records) uint32 { 296 // Index the TTLs in use: 297 d := make(map[uint32]int) 298 for _, r := range records { 299 if r.Type != "NS" { 300 d[r.TTL]++ 301 } 302 } 303 // Find the largest count: 304 var mc int 305 for _, value := range d { 306 if value > mc { 307 mc = value 308 } 309 } 310 // Find the largest key with that count: 311 var mk uint32 312 for key, value := range d { 313 if value == mc { 314 if key > mk { 315 mk = key 316 } 317 } 318 } 319 return mk 320 }