mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
NEW PROVIDER: OctoDNS (#309)
* govendor gopkg.in/yaml.v2 * Ignore YAML and BIND test data litter. Create README.txt files to force git to create subdirectories. * Update convertzone to also read OctoDNS files
This commit is contained in:
320
providers/octodns/octoyaml/write.go
Normal file
320
providers/octodns/octoyaml/write.go
Normal file
@@ -0,0 +1,320 @@
|
||||
package octoyaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
"github.com/miekg/dns/dnsutil"
|
||||
"github.com/pkg/errors"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// WriteYaml outputs a yaml version of a list of RecordConfig.
|
||||
func WriteYaml(w io.Writer, records models.Records, origin string) error {
|
||||
if len(records) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pick the most common TTL as the default so we can
|
||||
// write the fewest "ttl:" lines.
|
||||
defaultTTL := mostCommonTTL(records)
|
||||
|
||||
// Make a copy of the records, since we want to sort and muck with them.
|
||||
recsCopy := models.Records{}
|
||||
for _, r := range records {
|
||||
recsCopy = append(recsCopy, r)
|
||||
}
|
||||
for _, r := range recsCopy {
|
||||
if r.Name == "@" {
|
||||
r.Name = ""
|
||||
}
|
||||
}
|
||||
|
||||
z := &genYamlData{
|
||||
Origin: dnsutil.AddOrigin(origin, "."),
|
||||
DefaultTTL: defaultTTL,
|
||||
Records: recsCopy,
|
||||
}
|
||||
|
||||
// Sort in the weird order that OctoDNS expects:
|
||||
sort.Sort(z)
|
||||
|
||||
// Generate the YAML:
|
||||
fmt.Fprintln(w, "---")
|
||||
yb, err := yaml.Marshal(z.genInterfaceList(w))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(yb)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// genInterfaceList outputs YAML ordered slices for the entire zone.
|
||||
// Each item in the list is an interface that will MarshallYAML to
|
||||
// the desired output.
|
||||
func (z *genYamlData) genInterfaceList(w io.Writer) yaml.MapSlice {
|
||||
var yam yaml.MapSlice
|
||||
// Group the records by label.
|
||||
order, groups := z.Records.GroupedByLabel()
|
||||
// For each group, generate the YAML.
|
||||
for _, label := range order {
|
||||
group := groups[label]
|
||||
// Within the group, sort the similar Types together:
|
||||
sort.SliceStable(group, func(i, j int) bool { return zoneRrtypeLess(group[i].Type, group[j].Type) })
|
||||
// Generate the YAML records:
|
||||
yam = append(yam, oneLabel(group))
|
||||
}
|
||||
return yam
|
||||
}
|
||||
|
||||
// "simple" records are when a label has a single rtype.
|
||||
// It may have a single (simple) or multiple (many) values.
|
||||
|
||||
// Used to generate:
|
||||
// label:
|
||||
// type: A
|
||||
// value: 1.2.3.4
|
||||
type simple struct {
|
||||
TTL uint32 `yaml:"ttl,omitempty"`
|
||||
Type string `yaml:"type"`
|
||||
Value string `yaml:"value"`
|
||||
}
|
||||
|
||||
// Used to generate:
|
||||
// label:
|
||||
// type: A
|
||||
// values:
|
||||
// - 1.2.3.4
|
||||
// - 2.3.4.5
|
||||
type many struct {
|
||||
TTL uint32 `yaml:"ttl,omitempty"`
|
||||
Type string `yaml:"type"`
|
||||
Values []string `yaml:"values"`
|
||||
}
|
||||
|
||||
// complexItems are when a single label has multiple rtypes
|
||||
// associated with it. For example, a label with both an A and MX record.
|
||||
type complexItems []interface{}
|
||||
|
||||
// Used to generate a complex item with either a single value or multiple values:
|
||||
// 'thing': >> complexVals
|
||||
// - type: CNAME
|
||||
// value: newplace.example.com. << value
|
||||
// 'www':
|
||||
// - type: A
|
||||
// values:
|
||||
// - 1.2.3.4 << values
|
||||
// - 1.2.3.5 << values
|
||||
// - type: MX
|
||||
// values:
|
||||
// - priority: 10 << fields
|
||||
// value: mx1.example.com. << fields
|
||||
// - priority: 10 << fields
|
||||
// value: mx2.example.com. << fields
|
||||
type complexVals struct {
|
||||
TTL uint32 `yaml:"ttl,omitempty"`
|
||||
Type string `yaml:"type"`
|
||||
Value string `yaml:"value,omitempty"`
|
||||
Values []string `yaml:"values,omitempty"`
|
||||
}
|
||||
|
||||
// Used to generate rtypes like MX rand SRV ecords, which have multiple
|
||||
// fields within the rtype.
|
||||
type complexFields struct {
|
||||
TTL uint32 `yaml:"ttl,omitempty"`
|
||||
Type string `yaml:"type"`
|
||||
Fields []fields `yaml:"values,omitempty"`
|
||||
}
|
||||
|
||||
// Used to generate the fields themselves:
|
||||
type fields struct {
|
||||
Priority uint16 `yaml:"priority,omitempty"`
|
||||
SrvWeight uint16 `yaml:"weight,omitempty"`
|
||||
SrvPort uint16 `yaml:"port,omitempty"`
|
||||
Value string `yaml:"value,omitempty"`
|
||||
}
|
||||
|
||||
// FIXME(tlim): An MX record with .Priority=0 will not output the priority.
|
||||
|
||||
// sameType returns true if all records have the same type.
|
||||
func sameType(records models.Records) bool {
|
||||
t := records[0].Type
|
||||
for _, r := range records {
|
||||
if r.Type != t {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// oneLabel handles all the DNS records associated with a single label.
|
||||
// It dispatches the right code whether the label is simple, many, or complex.
|
||||
func oneLabel(records models.Records) yaml.MapItem {
|
||||
item := yaml.MapItem{
|
||||
// a yaml.MapItem is a YAML map that retains the key order.
|
||||
Key: records[0].Name,
|
||||
}
|
||||
// Special case labels with a single record:
|
||||
if len(records) == 1 {
|
||||
switch rtype := records[0].Type; rtype {
|
||||
case "A", "CNAME", "NS", "PTR", "TXT":
|
||||
v := simple{
|
||||
Type: rtype,
|
||||
Value: records[0].GetTargetField(),
|
||||
TTL: records[0].TTL,
|
||||
}
|
||||
if v.Type == "TXT" {
|
||||
v.Value = strings.Replace(models.StripQuotes(v.Value), `;`, `\;`, -1)
|
||||
}
|
||||
//fmt.Printf("yamlwrite:oneLabel: simple ttl=%d\n", v.TTL)
|
||||
item.Value = v
|
||||
//fmt.Printf("yamlwrite:oneLabel: SIMPLE=%v\n", item)
|
||||
return item
|
||||
case "MX", "SRV":
|
||||
// Always processed as a complex{}
|
||||
default:
|
||||
panic(errors.Errorf("yamlwrite:oneLabel:len1 rtype not implemented: %s", rtype))
|
||||
}
|
||||
}
|
||||
|
||||
// Special case labels with many records, all the same rType:
|
||||
if sameType(records) {
|
||||
switch rtype := records[0].Type; rtype {
|
||||
case "A", "CNAME", "NS":
|
||||
v := many{
|
||||
Type: rtype,
|
||||
TTL: records[0].TTL,
|
||||
}
|
||||
for _, rec := range records {
|
||||
v.Values = append(v.Values, rec.GetTargetField())
|
||||
}
|
||||
item.Value = v
|
||||
//fmt.Printf("SIMPLE=%v\n", item)
|
||||
return item
|
||||
case "MX", "SRV":
|
||||
// Always processed as a complex{}
|
||||
default:
|
||||
panic(errors.Errorf("oneLabel:many rtype not implemented: %s", rtype))
|
||||
}
|
||||
}
|
||||
|
||||
// All other labels are complexItems
|
||||
|
||||
var low int // First index of a run.
|
||||
var lst complexItems
|
||||
var last = records[0].Type
|
||||
for i := range records {
|
||||
if records[i].Type != last {
|
||||
//fmt.Printf("yamlwrite:oneLabel: Calling oneType( [%d:%d] ) last=%s type=%s\n", low, i, last, records[0].Type)
|
||||
lst = append(lst, oneType(records[low:i]))
|
||||
low = i // Current is the first of a run.
|
||||
last = records[i].Type
|
||||
}
|
||||
if i == (len(records) - 1) {
|
||||
// we are on the last element.
|
||||
//fmt.Printf("yamlwrite:oneLabel: Calling oneType( [%d:%d] ) last=%s type=%s\n", low, i+1, last, records[0].Type)
|
||||
lst = append(lst, oneType(records[low:i+1]))
|
||||
}
|
||||
}
|
||||
item.Value = lst
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
// oneType returns interfaces that will MarshalYAML properly for a label with
|
||||
// one or more records, all the same rtype.
|
||||
func oneType(records models.Records) interface{} {
|
||||
//fmt.Printf("yamlwrite:oneType len=%d type=%s\n", len(records), records[0].Type)
|
||||
rtype := records[0].Type
|
||||
switch rtype {
|
||||
case "A", "AAAA", "NS":
|
||||
vv := complexVals{
|
||||
Type: rtype,
|
||||
TTL: records[0].TTL,
|
||||
}
|
||||
if len(records) == 1 {
|
||||
vv.Value = records[0].GetTargetField()
|
||||
} else {
|
||||
for _, rc := range records {
|
||||
vv.Values = append(vv.Values, rc.GetTargetCombined())
|
||||
}
|
||||
}
|
||||
return vv
|
||||
case "MX":
|
||||
vv := complexFields{
|
||||
Type: rtype,
|
||||
TTL: records[0].TTL,
|
||||
}
|
||||
for _, rc := range records {
|
||||
vv.Fields = append(vv.Fields, fields{
|
||||
Value: rc.GetTargetField(),
|
||||
Priority: rc.MxPreference,
|
||||
})
|
||||
}
|
||||
return vv
|
||||
case "SRV":
|
||||
vv := complexFields{
|
||||
Type: rtype,
|
||||
TTL: records[0].TTL,
|
||||
}
|
||||
for _, rc := range records {
|
||||
vv.Fields = append(vv.Fields, fields{
|
||||
Value: rc.GetTargetField(),
|
||||
Priority: rc.SrvPriority,
|
||||
SrvWeight: rc.SrvWeight,
|
||||
SrvPort: rc.SrvPort,
|
||||
})
|
||||
}
|
||||
return vv
|
||||
case "TXT":
|
||||
vv := complexVals{
|
||||
Type: rtype,
|
||||
TTL: records[0].TTL,
|
||||
}
|
||||
if len(records) == 1 {
|
||||
vv.Value = strings.Replace(models.StripQuotes(records[0].GetTargetField()), `;`, `\;`, -1)
|
||||
} else {
|
||||
for _, rc := range records {
|
||||
vv.Values = append(vv.Values, models.StripQuotes(rc.GetTargetCombined()))
|
||||
}
|
||||
}
|
||||
return vv
|
||||
|
||||
default:
|
||||
panic(errors.Errorf("yamlwrite:oneType rtype=%s not implemented", rtype))
|
||||
}
|
||||
}
|
||||
|
||||
// mostCommonTTL returns the most common TTL in a set of records. If there is
|
||||
// a tie, the highest TTL is selected. This makes the results consistent.
|
||||
// NS records are not included in the analysis because Tom said so.
|
||||
func mostCommonTTL(records models.Records) uint32 {
|
||||
// Index the TTLs in use:
|
||||
d := make(map[uint32]int)
|
||||
for _, r := range records {
|
||||
if r.Type != "NS" {
|
||||
d[r.TTL]++
|
||||
}
|
||||
}
|
||||
// Find the largest count:
|
||||
var mc int
|
||||
for _, value := range d {
|
||||
if value > mc {
|
||||
mc = value
|
||||
}
|
||||
}
|
||||
// Find the largest key with that count:
|
||||
var mk uint32
|
||||
for key, value := range d {
|
||||
if value == mc {
|
||||
if key > mk {
|
||||
mk = key
|
||||
}
|
||||
}
|
||||
}
|
||||
return mk
|
||||
}
|
Reference in New Issue
Block a user