github.com/teknogeek/dnscontrol/v2@v2.10.1-0.20200227202244-ae299b55ba42/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/miekg/dns" 47 "github.com/miekg/dns/dnsutil" 48 49 "github.com/StackExchange/dnscontrol/v2/pkg/prettyzone" 50 "github.com/StackExchange/dnscontrol/v2/providers/octodns/octoyaml" 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 prettyzone.WriteZoneFileRR(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, fmt.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, fmt.Errorf("Could not open file: %s: %w", filename, err) 120 } 121 } else { 122 return "", "", nil, fmt.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(fmt.Errorf("can not get corrections: %w", err)) 146 } 147 148 for _, x := range foundRecords { 149 l = append(l, x.ToRR()) 150 } 151 return l 152 } 153 154 // rrFormat outputs the zonefile in either DSL or TSV format. 155 func rrFormat(zonename string, filename string, recs []dns.RR, defaultTTL uint32, dsl bool) { 156 zonenamedot := zonename + "." 157 158 for _, x := range recs { 159 160 // Skip comments. Parse the formatted version. 161 line := x.String() 162 if line[0] == ';' { 163 continue 164 } 165 items := strings.SplitN(line, "\t", 5) 166 if len(items) < 5 { 167 log.Fatalf("Too few items in: %v", line) 168 } 169 170 target := items[4] 171 172 hdr := x.Header() 173 nameFqdn := hdr.Name 174 name := dnsutil.TrimDomainName(nameFqdn, zonenamedot) 175 ttl := strconv.FormatUint(uint64(hdr.Ttl), 10) 176 classStr := dns.ClassToString[hdr.Class] 177 typeStr := dns.TypeToString[hdr.Rrtype] 178 179 // MX records should split out the prio vs. target. 180 if hdr.Rrtype == dns.TypeMX { 181 target = strings.Replace(target, " ", "\t", 1) 182 } 183 184 var ttlop string 185 if hdr.Ttl == defaultTTL { 186 ttlop = "" 187 } else { 188 ttlop = fmt.Sprintf(", TTL(%d)", hdr.Ttl) 189 } 190 191 // NS records at the apex should be NAMESERVER() records. 192 if hdr.Rrtype == dns.TypeNS && name == "@" { 193 fmt.Printf(",\n\tNAMESERVER('%s'%s)", target, ttlop) 194 continue 195 } 196 197 if !dsl { // TSV format: 198 fmt.Printf("%s\t%s\t%s\t%s\t%s\n", name, ttl, classStr, typeStr, target) 199 } else { // DSL format: 200 switch hdr.Rrtype { // #rtype_variations 201 case dns.TypeMX: 202 m := strings.SplitN(target, "\t", 2) 203 target = m[0] + ", '" + m[1] + "'" 204 case dns.TypeSOA: 205 continue 206 case dns.TypeTXT: 207 if len(x.(*dns.TXT).Txt) == 1 { 208 target = `'` + x.(*dns.TXT).Txt[0] + `'` 209 } else { 210 target = `['` + strings.Join(x.(*dns.TXT).Txt, `', '`) + `']` 211 } 212 default: 213 target = "'" + target + "'" 214 } 215 fmt.Printf(",\n\t%s('%s', %s%s)", typeStr, name, target, ttlop) 216 } 217 } 218 219 }