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