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  }