mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
Add "get-zone" command (#613)
* Add GetZoneRecords to DNSProvider interface * dnscontrol now uses ufave/cli/v2 * NEW: get-zones.md * HasRecordTypeName should be a method on models.Records not models.DomainConfig * Implement BIND's GetZoneRecords * new WriteZoneFile implemented * go mod vendor * Update docs to use get-zone instead of convertzone * Add CanGetZone capability and update all providers. * Get all zones for a provider at once (#626) * implement GetZoneRecords for cloudflare * munge cloudflare ttls * Implement GetZoneRecords for cloudflare (#625) Co-authored-by: Craig Peterson <192540+captncraig@users.noreply.github.com>
This commit is contained in:
207
commands/getZones.go
Normal file
207
commands/getZones.go
Normal file
@@ -0,0 +1,207 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v2/models"
|
||||
"github.com/StackExchange/dnscontrol/v2/pkg/prettyzone"
|
||||
"github.com/StackExchange/dnscontrol/v2/providers"
|
||||
"github.com/StackExchange/dnscontrol/v2/providers/config"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var _ = cmd(catUtils, func() *cli.Command {
|
||||
var args GetZoneArgs
|
||||
return &cli.Command{
|
||||
Name: "get-zones",
|
||||
Aliases: []string{"get-zone"},
|
||||
Usage: "gets a zone from a provider (stand-alone)",
|
||||
Action: func(ctx *cli.Context) error {
|
||||
if ctx.NArg() < 3 {
|
||||
return cli.NewExitError("Arguments should be: credskey providername zone(s) (Ex: r53 ROUTE53 example.com)", 1)
|
||||
|
||||
}
|
||||
args.CredName = ctx.Args().Get(0)
|
||||
args.ProviderName = ctx.Args().Get(1)
|
||||
args.ZoneNames = ctx.Args().Slice()[2:]
|
||||
return exit(GetZone(args))
|
||||
},
|
||||
Flags: args.flags(),
|
||||
UsageText: "dnscontrol get-zones [command options] credkey provider zone [...]",
|
||||
Description: `Download a zone from a provider. This is a stand-alone utility.
|
||||
|
||||
ARGUMENTS:
|
||||
credkey: The name used in creds.json (first parameter to NewDnsProvider() in dnsconfig.js)
|
||||
provider: The name of the provider (second parameter to NewDnsProvider() in dnsconfig.js)
|
||||
zone: One or more zones (domains) to download; or "all".
|
||||
|
||||
FORMATS:
|
||||
--format=dsl dnsconfig.js format (a decent first draft)
|
||||
--format=pretty BIND Zonefile format
|
||||
--format=tsv TAB separated value (useful for AWK)
|
||||
--format=tsvfqdn tsv with FQDNs (useful for multiple zones)
|
||||
|
||||
EXAMPLES:
|
||||
dnscontrol get-zones myr53 ROUTE53 example.com
|
||||
dnscontrol get-zones gmain GANDI_V5 example.comn other.com
|
||||
dnscontrol get-zones cfmain CLOUDFLAREAPI all
|
||||
dnscontrol get-zones -format=tsv bind BIND example.com
|
||||
dnscontrol get-zones -format=dsl -out=draft.js glcoud GCLOUD example.com`,
|
||||
}
|
||||
}())
|
||||
|
||||
// GetZoneArgs args required for the create-domain subcommand.
|
||||
type GetZoneArgs struct {
|
||||
GetCredentialsArgs // Args related to creds.json
|
||||
CredName string // key in creds.json
|
||||
ProviderName string // provider name: BIND, GANDI_V5, etc or "-"
|
||||
ZoneNames []string // The zones to get
|
||||
OutputFormat string // Output format
|
||||
OutputFile string // Filename to send output ("" means stdout)
|
||||
DefaultTTL int // default TTL for providers where it is unknown
|
||||
}
|
||||
|
||||
func (args *GetZoneArgs) flags() []cli.Flag {
|
||||
flags := args.GetCredentialsArgs.flags()
|
||||
flags = append(flags, &cli.StringFlag{
|
||||
Name: "format",
|
||||
Destination: &args.OutputFormat,
|
||||
Value: "pretty",
|
||||
Usage: `Output format: dsl pretty tsv tsvfqdn`,
|
||||
})
|
||||
flags = append(flags, &cli.StringFlag{
|
||||
Name: "out",
|
||||
Destination: &args.OutputFile,
|
||||
Usage: `Instead of stdout, write to this file`,
|
||||
})
|
||||
flags = append(flags, &cli.IntFlag{
|
||||
Name: "ttl",
|
||||
Destination: &args.DefaultTTL,
|
||||
Usage: `Default TTL`,
|
||||
Value: 300,
|
||||
})
|
||||
return flags
|
||||
}
|
||||
|
||||
// GetZone contains all data/flags needed to run get-zones, independently of CLI.
|
||||
func GetZone(args GetZoneArgs) error {
|
||||
var providerConfigs map[string]map[string]string
|
||||
var err error
|
||||
|
||||
// Read it in:
|
||||
providerConfigs, err = config.LoadProviderConfigs(args.CredsFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
provider, err := providers.CreateDNSProvider(args.ProviderName, providerConfigs[args.CredName], nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// decide which zones we need to convert
|
||||
zones := args.ZoneNames
|
||||
if len(args.ZoneNames) == 1 && args.ZoneNames[0] == "all" {
|
||||
lister, ok := provider.(providers.ZoneLister)
|
||||
if !ok {
|
||||
return fmt.Errorf("provider type %s cannot list zones to use the 'all' feature", args.ProviderName)
|
||||
}
|
||||
zones, err = lister.ListZones()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// actually fetch all of the records
|
||||
zoneRecs := make([]models.Records, len(zones))
|
||||
for i, zone := range zones {
|
||||
recs, err := provider.GetZoneRecords(zone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
zoneRecs[i] = recs
|
||||
}
|
||||
|
||||
// Write it out:
|
||||
|
||||
// first open output stream and print initial header (if applicable)
|
||||
w := os.Stdout
|
||||
if args.OutputFile != "" {
|
||||
w, err = os.Create(args.OutputFile)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.Close()
|
||||
if args.OutputFormat == "dsl" {
|
||||
fmt.Fprintf(w, `var %s = NewDnsProvider("%s", "%s");`+"\n",
|
||||
args.CredName, args.CredName, args.ProviderName)
|
||||
}
|
||||
|
||||
for i, recs := range zoneRecs {
|
||||
zoneName := zones[i]
|
||||
// now print all zones
|
||||
z := prettyzone.PrettySort(recs, zoneName, 0)
|
||||
switch args.OutputFormat {
|
||||
case "pretty":
|
||||
fmt.Fprintf(w, "$ORIGIN %s.\n", zoneName)
|
||||
prettyzone.WriteZoneFileRC(w, z.Records, zoneName)
|
||||
fmt.Fprintln(w)
|
||||
case "dsl":
|
||||
|
||||
fmt.Fprintf(w, `D("%s", REG_CHANGEME,`+"\n", zoneName)
|
||||
fmt.Fprintf(w, "\tDnsProvider(%s)", args.CredName)
|
||||
for _, rec := range recs {
|
||||
fmt.Fprint(w, formatDsl(zoneName, rec, uint32(args.DefaultTTL)))
|
||||
}
|
||||
fmt.Fprint(w, "\n)\n")
|
||||
case "tsv":
|
||||
for _, rec := range recs {
|
||||
fmt.Fprintf(w,
|
||||
fmt.Sprintf("%s\t%d\tIN\t%s\t%s\n",
|
||||
rec.Name, rec.TTL, rec.Type, rec.GetTargetCombined()))
|
||||
}
|
||||
case "tsvfqdn":
|
||||
for _, rec := range recs {
|
||||
fmt.Fprintf(w,
|
||||
fmt.Sprintf("%s\t%d\tIN\t%s\t%s\n",
|
||||
rec.NameFQDN, rec.TTL, rec.Type, rec.GetTargetCombined()))
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("format %q unknown", args.OutputFile)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatDsl(zonename string, rec *models.RecordConfig, defaultTTL uint32) string {
|
||||
|
||||
target := rec.GetTargetCombined()
|
||||
|
||||
ttlop := ""
|
||||
if rec.TTL != defaultTTL && rec.TTL != 0 {
|
||||
ttlop = fmt.Sprintf(", TTL(%d)", rec.TTL)
|
||||
}
|
||||
|
||||
switch rec.Type { // #rtype_variations
|
||||
case "MX":
|
||||
target = fmt.Sprintf("%d, '%s'", rec.MxPreference, rec.GetTargetField())
|
||||
case "SOA":
|
||||
case "TXT":
|
||||
if len(rec.TxtStrings) == 1 {
|
||||
target = `'` + rec.TxtStrings[0] + `'`
|
||||
} else {
|
||||
target = `['` + strings.Join(rec.TxtStrings, `', '`) + `']`
|
||||
}
|
||||
case "NS":
|
||||
// NS records at the apex should be NAMESERVER() records.
|
||||
if rec.Name == "@" {
|
||||
return fmt.Sprintf(",\n\tNAMESERVER('%s'%s)", target, ttlop)
|
||||
}
|
||||
default:
|
||||
target = "'" + target + "'"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(",\n\t%s('%s', %s%s)", rec.Type, rec.Name, target, ttlop)
|
||||
}
|
Reference in New Issue
Block a user