github.com/philhug/dnscontrol@v0.2.4-0.20180625181521-921fa9849001/cmd/convertzone/main.go (about) 1 package main 2 3 /* 4 convertzone: Read and write DNS zone files. 5 6 convertzone [-in=INPUT] [-out=OUTPUT] zonename [filename] 7 8 Input format: 9 -in=bind BIND-style zonefiles (DEFAULT) 10 -in=octodns OctoDNS YAML "config" files. 11 12 Output format: 13 14 -out=dsl DNSControl DSL language (dnsconfig.js) (DEFAULT) 15 -out=tsv TAB-separated values 16 -out=pretty pretty-printed (BIND-style zonefiles) 17 18 zonename The FQDN of the zone name. 19 filename File to read (optional. Defaults to stdin) 20 21 The DSL output format is useful for creating the first 22 draft of your dnsconfig.js when importing zones from 23 other services. 24 25 The TSV format makes it easy to process a zonefile with 26 shell tools. `awk -F"\t" $2 = "A" { print $3 }` 27 28 The PRETTY format is just a nice way to clean up a zonefile. 29 30 If no filename is specified, stdin is assumed. 31 Output is sent to stdout. 32 33 The zonename is required as it can not be guessed automatically from the input. 34 */ 35 36 import ( 37 "bufio" 38 "flag" 39 "fmt" 40 "io" 41 "log" 42 "os" 43 "strconv" 44 "strings" 45 46 "github.com/StackExchange/dnscontrol/providers/bind" 47 "github.com/StackExchange/dnscontrol/providers/octodns/octoyaml" 48 "github.com/miekg/dns" 49 "github.com/miekg/dns/dnsutil" 50 "github.com/pkg/errors" 51 ) 52 53 var flagInfmt = flag.String("in", "zone", "zone|octodns") 54 var flagOutfmt = flag.String("out", "dsl", "dsl|tsv|pretty") 55 var flagDefaultTTL = flag.Uint("ttl", 300, "Default TTL") 56 var flagRegText = flag.String("registrar", "REG_FILL_IN", "registrar text") 57 var flagProviderText = flag.String("provider", "DNS_FILL_IN", "provider text") 58 59 func main() { 60 flag.Parse() 61 zonename, filename, reader, err := parseargs(flag.Args()) 62 if err != nil { 63 fmt.Printf("ERROR: %v\n\n", err) 64 fmt.Println("convertzone [-flags] ZONENAME FILENAME") 65 flag.Usage() 66 os.Exit(1) 67 } 68 69 defTTL := uint32(*flagDefaultTTL) 70 71 var recs []dns.RR 72 73 // Read it in: 74 75 switch *flagInfmt { 76 case "zone": 77 recs = readZone(zonename, reader, filename) 78 case "oct", "octo", "octodns": 79 recs = readOctodns(zonename, reader, filename) 80 } 81 82 // Write it out: 83 84 switch *flagOutfmt { 85 case "pretty": 86 bind.WriteZoneFile(os.Stdout, recs, zonename) 87 case "dsl": 88 fmt.Printf(`D("%s", %s, DnsProvider(%s)`, zonename, *flagRegText, *flagProviderText) 89 rrFormat(zonename, filename, recs, defTTL, true) 90 fmt.Println("\n)") 91 case "tsv": 92 rrFormat(zonename, filename, recs, defTTL, false) 93 default: 94 fmt.Println("convertzone [-flags] ZONENAME FILENAME") 95 flag.Usage() 96 } 97 98 } 99 100 // parseargs parses the non-flag arguments. 101 func parseargs(args []string) (zonename string, filename string, r io.Reader, err error) { 102 // 1 args: first arg is the zonename. Read stdin. 103 // 2 args: first arg is the zonename. 2nd is the filename. 104 // Anything else returns an error. 105 106 if len(args) < 2 { 107 return "", "", nil, errors.Errorf("no command line parameters. Zone name required") 108 } 109 110 zonename = args[0] 111 112 if len(args) == 1 { 113 filename = "stdin" 114 r = bufio.NewReader(os.Stdin) 115 } else if len(args) == 2 { 116 filename = flag.Arg(1) 117 r, err = os.Open(filename) 118 if err != nil { 119 return "", "", nil, errors.Wrapf(err, "Could not open file: %s", filename) 120 } 121 } else { 122 return "", "", nil, errors.Errorf("too many command line parameters") 123 } 124 125 return zonename, filename, r, nil 126 } 127 128 func readZone(zonename string, r io.Reader, filename string) []dns.RR { 129 var l []dns.RR 130 for x := range dns.ParseZone(r, zonename, filename) { 131 if x.Error != nil { 132 log.Println(x.Error) 133 } else { 134 l = append(l, x.RR) 135 } 136 } 137 return l 138 } 139 140 func readOctodns(zonename string, r io.Reader, filename string) []dns.RR { 141 var l []dns.RR 142 143 foundRecords, err := octoyaml.ReadYaml(r, zonename) 144 if err != nil { 145 log.Println(errors.Wrapf(err, "can not get corrections")) 146 } 147 148 for _, x := range foundRecords { 149 l = append(l, x.ToRR()) 150 } 151 return l 152 } 153 154 // pretty outputs the zonefile using the prettyprinter. 155 func writePretty(zonename string, recs []dns.RR, defaultTTL uint32) { 156 bind.WriteZoneFile(os.Stdout, recs, zonename) 157 } 158 159 // rrFormat outputs the zonefile in either DSL or TSV format. 160 func rrFormat(zonename string, filename string, recs []dns.RR, defaultTTL uint32, dsl bool) { 161 zonenamedot := zonename + "." 162 163 for _, x := range recs { 164 165 // Skip comments. Parse the formatted version. 166 line := x.String() 167 if line[0] == ';' { 168 continue 169 } 170 items := strings.SplitN(line, "\t", 5) 171 if len(items) < 5 { 172 log.Fatalf("Too few items in: %v", line) 173 } 174 175 target := items[4] 176 177 hdr := x.Header() 178 nameFqdn := hdr.Name 179 name := dnsutil.TrimDomainName(nameFqdn, zonenamedot) 180 ttl := strconv.FormatUint(uint64(hdr.Ttl), 10) 181 classStr := dns.ClassToString[hdr.Class] 182 typeStr := dns.TypeToString[hdr.Rrtype] 183 184 // MX records should split out the prio vs. target. 185 if hdr.Rrtype == dns.TypeMX { 186 target = strings.Replace(target, " ", "\t", 1) 187 } 188 189 var ttlop string 190 if hdr.Ttl == defaultTTL { 191 ttlop = "" 192 } else { 193 ttlop = fmt.Sprintf(", TTL(%d)", hdr.Ttl) 194 } 195 196 // NS records at the apex should be NAMESERVER() records. 197 if hdr.Rrtype == dns.TypeNS && name == "@" { 198 fmt.Printf(",\n\tNAMESERVER('%s'%s)", target, ttlop) 199 continue 200 } 201 202 if !dsl { // TSV format: 203 fmt.Printf("%s\t%s\t%s\t%s\t%s\n", name, ttl, classStr, typeStr, target) 204 } else { // DSL format: 205 switch hdr.Rrtype { // #rtype_variations 206 case dns.TypeMX: 207 m := strings.SplitN(target, "\t", 2) 208 target = m[0] + ", '" + m[1] + "'" 209 case dns.TypeSOA: 210 continue 211 case dns.TypeTXT: 212 if len(x.(*dns.TXT).Txt) == 1 { 213 target = `'` + x.(*dns.TXT).Txt[0] + `'` 214 } else { 215 target = `['` + strings.Join(x.(*dns.TXT).Txt, `', '`) + `']` 216 } 217 default: 218 target = "'" + target + "'" 219 } 220 fmt.Printf(",\n\t%s('%s', %s%s)", typeStr, name, target, ttlop) 221 } 222 } 223 224 }