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  }