func constrcutZones(rz rawZones, serverName string) (zones, auth, error) {
	a := make(auth)
	z := make(zones)
	for zoneName, hosts := range rz.Zones {
		zoneName = dns.Fqdn(zoneName)
		soa, err := dns.NewRR(fmt.Sprintf("%s SOA %s dns.%s  %s 10000 2400 604800 3600", zoneName, serverName, serverName, time.Now().Format("0601021504")))
		if err != nil {
			return nil, nil, err
		}
		z[zoneName] = map[uint16][]dns.RR{
			dns.TypeSOA: []dns.RR{soa},
		}

		authRR, err := dns.NewRR(fmt.Sprintf("%s NS %s", zoneName, serverName))
		if err != nil {
			return nil, nil, fmt.Errorf("Failed to create authority NS record: %v", err)
		}

		for host, records := range hosts {
			host = dns.Fqdn(host)
			if _, present := z[host]; !present {
				z[host] = make(map[uint16][]dns.RR)
			}

			for typeStr, v := range records {
				rType, present := dns.StringToType[strings.ToUpper(typeStr)]
				if !present {
					return nil, nil, fmt.Errorf("Invalid record type")
				}

				for _, presentation := range v {
					rr, err := dns.NewRR(fmt.Sprintf("%s %s %s", host, strings.ToUpper(typeStr), presentation))
					if err != nil {
						return nil, nil, fmt.Errorf("Couldn't parse record: %v", err)
					}
					z[host][rType] = append(z[host][rType], rr)
					a[host] = &authRR
				}
			}
		}
	}
	return z, a, nil
}
// Retrieve the DNSKEY records of a zone and convert them
// to DS records for SHA1, SHA256 and SHA384.
func ExampleDS(zone string) {
	config, _ := dns.ClientConfigFromFile("/etc/resolv.conf")
	c := new(dns.Client)
	m := new(dns.Msg)
	if zone == "" {
		zone = "miek.nl"
	}
	m.SetQuestion(dns.Fqdn(zone), dns.TypeDNSKEY)
	m.SetEdns0(4096, true)
	r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port)
	if err != nil {
		return
	}
	if r.Rcode != dns.RcodeSuccess {
		return
	}
	for _, k := range r.Answer {
		if key, ok := k.(*dns.DNSKEY); ok {
			for _, alg := range []uint8{dns.SHA1, dns.SHA256, dns.SHA384} {
				fmt.Printf("%s; %d\n", key.ToDS(alg).String(), key.Flags)
			}
		}
	}
}
func main() {
	app := cli.NewApp()
	app.Name = "dns-workbench"
	app.Usage = "a simple authoritative DNS workbench server"
	app.Authors = []cli.Author{cli.Author{Name: "Roland Shoemaker", Email: "*****@*****.**"}}
	app.Version = "0.0.1"

	app.Flags = []cli.Flag{}
	app.Commands = []cli.Command{
		{
			Name:  "run",
			Usage: "Starts the DNS server",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "dns-name",
					Value: "localhost",
					Usage: "Hostname of the DNS server",
				},
				cli.StringFlag{
					Name:  "dns-address",
					Value: "127.0.0.1",
					Usage: "Address for the DNS server to listen on",
				},
				cli.StringFlag{
					Name:  "dns-port",
					Value: "8053",
					Usage: "Port for the DNS server to listen on",
				},
				cli.StringFlag{
					Name:  "dns-network",
					Value: "udp",
					Usage: "Network for the DNS server to listen on",
				},
				cli.BoolFlag{
					Name:  "dns-compression",
					Usage: "Use DNS message compression",
				},
				cli.StringFlag{
					Name:  "zone-file",
					Usage: "Path to workbench zones file",
				},
				cli.StringFlag{
					Name:  "api-uri",
					Value: "127.0.0.1:5353",
					Usage: "Address for the HTTP API to listen on",
				},
				cli.BoolTFlag{
					Name:  "disable-api",
					Usage: "Don't start the HTTP API",
				},
			},
			Action: func(c *cli.Context) {
				var rz rawZones
				var z zones
				var a auth
				var err error

				logger := log.New(os.Stdout, "[dns-wb] ", log.Flags())

				if rzFile := c.String("zone-file"); rzFile != "" {
					rz, err = loadYAML(rzFile)
					if err != nil {
						logger.Fatalf("Failed to read zone file: %s\n", err)
					}
					z, a, err = constrcutZones(rz, dns.Fqdn(c.String("dns-name")))
					if err != nil {
						logger.Fatalf("Failed to parse zone file: %s\n", err)
					}
				} else {
					z = make(zones)
					a = make(auth)
				}

				wb := workbench{
					z:           z,
					a:           a,
					l:           logger,
					name:        dns.Fqdn(c.String("dns-name")),
					bind:        c.String("dns-address"),
					port:        c.String("dns-port"),
					net:         c.String("dns-network"),
					rTimeout:    time.Second * 2,
					wTimeout:    time.Second * 2,
					iTimeout:    time.Second * 8,
					compression: c.Bool("dns-compression"),
				}
				go func() {
					http.HandleFunc("/api/reload", wb.apiReload)
					logger.Printf("API listening on %s\n", c.String("api-uri"))
					err := http.ListenAndServe(c.String("api-uri"), nil)
					if err != nil {
						logger.Printf("HTTP API crashed: %s\n", err)
						// Dont' exit
					}
				}()
				err = wb.serveWorkbench()
				if err != nil {
					logger.Fatalf("DNS server crashed: %s\n", err)
					os.Exit(1)
				}
			},
		},
		{
			Name:  "reload",
			Usage: "Loads a new zone file into a running workbench",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "zone-file",
					Usage: "Path to workbench zones file",
				},
				cli.StringFlag{
					Name:  "api-uri",
					Value: "127.0.0.1:5353",
					Usage: "Address for the HTTP API",
				},
			},
			Action: func(c *cli.Context) {
				logger := log.New(os.Stdout, "[dns-wb] ", log.Flags())

				if c.String("zone-file") == "" {
					logger.Fatalf("Zone file option is required\n")
				}
				rz, err := loadYAML(c.String("zone-file"))
				if err != nil {
					logger.Fatalf("Failed to load zone file: %s\n", err)
				}
				rzJSON, err := json.Marshal(rz)
				if err != nil {
					logger.Fatalf("Failed to marshal zone file to JSON: %s\n", err)
				}
				req, err := http.NewRequest("POST", fmt.Sprintf("http://%s/api/reload", c.String("api-uri")), bytes.NewBuffer(rzJSON))
				req.Header.Set("Content-Type", "application/json")
				client := &http.Client{}
				resp, err := client.Do(req)
				if err != nil {
					logger.Fatalf("Failed to send update: %s\n", err)
				}
				defer resp.Body.Close()

				if resp.StatusCode != 200 {
					apiErr, err := ioutil.ReadAll(resp.Body)
					if err != nil {
						logger.Fatalf("Failed to read response body: %s\n", err)
						return
					}
					logger.Fatalf("Failed to reload zones: %s\n", apiErr)
					return
				}
				logger.Printf("Succesesfully reloaded zones\n")
			},
		},
	}

	err := app.Run(os.Args)
	if err != nil {
		fmt.Println(err)
	}
}