1
0
mirror of https://github.com/StackExchange/dnscontrol.git synced 2024-05-11 05:55:12 +00:00

Vet and Lint the entire system (#296)

* govet and golint corrections
This commit is contained in:
Tom Limoncelli
2018-01-09 12:53:16 -05:00
committed by GitHub
parent 1a91a7f536
commit b7c251190f
64 changed files with 540 additions and 433 deletions

View File

@ -48,18 +48,18 @@ func getVersion() string {
if *sha != "" { if *sha != "" {
return *sha return *sha
} }
//check teamcity build version // check teamcity build version
if v := os.Getenv("BUILD_VCS_NUMBER"); v != "" { if v := os.Getenv("BUILD_VCS_NUMBER"); v != "" {
return v return v
} }
//check git // check git
cmd := exec.Command("git", "rev-parse", "HEAD") cmd := exec.Command("git", "rev-parse", "HEAD")
v, err := cmd.CombinedOutput() v, err := cmd.CombinedOutput()
if err != nil { if err != nil {
return "" return ""
} }
ver := strings.TrimSpace(string(v)) ver := strings.TrimSpace(string(v))
//see if dirty // see if dirty
cmd = exec.Command("git", "diff-index", "--quiet", "HEAD", "--") cmd = exec.Command("git", "diff-index", "--quiet", "HEAD", "--")
err = cmd.Run() err = cmd.Run()
// exit status 1 indicates dirty tree // exit status 1 indicates dirty tree

View File

@ -93,11 +93,15 @@ func generateFeatureMatrix() error {
return ioutil.WriteFile("docs/_includes/matrix.html", buf.Bytes(), 0644) return ioutil.WriteFile("docs/_includes/matrix.html", buf.Bytes(), 0644)
} }
// FeatureDef describes features.
type FeatureDef struct { type FeatureDef struct {
Name, Desc string Name, Desc string
} }
// FeatureMap maps provider names to compliance documentation.
type FeatureMap map[string]*providers.DocumentationNote type FeatureMap map[string]*providers.DocumentationNote
// SetSimple configures a provider's setting in fm.
func (fm FeatureMap) SetSimple(name string, unknownsAllowed bool, f func() bool) { func (fm FeatureMap) SetSimple(name string, unknownsAllowed bool, f func() bool) {
if f() { if f() {
fm[name] = &providers.DocumentationNote{HasFeature: true} fm[name] = &providers.DocumentationNote{HasFeature: true}
@ -106,6 +110,7 @@ func (fm FeatureMap) SetSimple(name string, unknownsAllowed bool, f func() bool)
} }
} }
// FeatureMatrix describes features and which providers support it.
type FeatureMatrix struct { type FeatureMatrix struct {
Features []FeatureDef Features []FeatureDef
Providers map[string]FeatureMap Providers map[string]FeatureMap

View File

@ -124,7 +124,7 @@ func init() {
hc := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(&oauth2.Token{AccessToken: string(t)})) hc := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(&oauth2.Token{AccessToken: string(t)}))
client = github.NewClient(hc) client = github.NewClient(hc)
//get current version if in travis build // get current version if in travis build
if tc := os.Getenv("TRAVIS_COMMIT"); tc != "" { if tc := os.Getenv("TRAVIS_COMMIT"); tc != "" {
commitish = tc commitish = tc
} }

View File

@ -77,6 +77,7 @@ func (args *GetDNSConfigArgs) flags() []cli.Flag {
) )
} }
// GetDNSConfig reads the json-formatted IR file.
func GetDNSConfig(args GetDNSConfigArgs) (*models.DNSConfig, error) { func GetDNSConfig(args GetDNSConfigArgs) (*models.DNSConfig, error) {
if args.JSONFile != "" { if args.JSONFile != "" {
f, err := os.Open(args.JSONFile) f, err := os.Open(args.JSONFile)
@ -145,6 +146,7 @@ func (args *PrintJSONArgs) flags() []cli.Flag {
} }
} }
// GetCredentialsArgs encapsulates the flags/args for sub-commands that use the creds.json file.
type GetCredentialsArgs struct { type GetCredentialsArgs struct {
CredsFile string CredsFile string
} }
@ -160,6 +162,7 @@ func (args *GetCredentialsArgs) flags() []cli.Flag {
} }
} }
// FilterArgs encapsulates the flags/args for sub-commands that can filter by provider or domain.
type FilterArgs struct { type FilterArgs struct {
Providers string Providers string
Domains string Domains string

View File

@ -20,6 +20,7 @@ var _ = cmd(catUtils, func() *cli.Command {
} }
}()) }())
// CreateDomainsArgs args required for the create-domain subcommand.
type CreateDomainsArgs struct { type CreateDomainsArgs struct {
GetDNSConfigArgs GetDNSConfigArgs
GetCredentialsArgs GetCredentialsArgs
@ -31,6 +32,7 @@ func (args *CreateDomainsArgs) flags() []cli.Flag {
return flags return flags
} }
// CreateDomains contains all data/flags needed to run create-domains, independently of CLI.
func CreateDomains(args CreateDomainsArgs) error { func CreateDomains(args CreateDomainsArgs) error {
cfg, err := GetDNSConfig(args.GetDNSConfigArgs) cfg, err := GetDNSConfig(args.GetDNSConfigArgs)
if err != nil { if err != nil {

View File

@ -52,6 +52,7 @@ var _ = cmd(catMain, func() *cli.Command {
} }
}()) }())
// PushArgs contains all data/flags needed to run push, independently of CLI
type PushArgs struct { type PushArgs struct {
PreviewArgs PreviewArgs
Interactive bool Interactive bool
@ -67,10 +68,12 @@ func (args *PushArgs) flags() []cli.Flag {
return flags return flags
} }
// Preview implements the preview subcommand.
func Preview(args PreviewArgs) error { func Preview(args PreviewArgs) error {
return run(args, false, false, printer.ConsolePrinter{}) return run(args, false, false, printer.ConsolePrinter{})
} }
// Push implements the push subcommand.
func Push(args PushArgs) error { func Push(args PushArgs) error {
return run(args.PreviewArgs, true, args.Interactive, printer.ConsolePrinter{}) return run(args.PreviewArgs, true, args.Interactive, printer.ConsolePrinter{})
} }

View File

@ -46,6 +46,7 @@ var _ = cmd(catDebug, func() *cli.Command {
} }
}()) }())
// PrintIRArgs encapsulates the flags/arguments for the print-ir command.
type PrintIRArgs struct { type PrintIRArgs struct {
GetDNSConfigArgs GetDNSConfigArgs
PrintJSONArgs PrintJSONArgs
@ -62,6 +63,7 @@ func (args *PrintIRArgs) flags() []cli.Flag {
return flags return flags
} }
// PrintIR implements the print-ir subcommand.
func PrintIR(args PrintIRArgs) error { func PrintIR(args PrintIRArgs) error {
cfg, err := GetDNSConfig(args.GetDNSConfigArgs) cfg, err := GetDNSConfig(args.GetDNSConfigArgs)
if err != nil { if err != nil {
@ -76,6 +78,7 @@ func PrintIR(args PrintIRArgs) error {
return PrintJSON(args.PrintJSONArgs, cfg) return PrintJSON(args.PrintJSONArgs, cfg)
} }
// PrintValidationErrors formats and prints the validation errors and warnings.
func PrintValidationErrors(errs []error) (fatal bool) { func PrintValidationErrors(errs []error) (fatal bool) {
if len(errs) == 0 { if len(errs) == 0 {
return false return false
@ -92,6 +95,7 @@ func PrintValidationErrors(errs []error) (fatal bool) {
return return
} }
// ExecuteDSL executes the dnsconfig.js contents.
func ExecuteDSL(args ExecuteDSLArgs) (*models.DNSConfig, error) { func ExecuteDSL(args ExecuteDSLArgs) (*models.DNSConfig, error) {
if args.JSFile == "" { if args.JSFile == "" {
return nil, fmt.Errorf("No config specified") return nil, fmt.Errorf("No config specified")
@ -107,6 +111,7 @@ func ExecuteDSL(args ExecuteDSLArgs) (*models.DNSConfig, error) {
return dnsConfig, nil return dnsConfig, nil
} }
// PrintJSON outputs/prettyprints the IR data.
func PrintJSON(args PrintJSONArgs, config *models.DNSConfig) (err error) { func PrintJSON(args PrintJSONArgs, config *models.DNSConfig) (err error) {
var dat []byte var dat []byte
if args.Pretty { if args.Pretty {

View File

@ -22,13 +22,13 @@ Modifier arguments are processed according to type as follows:
var REGISTRAR = NewRegistrar("name.com", "NAMEDOTCOM"); var REGISTRAR = NewRegistrar("name.com", "NAMEDOTCOM");
var r53 = NewDnsProvider("R53","ROUTE53"); var r53 = NewDnsProvider("R53","ROUTE53");
//simple domain // simple domain
D("example.com", REGISTRAR, DnsProvider(r53), D("example.com", REGISTRAR, DnsProvider(r53),
A("@","1.2.3.4"), A("@","1.2.3.4"),
CNAME("test", "foo.example2.com.") CNAME("test", "foo.example2.com.")
); );
//"macro" for records that can be mixed into any zone // "macro" for records that can be mixed into any zone
var GOOGLE_APPS_DOMAIN_MX = [ var GOOGLE_APPS_DOMAIN_MX = [
MX('@', 1, 'aspmx.l.google.com.'), MX('@', 1, 'aspmx.l.google.com.'),
MX('@', 5, 'alt1.aspmx.l.google.com.'), MX('@', 5, 'alt1.aspmx.l.google.com.'),

View File

@ -10,14 +10,14 @@ arguments passed as if they were the first modifiers in the argument list.
{% include startExample.html %} {% include startExample.html %}
{% highlight js %} {% highlight js %}
var COMMON = NewDnsProvider("foo","BIND"); var COMMON = NewDnsProvider("foo","BIND");
//we want to create backup zone files for all domains, but not actually register them. // we want to create backup zone files for all domains, but not actually register them.
//also create a default TTL // also create a default TTL
DEFAULTS( DnsProvider(COMMON,0), DefaultTTL(1000)); DEFAULTS( DnsProvider(COMMON,0), DefaultTTL(1000));
D("example.com", REGISTRAR, DnsProvider("R53"), A("@","1.2.3.4")); //this domain will have the defaults set. D("example.com", REGISTRAR, DnsProvider("R53"), A("@","1.2.3.4")); // this domain will have the defaults set.
//clear defaults // clear defaults
DEFAULTS(); DEFAULTS();
D("example2.com", REGISTRAR, DnsProvider("R53"), A("@","1.2.3.4")); //this domain will not have the previous defaults. D("example2.com", REGISTRAR, DnsProvider("R53"), A("@","1.2.3.4")); // this domain will not have the previous defaults.
{%endhighlight%} {%endhighlight%}
{% include endExample.html %} {% include endExample.html %}

View File

@ -100,7 +100,7 @@ list.
Each entry in the list is a new state. For example: Each entry in the list is a new state. For example:
``` ```
//MX // MX
tc("Empty"), <<< 1 tc("Empty"), <<< 1
tc("MX record", mx("@", 5, "foo.com.")), <<< 2 tc("MX record", mx("@", 5, "foo.com.")), <<< 2
tc("Change MX pref", mx("@", 10, "foo.com.")), <<< 3 tc("Change MX pref", mx("@", 10, "foo.com.")), <<< 3

View File

@ -46,8 +46,8 @@ D('example.com', registrar,
var addrA = IP('1.2.3.4') var addrA = IP('1.2.3.4')
D('example.com', REG, DnsProvider('R53'), D('example.com', REG, DnsProvider('R53'),
A('@', addrA), //1.2.3.4 A('@', addrA), // 1.2.3.4
A('www', addrA + 1), //1.2.3.5 A('www', addrA + 1), // 1.2.3.5
) )
{% endhighlight %} {% endhighlight %}

View File

@ -64,13 +64,13 @@ If you are using other providers, you will likely need to make a `creds.json` fi
{% highlight js %} {% highlight js %}
{ {
"cloudflare":{ //provider name to be used in dnsconfig.js "cloudflare":{ // provider name to be used in dnsconfig.js
"apikey": "key", //API key "apikey": "key", // API key
"apiuser": "username" //username for cloudflare "apiuser": "username" // username for cloudflare
}, },
"namecom":{ //provider name to be used in dnsconfig.js "namecom":{ // provider name to be used in dnsconfig.js
"apikey": "key", //API Key "apikey": "key", // API Key
"apiuser": "username" //username for name.com "apiuser": "username" // username for name.com
} }
} }
{%endhighlight%} {%endhighlight%}

View File

@ -136,7 +136,7 @@ func runTests(t *testing.T, prv providers.DNSServiceProvider, domainName string,
t.Fatal(err) t.Fatal(err)
} }
} }
//run a second time and expect zero corrections // run a second time and expect zero corrections
corrections, err = prv.GetDomainCorrections(dom2) corrections, err = prv.GetDomainCorrections(dom2)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -296,7 +296,7 @@ func manyA(namePattern, target string, n int) []*rec {
} }
func makeTests(t *testing.T) []*TestCase { func makeTests(t *testing.T) []*TestCase {
//ALWAYS ADD TO BOTTOM OF LIST. Order and indexes matter. // ALWAYS ADD TO BOTTOM OF LIST. Order and indexes matter.
tests := []*TestCase{ tests := []*TestCase{
// A // A
tc("Empty"), tc("Empty"),
@ -319,20 +319,20 @@ func makeTests(t *testing.T) []*TestCase {
tc("Change back to CNAME", cname("foo", "google.com.")), tc("Change back to CNAME", cname("foo", "google.com.")),
tc("Record pointing to @", cname("foo", "**current-domain**")), tc("Record pointing to @", cname("foo", "**current-domain**")),
//NS // NS
tc("Empty"), tc("Empty"),
tc("NS for subdomain", ns("xyz", "ns2.foo.com.")), tc("NS for subdomain", ns("xyz", "ns2.foo.com.")),
tc("Dual NS for subdomain", ns("xyz", "ns2.foo.com."), ns("xyz", "ns1.foo.com.")), tc("Dual NS for subdomain", ns("xyz", "ns2.foo.com."), ns("xyz", "ns1.foo.com.")),
tc("NS Record pointing to @", ns("foo", "**current-domain**")), tc("NS Record pointing to @", ns("foo", "**current-domain**")),
//IDNAs // IDNAs
tc("Empty"), tc("Empty"),
tc("Internationalized name", a("ööö", "1.2.3.4")), tc("Internationalized name", a("ööö", "1.2.3.4")),
tc("Change IDN", a("ööö", "2.2.2.2")), tc("Change IDN", a("ööö", "2.2.2.2")),
tc("Internationalized CNAME Target", cname("a", "ööö.com.")), tc("Internationalized CNAME Target", cname("a", "ööö.com.")),
tc("IDN CNAME AND Target", cname("öoö", "ööö.企业.")), tc("IDN CNAME AND Target", cname("öoö", "ööö.企业.")),
//MX // MX
tc("Empty"), tc("Empty"),
tc("MX record", mx("@", 5, "foo.com.")), tc("MX record", mx("@", 5, "foo.com.")),
tc("Second MX record, same prio", mx("@", 5, "foo.com."), mx("@", 5, "foo2.com.")), tc("Second MX record, same prio", mx("@", 5, "foo.com."), mx("@", 5, "foo2.com.")),
@ -394,7 +394,7 @@ func makeTests(t *testing.T) []*TestCase {
) )
} }
//TLSA // TLSA
if !providers.ProviderHasCabability(*providerToRun, providers.CanUseTLSA) { if !providers.ProviderHasCabability(*providerToRun, providers.CanUseTLSA) {
t.Log("Skipping TLSA Tests because provider does not support them") t.Log("Skipping TLSA Tests because provider does not support them")
} else { } else {
@ -425,7 +425,7 @@ func makeTests(t *testing.T) []*TestCase {
// Known page sizes: // Known page sizes:
// - gandi: 100 // - gandi: 100
skip := map[string]bool{ skip := map[string]bool{
"NS1": true, //ns1 free acct only allows 50 records "NS1": true, // ns1 free acct only allows 50 records
} }
if skip[*providerToRun] { if skip[*providerToRun] {
t.Log("Skipping Large record count Tests because provider does not support them") t.Log("Skipping Large record count Tests because provider does not support them")

View File

@ -34,7 +34,7 @@ func versionString() string {
if SHA != "" { if SHA != "" {
version = fmt.Sprintf("%s (%s)", Version, SHA) version = fmt.Sprintf("%s (%s)", Version, SHA)
} else { } else {
version = fmt.Sprintf("%s-dev", Version) //no SHA. '0.x.y-dev' indicates it is run from source without build script. version = fmt.Sprintf("%s-dev", Version) // no SHA. '0.x.y-dev' indicates it is run from source without build script.
} }
if BuildTime != "" { if BuildTime != "" {
i, err := strconv.ParseInt(BuildTime, 10, 64) i, err := strconv.ParseInt(BuildTime, 10, 64)

View File

@ -16,14 +16,17 @@ import (
"golang.org/x/net/idna" "golang.org/x/net/idna"
) )
// DefaultTTL is applied to any DNS record without an explicit TTL.
const DefaultTTL = uint32(300) const DefaultTTL = uint32(300)
// DNSConfig describes the desired DNS configuration, usually loaded from dnsconfig.js.
type DNSConfig struct { type DNSConfig struct {
Registrars []*RegistrarConfig `json:"registrars"` Registrars []*RegistrarConfig `json:"registrars"`
DNSProviders []*DNSProviderConfig `json:"dns_providers"` DNSProviders []*DNSProviderConfig `json:"dns_providers"`
Domains []*DomainConfig `json:"domains"` Domains []*DomainConfig `json:"domains"`
} }
// FindDomain returns the *DomainConfig for domain query in config.
func (config *DNSConfig) FindDomain(query string) *DomainConfig { func (config *DNSConfig) FindDomain(query string) *DomainConfig {
for _, b := range config.Domains { for _, b := range config.Domains {
if b.Name == query { if b.Name == query {
@ -33,12 +36,14 @@ func (config *DNSConfig) FindDomain(query string) *DomainConfig {
return nil return nil
} }
// RegistrarConfig describes a registrar.
type RegistrarConfig struct { type RegistrarConfig struct {
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
Metadata json.RawMessage `json:"meta,omitempty"` Metadata json.RawMessage `json:"meta,omitempty"`
} }
// DNSProviderConfig describes a DNS service provider.
type DNSProviderConfig struct { type DNSProviderConfig struct {
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
@ -108,53 +113,53 @@ type RecordConfig struct {
Original interface{} `json:"-"` // Store pointer to provider-specific record object. Used in diffing. Original interface{} `json:"-"` // Store pointer to provider-specific record object. Used in diffing.
} }
func (r *RecordConfig) String() (content string) { func (rc *RecordConfig) String() (content string) {
if r.CombinedTarget { if rc.CombinedTarget {
return r.Target return rc.Target
} }
content = fmt.Sprintf("%s %s %s %d", r.Type, r.NameFQDN, r.Target, r.TTL) content = fmt.Sprintf("%s %s %s %d", rc.Type, rc.NameFQDN, rc.Target, rc.TTL)
switch r.Type { // #rtype_variations switch rc.Type { // #rtype_variations
case "A", "AAAA", "CNAME", "NS", "PTR", "TXT": case "A", "AAAA", "CNAME", "NS", "PTR", "TXT":
// Nothing special. // Nothing special.
case "MX": case "MX":
content += fmt.Sprintf(" pref=%d", r.MxPreference) content += fmt.Sprintf(" pref=%d", rc.MxPreference)
case "SOA": case "SOA":
content = fmt.Sprintf("%s %s %s %d", r.Type, r.Name, r.Target, r.TTL) content = fmt.Sprintf("%s %s %s %d", rc.Type, rc.Name, rc.Target, rc.TTL)
case "SRV": case "SRV":
content += fmt.Sprintf(" srvpriority=%d srvweight=%d srvport=%d", r.SrvPriority, r.SrvWeight, r.SrvPort) content += fmt.Sprintf(" srvpriority=%d srvweight=%d srvport=%d", rc.SrvPriority, rc.SrvWeight, rc.SrvPort)
case "TLSA": case "TLSA":
content += fmt.Sprintf(" tlsausage=%d tlsaselector=%d tlsamatchingtype=%d", r.TlsaUsage, r.TlsaSelector, r.TlsaMatchingType) content += fmt.Sprintf(" tlsausage=%d tlsaselector=%d tlsamatchingtype=%d", rc.TlsaUsage, rc.TlsaSelector, rc.TlsaMatchingType)
case "CAA": case "CAA":
content += fmt.Sprintf(" caatag=%s caaflag=%d", r.CaaTag, r.CaaFlag) content += fmt.Sprintf(" caatag=%s caaflag=%d", rc.CaaTag, rc.CaaFlag)
default: default:
msg := fmt.Sprintf("rc.String rtype %v unimplemented", r.Type) msg := fmt.Sprintf("rc.String rtype %v unimplemented", rc.Type)
panic(msg) panic(msg)
// We panic so that we quickly find any switch statements // We panic so that we quickly find any switch statements
// that have not been updated for a new RR type. // that have not been updated for a new RR type.
} }
for k, v := range r.Metadata { for k, v := range rc.Metadata {
content += fmt.Sprintf(" %s=%s", k, v) content += fmt.Sprintf(" %s=%s", k, v)
} }
return content return content
} }
// Content combines Target and other fields into one string. // Content combines Target and other fields into one string.
func (r *RecordConfig) Content() string { func (rc *RecordConfig) Content() string {
if r.CombinedTarget { if rc.CombinedTarget {
return r.Target return rc.Target
} }
// If this is a pseudo record, just return the target. // If this is a pseudo record, just return the target.
if _, ok := dns.StringToType[r.Type]; !ok { if _, ok := dns.StringToType[rc.Type]; !ok {
return r.Target return rc.Target
} }
// We cheat by converting to a dns.RR and use the String() function. // We cheat by converting to a dns.RR and use the String() function.
// Sadly that function always includes a header, which we must strip out. // Sadly that function always includes a header, which we must strip out.
// TODO(tlim): Request the dns project add a function that returns // TODO(tlim): Request the dns project add a function that returns
// the string without the header. // the string without the header.
rr := r.ToRR() rr := rc.ToRR()
header := rr.Header().String() header := rr.Header().String()
full := rr.String() full := rr.String()
if !strings.HasPrefix(full, header) { if !strings.HasPrefix(full, header) {
@ -164,30 +169,30 @@ func (r *RecordConfig) Content() string {
} }
// MergeToTarget combines "extra" fields into .Target, and zeros the merged fields. // MergeToTarget combines "extra" fields into .Target, and zeros the merged fields.
func (r *RecordConfig) MergeToTarget() { func (rc *RecordConfig) MergeToTarget() {
if r.CombinedTarget { if rc.CombinedTarget {
pm := strings.Join([]string{"MergeToTarget: Already collapsed: ", r.Name, r.Target}, " ") pm := strings.Join([]string{"MergeToTarget: Already collapsed: ", rc.Name, rc.Target}, " ")
panic(pm) panic(pm)
} }
// Merge "extra" fields into the Target. // Merge "extra" fields into the Target.
r.Target = r.Content() rc.Target = rc.Content()
// Zap any fields that may have been merged. // Zap any fields that may have been merged.
r.MxPreference = 0 rc.MxPreference = 0
r.SrvPriority = 0 rc.SrvPriority = 0
r.SrvWeight = 0 rc.SrvWeight = 0
r.SrvPort = 0 rc.SrvPort = 0
r.CaaFlag = 0 rc.CaaFlag = 0
r.CaaTag = "" rc.CaaTag = ""
r.TlsaUsage = 0 rc.TlsaUsage = 0
r.TlsaMatchingType = 0 rc.TlsaMatchingType = 0
r.TlsaSelector = 0 rc.TlsaSelector = 0
r.CombinedTarget = true rc.CombinedTarget = true
} }
/// Convert RecordConfig -> dns.RR. // ToRR converts a RecordConfig to a dns.RR.
func (rc *RecordConfig) ToRR() dns.RR { func (rc *RecordConfig) ToRR() dns.RR {
// Don't call this on fake types. // Don't call this on fake types.
@ -266,8 +271,10 @@ func atou32(s string) uint32 {
return uint32(i64) return uint32(i64)
} }
// Records is a list of *RecordConfig.
type Records []*RecordConfig type Records []*RecordConfig
// Grouped returns a map of keys to records.
func (r Records) Grouped() map[RecordKey]Records { func (r Records) Grouped() map[RecordKey]Records {
groups := map[RecordKey]Records{} groups := map[RecordKey]Records{}
for _, rec := range r { for _, rec := range r {
@ -310,20 +317,24 @@ func fixTxt(recs []*RecordConfig) {
} }
} }
// RecordKey represents a resource record in a format used by some systems.
type RecordKey struct { type RecordKey struct {
Name string Name string
Type string Type string
} }
func (r *RecordConfig) Key() RecordKey { // Key converts a RecordConfig into a RecordKey.
return RecordKey{r.Name, r.Type} func (rc *RecordConfig) Key() RecordKey {
return RecordKey{rc.Name, rc.Type}
} }
// Nameserver describes a nameserver.
type Nameserver struct { type Nameserver struct {
Name string `json:"name"` // Normalized to a FQDN with NO trailing "." Name string `json:"name"` // Normalized to a FQDN with NO trailing "."
Target string `json:"target"` Target string `json:"target"`
} }
// StringsToNameservers constructs a list of *Nameserver structs using a list of FQDNs.
func StringsToNameservers(nss []string) []*Nameserver { func StringsToNameservers(nss []string) []*Nameserver {
nservers := []*Nameserver{} nservers := []*Nameserver{}
for _, ns := range nss { for _, ns := range nss {
@ -332,6 +343,7 @@ func StringsToNameservers(nss []string) []*Nameserver {
return nservers return nservers
} }
// DomainConfig describes a DNS domain (tecnically a DNS zone).
type DomainConfig struct { type DomainConfig struct {
Name string `json:"name"` // NO trailing "." Name string `json:"name"` // NO trailing "."
Registrar string `json:"registrar"` Registrar string `json:"registrar"`
@ -342,23 +354,25 @@ type DomainConfig struct {
KeepUnknown bool `json:"keepunknown,omitempty"` KeepUnknown bool `json:"keepunknown,omitempty"`
} }
// Copy returns a deep copy of the DomainConfig.
func (dc *DomainConfig) Copy() (*DomainConfig, error) { func (dc *DomainConfig) Copy() (*DomainConfig, error) {
newDc := &DomainConfig{} newDc := &DomainConfig{}
err := copyObj(dc, newDc) err := copyObj(dc, newDc)
return newDc, err return newDc, err
} }
func (r *RecordConfig) Copy() (*RecordConfig, error) { // Copy returns a deep copy of a RecordConfig.
func (rc *RecordConfig) Copy() (*RecordConfig, error) {
newR := &RecordConfig{} newR := &RecordConfig{}
err := copyObj(r, newR) err := copyObj(rc, newR)
return newR, err return newR, err
} }
//Punycode will convert all records to punycode format. // Punycode will convert all records to punycode format.
//It will encode: // It will encode:
//- Name // - Name
//- NameFQDN // - NameFQDN
//- Target (CNAME and MX only) // - Target (CNAME and MX only)
func (dc *DomainConfig) Punycode() error { func (dc *DomainConfig) Punycode() error {
var err error var err error
for _, rec := range dc.Records { for _, rec := range dc.Records {
@ -436,9 +450,9 @@ func (dc *DomainConfig) CombineSRVs() {
} }
} }
//SplitCombinedSrvValue splits a combined SRV priority, weight, port and target into // SplitCombinedSrvValue splits a combined SRV priority, weight, port and target into
//separate entities, some DNS providers want "5" "10" 15" and "foo.com.", // separate entities, some DNS providers want "5" "10" 15" and "foo.com.",
//while other providers want "5 10 15 foo.com.". // while other providers want "5 10 15 foo.com.".
func SplitCombinedSrvValue(s string) (priority, weight, port uint16, target string, err error) { func SplitCombinedSrvValue(s string) (priority, weight, port uint16, target string, err error) {
parts := strings.Fields(s) parts := strings.Fields(s)
@ -476,6 +490,7 @@ func (dc *DomainConfig) CombineCAAs() {
} }
} }
// SplitCombinedCaaValue parses a string listing the parts of a CAA record into its components.
func SplitCombinedCaaValue(s string) (tag string, flag uint8, value string, err error) { func SplitCombinedCaaValue(s string) (tag string, flag uint8, value string, err error) {
splitData := strings.SplitN(s, " ", 3) splitData := strings.SplitN(s, " ", 3)
@ -509,12 +524,10 @@ func copyObj(input interface{}, output interface{}) error {
if err := enc.Encode(input); err != nil { if err := enc.Encode(input); err != nil {
return err return err
} }
if err := dec.Decode(output); err != nil { return dec.Decode(output)
return err
}
return nil
} }
// HasRecordTypeName returns True if there is a record with this rtype and name.
func (dc *DomainConfig) HasRecordTypeName(rtype, name string) bool { func (dc *DomainConfig) HasRecordTypeName(rtype, name string) bool {
for _, r := range dc.Records { for _, r := range dc.Records {
if r.Type == rtype && r.Name == name { if r.Type == rtype && r.Name == name {
@ -524,6 +537,7 @@ func (dc *DomainConfig) HasRecordTypeName(rtype, name string) bool {
return false return false
} }
// Filter removes all records that don't match the filter f.
func (dc *DomainConfig) Filter(f func(r *RecordConfig) bool) { func (dc *DomainConfig) Filter(f func(r *RecordConfig) bool) {
recs := []*RecordConfig{} recs := []*RecordConfig{}
for _, r := range dc.Records { for _, r := range dc.Records {
@ -534,6 +548,10 @@ func (dc *DomainConfig) Filter(f func(r *RecordConfig) bool) {
dc.Records = recs dc.Records = recs
} }
// InterfaceToIP returns an IP address when given a 32-bit value or a string. That is,
// dnsconfig.js output may represent IP addresses as either a string ("1.2.3.4")
// or as an numeric value (the integer representation of the 32-bit value). This function
// converts either to a net.IP.
func InterfaceToIP(i interface{}) (net.IP, error) { func InterfaceToIP(i interface{}) (net.IP, error) {
switch v := i.(type) { switch v := i.(type) {
case float64: case float64:
@ -545,11 +563,11 @@ func InterfaceToIP(i interface{}) (net.IP, error) {
} }
return nil, fmt.Errorf("%s is not a valid ip address", v) return nil, fmt.Errorf("%s is not a valid ip address", v)
default: default:
return nil, fmt.Errorf("Cannot convert type %s to ip.", reflect.TypeOf(i)) return nil, fmt.Errorf("cannot convert type %s to ip", reflect.TypeOf(i))
} }
} }
//Correction is anything that can be run. Implementation is up to the specific provider. // Correction is anything that can be run. Implementation is up to the specific provider.
type Correction struct { type Correction struct {
F func() error `json:"-"` F func() error `json:"-"`
Msg string Msg string

View File

@ -209,7 +209,7 @@ var TLSA = recordBuilder('TLSA', {
['usage', _.isNumber], ['usage', _.isNumber],
['selector', _.isNumber], ['selector', _.isNumber],
['matchingtype', _.isNumber], ['matchingtype', _.isNumber],
['target', _.isString], //recordBuilder needs a "target" argument ['target', _.isString], // recordBuilder needs a "target" argument
], ],
transform: function(record, args, modifiers) { transform: function(record, args, modifiers) {
record.name = args.name; record.name = args.name;
@ -474,7 +474,7 @@ function addRecord(d, type, name, target, mods) {
if (_.isFunction(m)) { if (_.isFunction(m)) {
m(rec); m(rec);
} else if (_.isObject(m)) { } else if (_.isObject(m)) {
//convert transforms to strings // convert transforms to strings
if (m.transform && _.isArray(m.transform)) { if (m.transform && _.isArray(m.transform)) {
m.transform = format_tt(m.transform); m.transform = format_tt(m.transform);
} }
@ -493,7 +493,7 @@ function addRecord(d, type, name, target, mods) {
return rec; return rec;
} }
//ip conversion functions from http://stackoverflow.com/a/8105740/121660 // ip conversion functions from http://stackoverflow.com/a/8105740/121660
// via http://javascript.about.com/library/blipconvert.htm // via http://javascript.about.com/library/blipconvert.htm
function IP(dot) { function IP(dot) {
var d = dot.split('.'); var d = dot.split('.');

View File

@ -9,12 +9,12 @@ import (
"github.com/StackExchange/dnscontrol/pkg/transform" "github.com/StackExchange/dnscontrol/pkg/transform"
"github.com/robertkrimen/otto" "github.com/robertkrimen/otto"
//load underscore js into vm by default // load underscore js into vm by default
_ "github.com/robertkrimen/otto/underscore" _ "github.com/robertkrimen/otto/underscore" // required by otto
) )
//ExecuteJavascript accepts a javascript string and runs it, returning the resulting dnsConfig. // ExecuteJavascript accepts a javascript string and runs it, returning the resulting dnsConfig.
func ExecuteJavascript(script string, devMode bool) (*models.DNSConfig, error) { func ExecuteJavascript(script string, devMode bool) (*models.DNSConfig, error) {
vm := otto.New() vm := otto.New()
@ -48,6 +48,7 @@ func ExecuteJavascript(script string, devMode bool) (*models.DNSConfig, error) {
return conf, nil return conf, nil
} }
// GetHelpers returns the filename of helpers.js, or the esc'ed version.
func GetHelpers(devMode bool) string { func GetHelpers(devMode bool) string {
return _escFSMustString(devMode, "/helpers.js") return _escFSMustString(devMode, "/helpers.js")
} }

View File

@ -27,7 +27,7 @@ func TestParsedFiles(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
for _, f := range files { for _, f := range files {
//run all js files that start with a number. Skip others. // run all js files that start with a number. Skip others.
if filepath.Ext(f.Name()) != ".js" || !unicode.IsNumber(rune(f.Name()[0])) { if filepath.Ext(f.Name()) != ".js" || !unicode.IsNumber(rune(f.Name()[0])) {
continue continue
} }

View File

@ -192,89 +192,89 @@ var _escData = map[string]*_escFile{
"/helpers.js": { "/helpers.js": {
local: "pkg/js/helpers.js", local: "pkg/js/helpers.js",
size: 17431, size: 17434,
modtime: 0, modtime: 0,
compressed: ` compressed: `
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
`, `,
}, },

View File

@ -1,21 +1,22 @@
//Package nameservers provides logic for dynamically finding nameservers for a domain, and configuring NS records for them. // Package nameservers provides logic for dynamically finding nameservers for a domain, and configuring NS records for them.
package nameservers package nameservers
import ( import (
"fmt" "fmt"
"strings" "strings"
"strconv"
"github.com/StackExchange/dnscontrol/models" "github.com/StackExchange/dnscontrol/models"
"github.com/StackExchange/dnscontrol/providers" "github.com/StackExchange/dnscontrol/providers"
"github.com/miekg/dns/dnsutil" "github.com/miekg/dns/dnsutil"
"strconv"
) )
//DetermineNameservers will find all nameservers we should use for a domain. It follows the following rules: // DetermineNameservers will find all nameservers we should use for a domain. It follows the following rules:
//1. All explicitly defined NAMESERVER records will be used. // 1. All explicitly defined NAMESERVER records will be used.
//2. Each DSP declares how many nameservers to use. Default is all. 0 indicates to use none. // 2. Each DSP declares how many nameservers to use. Default is all. 0 indicates to use none.
func DetermineNameservers(dc *models.DomainConfig, maxNS int, dsps map[string]providers.DNSServiceProvider) ([]*models.Nameserver, error) { func DetermineNameservers(dc *models.DomainConfig, maxNS int, dsps map[string]providers.DNSServiceProvider) ([]*models.Nameserver, error) {
//always take explicit // always take explicit
ns := dc.Nameservers ns := dc.Nameservers
for dsp, n := range dc.DNSProviders { for dsp, n := range dc.DNSProviders {
if n == 0 { if n == 0 {
@ -41,7 +42,7 @@ func DetermineNameservers(dc *models.DomainConfig, maxNS int, dsps map[string]pr
return ns, nil return ns, nil
} }
//AddNSRecords creates NS records on a domain corresponding to the nameservers specified. // AddNSRecords creates NS records on a domain corresponding to the nameservers specified.
func AddNSRecords(dc *models.DomainConfig) { func AddNSRecords(dc *models.DomainConfig) {
ttl := uint32(300) ttl := uint32(300)
if ttls, ok := dc.Metadata["ns_ttl"]; ok { if ttls, ok := dc.Metadata["ns_ttl"]; ok {

View File

@ -71,7 +71,7 @@ func flattenSPFs(cfg *models.DNSConfig) []error {
if err := cache.Save("spfcache.updated.json"); err != nil { if err := cache.Save("spfcache.updated.json"); err != nil {
errs = append(errs, err) errs = append(errs, err)
} else { } else {
errs = append(errs, Warning{fmt.Errorf("%d spf record lookups are out of date with cache (%s).\nWrote changes to spfcache.updated.json. Please rename and commit:\n $ mv spfcache.updated.json spfcache.json\n $ git commit spfcache.json\n", len(changed), strings.Join(changed, ","))}) errs = append(errs, Warning{fmt.Errorf("%d spf record lookups are out of date with cache (%s).\nWrote changes to spfcache.updated.json. Please rename and commit:\n $ mv spfcache.updated.json spfcache.json\n $ git commit spfcache.json", len(changed), strings.Join(changed, ","))})
} }
} }
} }

View File

@ -74,7 +74,7 @@ func validateRecordTypes(rec *models.RecordConfig, domain string, pTypes []strin
return fmt.Errorf("Custom record type %s is not compatible with provider type %s", rec.Type, providerType) return fmt.Errorf("Custom record type %s is not compatible with provider type %s", rec.Type, providerType)
} }
} }
//it is ok. Lets replace the type with real type and add metadata to say we checked it // it is ok. Lets replace the type with real type and add metadata to say we checked it
rec.Metadata["orig_custom_type"] = rec.Type rec.Metadata["orig_custom_type"] = rec.Type
if cType.RealType != "" { if cType.RealType != "" {
rec.Type = cType.RealType rec.Type = cType.RealType
@ -87,7 +87,7 @@ func validateRecordTypes(rec *models.RecordConfig, domain string, pTypes []strin
// here we list common records expected to have underscores. Anything else containing an underscore will print a warning. // here we list common records expected to have underscores. Anything else containing an underscore will print a warning.
var labelUnderscores = []string{"_domainkey", "_dmarc", "_amazonses", "_acme-challenge"} var labelUnderscores = []string{"_domainkey", "_dmarc", "_amazonses", "_acme-challenge"}
//these record types may contain underscores // these record types may contain underscores
var rTypeUnderscores = []string{"SRV", "TLSA", "TXT"} var rTypeUnderscores = []string{"SRV", "TLSA", "TXT"}
func checkLabel(label string, rType string, domain string, meta map[string]string) error { func checkLabel(label string, rType string, domain string, meta map[string]string) error {
@ -116,7 +116,7 @@ func checkLabel(label string, rType string, domain string, meta map[string]strin
return nil return nil
} }
} }
//underscores are warnings // underscores are warnings
if strings.ContainsRune(label, '_') { if strings.ContainsRune(label, '_') {
return Warning{fmt.Errorf("label %s.%s contains an underscore", label, domain)} return Warning{fmt.Errorf("label %s.%s contains an underscore", label, domain)}
} }
@ -163,7 +163,7 @@ func checkTargets(rec *models.RecordConfig, domain string) (errs []error) {
case "TXT", "IMPORT_TRANSFORM", "CAA", "TLSA": case "TXT", "IMPORT_TRANSFORM", "CAA", "TLSA":
default: default:
if rec.Metadata["orig_custom_type"] != "" { if rec.Metadata["orig_custom_type"] != "" {
//it is a valid custom type. We perform no validation on target // it is a valid custom type. We perform no validation on target
return return
} }
errs = append(errs, fmt.Errorf("checkTargets: Unimplemented record type (%v) domain=%v name=%v", errs = append(errs, fmt.Errorf("checkTargets: Unimplemented record type (%v) domain=%v name=%v",
@ -244,6 +244,7 @@ type Warning struct {
error error
} }
// NormalizeAndValidateConfig performs and normalization and/or validation of the IR.
func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) { func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) {
ptypeMap := map[string]string{} ptypeMap := map[string]string{}
for _, p := range config.DNSProviders { for _, p := range config.DNSProviders {
@ -261,7 +262,7 @@ func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) {
pTypes = append(pTypes, pType) pTypes = append(pTypes, pType)
} }
//If NO_PURGE is in use, make sure this *isn't* a provider that *doesn't* support NO_PURGE. // If NO_PURGE is in use, make sure this *isn't* a provider that *doesn't* support NO_PURGE.
if domain.KeepUnknown && providers.ProviderHasCabability(pType, providers.CantUseNOPURGE) { if domain.KeepUnknown && providers.ProviderHasCabability(pType, providers.CantUseNOPURGE) {
errs = append(errs, fmt.Errorf("%s uses NO_PURGE which is not supported by %s(%s)", domain.Name, p, pType)) errs = append(errs, fmt.Errorf("%s uses NO_PURGE which is not supported by %s(%s)", domain.Name, p, pType))
} }
@ -366,12 +367,12 @@ func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) {
} }
} }
//Check that CNAMES don't have to co-exist with any other records // Check that CNAMES don't have to co-exist with any other records
for _, d := range config.Domains { for _, d := range config.Domains {
errs = append(errs, checkCNAMEs(d)...) errs = append(errs, checkCNAMEs(d)...)
} }
//Check that if any aliases / ptr / etc.. are used in a domain, every provider for that domain supports them // Check that if any aliases / ptr / etc.. are used in a domain, every provider for that domain supports them
for _, d := range config.Domains { for _, d := range config.Domains {
err := checkProviderCapabilities(d, config.DNSProviders) err := checkProviderCapabilities(d, config.DNSProviders)
if err != nil { if err != nil {
@ -449,14 +450,14 @@ func applyRecordTransforms(domain *models.DomainConfig) error {
if err != nil { if err != nil {
return err return err
} }
ip := net.ParseIP(rec.Target) //ip already validated above ip := net.ParseIP(rec.Target) // ip already validated above
newIPs, err := transform.TransformIPToList(net.ParseIP(rec.Target), table) newIPs, err := transform.TransformIPToList(net.ParseIP(rec.Target), table)
if err != nil { if err != nil {
return err return err
} }
for i, newIP := range newIPs { for i, newIP := range newIPs {
if i == 0 && !newIP.Equal(ip) { if i == 0 && !newIP.Equal(ip) {
rec.Target = newIP.String() //replace target of first record if different rec.Target = newIP.String() // replace target of first record if different
} else if i > 0 { } else if i > 0 {
// any additional ips need identical records with the alternate ip added to the domain // any additional ips need identical records with the alternate ip added to the domain
copy, err := rec.Copy() copy, err := rec.Copy()

View File

@ -112,7 +112,7 @@ func Test_transform_cname(t *testing.T) {
} }
func TestNSAtRoot(t *testing.T) { func TestNSAtRoot(t *testing.T) {
//do not allow ns records for @ // do not allow ns records for @
rec := &models.RecordConfig{Name: "test", Type: "NS", Target: "ns1.name.com."} rec := &models.RecordConfig{Name: "test", Type: "NS", Target: "ns1.name.com."}
errs := checkTargets(rec, "foo.com") errs := checkTargets(rec, "foo.com")
if len(errs) > 0 { if len(errs) > 0 {

View File

@ -22,7 +22,7 @@ type CLI interface {
PromptToRun() bool PromptToRun() bool
} }
// Printer is a simple acstraction for printing data. Can be passed to providers to give simple output capabilities. // Printer is a simple abstraction for printing data. Can be passed to providers to give simple output capabilities.
type Printer interface { type Printer interface {
Debugf(fmt string, args ...interface{}) Debugf(fmt string, args ...interface{})
Warnf(fmt string, args ...interface{}) Warnf(fmt string, args ...interface{})
@ -30,16 +30,20 @@ type Printer interface {
var reader = bufio.NewReader(os.Stdin) var reader = bufio.NewReader(os.Stdin)
// ConsolePrinter is a handle for the console printer.
type ConsolePrinter struct{} type ConsolePrinter struct{}
// StartDomain is called at the start of each domain.
func (c ConsolePrinter) StartDomain(domain string) { func (c ConsolePrinter) StartDomain(domain string) {
fmt.Printf("******************** Domain: %s\n", domain) fmt.Printf("******************** Domain: %s\n", domain)
} }
// PrintCorrection is called to print/format each correction.
func (c ConsolePrinter) PrintCorrection(i int, correction *models.Correction) { func (c ConsolePrinter) PrintCorrection(i int, correction *models.Correction) {
fmt.Printf("#%d: %s\n", i+1, correction.Msg) fmt.Printf("#%d: %s\n", i+1, correction.Msg)
} }
// PromptToRun prompts the user to see if they want to execute a correction.
func (c ConsolePrinter) PromptToRun() bool { func (c ConsolePrinter) PromptToRun() bool {
fmt.Print("Run? (Y/n): ") fmt.Print("Run? (Y/n): ")
txt, err := reader.ReadString('\n') txt, err := reader.ReadString('\n')
@ -57,6 +61,7 @@ func (c ConsolePrinter) PromptToRun() bool {
return run return run
} }
// EndCorrection is called at the end of each correction.
func (c ConsolePrinter) EndCorrection(err error) { func (c ConsolePrinter) EndCorrection(err error) {
if err != nil { if err != nil {
fmt.Println("FAILURE!", err) fmt.Println("FAILURE!", err)
@ -65,6 +70,7 @@ func (c ConsolePrinter) EndCorrection(err error) {
} }
} }
// StartDNSProvider is called at the start of each new provider.
func (c ConsolePrinter) StartDNSProvider(provider string, skip bool) { func (c ConsolePrinter) StartDNSProvider(provider string, skip bool) {
lbl := "" lbl := ""
if skip { if skip {
@ -73,6 +79,7 @@ func (c ConsolePrinter) StartDNSProvider(provider string, skip bool) {
fmt.Printf("----- DNS Provider: %s...%s", provider, lbl) fmt.Printf("----- DNS Provider: %s...%s", provider, lbl)
} }
// StartRegistrar is called at the start of each new registrar.
func (c ConsolePrinter) StartRegistrar(provider string, skip bool) { func (c ConsolePrinter) StartRegistrar(provider string, skip bool) {
lbl := "" lbl := ""
if skip { if skip {
@ -81,6 +88,7 @@ func (c ConsolePrinter) StartRegistrar(provider string, skip bool) {
fmt.Printf("----- Registrar: %s...%s", provider, lbl) fmt.Printf("----- Registrar: %s...%s", provider, lbl)
} }
// EndProvider is called at the end of each provider.
func (c ConsolePrinter) EndProvider(numCorrections int, err error) { func (c ConsolePrinter) EndProvider(numCorrections int, err error) {
if err != nil { if err != nil {
fmt.Println("ERROR") fmt.Println("ERROR")
@ -94,10 +102,12 @@ func (c ConsolePrinter) EndProvider(numCorrections int, err error) {
} }
} }
// Debugf is called to print/format debug information.
func (c ConsolePrinter) Debugf(format string, args ...interface{}) { func (c ConsolePrinter) Debugf(format string, args ...interface{}) {
fmt.Printf(format, args...) fmt.Printf(format, args...)
} }
// Warnf is called to print/format a warning.
func (c ConsolePrinter) Warnf(format string, args ...interface{}) { func (c ConsolePrinter) Warnf(format string, args ...interface{}) {
fmt.Printf("WARNING: "+format, args...) fmt.Printf("WARNING: "+format, args...)
} }

View File

@ -5,6 +5,7 @@ import (
"strings" "strings"
) )
// TXT outputs s as a TXT record.
func (s *SPFRecord) TXT() string { func (s *SPFRecord) TXT() string {
text := "v=spf1" text := "v=spf1"
for _, p := range s.Parts { for _, p := range s.Parts {
@ -15,12 +16,12 @@ func (s *SPFRecord) TXT() string {
const maxLen = 255 const maxLen = 255
//TXTSplit returns a set of txt records to use for SPF. // TXTSplit returns a set of txt records to use for SPF.
//pattern given is used to name all chained spf records. // pattern given is used to name all chained spf records.
//patern should include %d, which will be replaced by a counter. // patern should include %d, which will be replaced by a counter.
//should result in fqdn after replacement // should result in fqdn after replacement
//returned map will have keys with fqdn of resulting records. // returned map will have keys with fqdn of resulting records.
//root record will be under key "@" // root record will be under key "@"
func (s *SPFRecord) TXTSplit(pattern string) map[string]string { func (s *SPFRecord) TXTSplit(pattern string) map[string]string {
m := map[string]string{} m := map[string]string{}
s.split("@", pattern, 1, m) s.split("@", pattern, 1, m)
@ -54,7 +55,7 @@ func (s *SPFRecord) split(thisfqdn string, pattern string, nextIdx int, m map[st
} else { } else {
over = true over = true
if addedCount == 0 { if addedCount == 0 {
//the first part is too big to include. We kinda have to give up here. // the first part is too big to include. We kinda have to give up here.
m[thisfqdn] = base m[thisfqdn] = base
return return
} }
@ -68,6 +69,7 @@ func (s *SPFRecord) split(thisfqdn string, pattern string, nextIdx int, m map[st
newRec.split(nextFQDN, pattern, nextIdx+1, m) newRec.split(nextFQDN, pattern, nextIdx+1, m)
} }
// Flatten optimizes s.
func (s *SPFRecord) Flatten(spec string) *SPFRecord { func (s *SPFRecord) Flatten(spec string) *SPFRecord {
newRec := &SPFRecord{} newRec := &SPFRecord{}
for _, p := range s.Parts { for _, p := range s.Parts {
@ -75,10 +77,10 @@ func (s *SPFRecord) Flatten(spec string) *SPFRecord {
// non-includes copy straight over // non-includes copy straight over
newRec.Parts = append(newRec.Parts, p) newRec.Parts = append(newRec.Parts, p)
} else if !matchesFlatSpec(spec, p.IncludeDomain) { } else if !matchesFlatSpec(spec, p.IncludeDomain) {
//includes that don't match get copied straight across // includes that don't match get copied straight across
newRec.Parts = append(newRec.Parts, p) newRec.Parts = append(newRec.Parts, p)
} else { } else {
//flatten child recursively // flatten child recursively
flattenedChild := p.IncludeRecord.Flatten(spec) flattenedChild := p.IncludeRecord.Flatten(spec)
// include their parts (skipping final all term) // include their parts (skipping final all term)
for _, childPart := range flattenedChild.Parts[:len(flattenedChild.Parts)-1] { for _, childPart := range flattenedChild.Parts[:len(flattenedChild.Parts)-1] {

View File

@ -9,10 +9,12 @@ import (
"io" "io"
) )
// SPFRecord stores the parts of an SPF record.
type SPFRecord struct { type SPFRecord struct {
Parts []*SPFPart Parts []*SPFPart
} }
// Lookups returns the number of DNS lookups required by s.
func (s *SPFRecord) Lookups() int { func (s *SPFRecord) Lookups() int {
count := 0 count := 0
for _, p := range s.Parts { for _, p := range s.Parts {
@ -26,6 +28,7 @@ func (s *SPFRecord) Lookups() int {
return count return count
} }
// SPFPart stores a part of an SPF record, with attributes.
type SPFPart struct { type SPFPart struct {
Text string Text string
IsLookup bool IsLookup bool
@ -40,6 +43,7 @@ var qualifiers = map[byte]bool{
'+': true, '+': true,
} }
// Parse parses a raw SPF record.
func Parse(text string, dnsres Resolver) (*SPFRecord, error) { func Parse(text string, dnsres Resolver) (*SPFRecord, error) {
if !strings.HasPrefix(text, "v=spf1 ") { if !strings.HasPrefix(text, "v=spf1 ") {
return nil, fmt.Errorf("Not an spf record") return nil, fmt.Errorf("Not an spf record")
@ -53,12 +57,12 @@ func Parse(text string, dnsres Resolver) (*SPFRecord, error) {
} }
rec.Parts = append(rec.Parts, p) rec.Parts = append(rec.Parts, p)
if part == "all" { if part == "all" {
//all. nothing else matters. // all. nothing else matters.
break break
} else if strings.HasPrefix(part, "a") || strings.HasPrefix(part, "mx") { } else if strings.HasPrefix(part, "a") || strings.HasPrefix(part, "mx") {
p.IsLookup = true p.IsLookup = true
} else if strings.HasPrefix(part, "ip4:") || strings.HasPrefix(part, "ip6:") { } else if strings.HasPrefix(part, "ip4:") || strings.HasPrefix(part, "ip6:") {
//ip address, 0 lookups // ip address, 0 lookups
continue continue
} else if strings.HasPrefix(part, "include:") { } else if strings.HasPrefix(part, "include:") {
p.IsLookup = true p.IsLookup = true
@ -100,8 +104,9 @@ func dump(rec *SPFRecord, indent string, w io.Writer) {
} }
} }
func (rec *SPFRecord) Print() string { // Print prints an SPFRecord.
func (s *SPFRecord) Print() string {
w := &bytes.Buffer{} w := &bytes.Buffer{}
dump(rec, "", w) dump(s, "", w)
return w.String() return w.String()
} }

View File

@ -17,6 +17,7 @@ type Resolver interface {
// LiveResolver simply queries DNS to resolve SPF records. // LiveResolver simply queries DNS to resolve SPF records.
type LiveResolver struct{} type LiveResolver struct{}
// GetSPF looks up the SPF record named "name".
func (l LiveResolver) GetSPF(name string) (string, error) { func (l LiveResolver) GetSPF(name string) (string, error) {
vals, err := net.LookupTXT(name) vals, err := net.LookupTXT(name)
if err != nil { if err != nil {
@ -66,6 +67,7 @@ type cache struct {
inner Resolver inner Resolver
} }
// NewCache creates a new cache file named filename.
func NewCache(filename string) (CachingResolver, error) { func NewCache(filename string) (CachingResolver, error) {
f, err := os.Open(filename) f, err := os.Open(filename)
if err != nil { if err != nil {

View File

@ -8,6 +8,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// ReverseDomainName turns a CIDR block into a reversed (in-addr) name.
func ReverseDomainName(cidr string) (string, error) { func ReverseDomainName(cidr string) (string, error) {
a, c, err := net.ParseCIDR(cidr) a, c, err := net.ParseCIDR(cidr)
if err != nil { if err != nil {

View File

@ -71,7 +71,7 @@ func TestReverse(t *testing.T) {
{"174.1.0.0/31", false, "0/31.0.1.174.in-addr.arpa"}, {"174.1.0.0/31", false, "0/31.0.1.174.in-addr.arpa"},
{"174.1.0.2/31", false, "2/31.0.1.174.in-addr.arpa"}, {"174.1.0.2/31", false, "2/31.0.1.174.in-addr.arpa"},
//Errror Cases: // Error Cases:
{"0.0.0.0/0", true, ""}, {"0.0.0.0/0", true, ""},
{"2001::/0", true, ""}, {"2001::/0", true, ""},
{"4.5/16", true, ""}, {"4.5/16", true, ""},

View File

@ -10,6 +10,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// PtrNameMagic implements the PTR magic.
func PtrNameMagic(name, domain string) (string, error) { func PtrNameMagic(name, domain string) (string, error) {
// Implement the PTR name magic. If the name is a properly formed // Implement the PTR name magic. If the name is a properly formed
// IPv4 or IPv6 address, we replace it with the right string (i.e // IPv4 or IPv6 address, we replace it with the right string (i.e
@ -20,9 +21,8 @@ func PtrNameMagic(name, domain string) (string, error) {
if strings.HasSuffix(name, ".in-addr.arpa.") || strings.HasSuffix(name, ".ip6.arpa.") { if strings.HasSuffix(name, ".in-addr.arpa.") || strings.HasSuffix(name, ".ip6.arpa.") {
if strings.HasSuffix(name, "."+domain+".") { if strings.HasSuffix(name, "."+domain+".") {
return strings.TrimSuffix(name, "."+domain+"."), nil return strings.TrimSuffix(name, "."+domain+"."), nil
} else {
return name, errors.Errorf("PTR record %v in wrong domain (%v)", name, domain)
} }
return name, errors.Errorf("PTR record %v in wrong domain (%v)", name, domain)
} }
// If the domain is .arpa, we do magic. // If the domain is .arpa, we do magic.

View File

@ -66,8 +66,8 @@ func TestPtrMagic(t *testing.T) {
{"2001:db8::1", "9.9.ip6.arpa", "", true}, {"2001:db8::1", "9.9.ip6.arpa", "", true},
// These should be errors but we don't check for them at this time: // These should be errors but we don't check for them at this time:
//{"blurg", "3.4.in-addr.arpa", "blurg", true}, // {"blurg", "3.4.in-addr.arpa", "blurg", true},
//{"1", "3.4.in-addr.arpa", "1", true}, // {"1", "3.4.in-addr.arpa", "1", true},
} }
for _, tst := range tests { for _, tst := range tests {
t.Run(fmt.Sprintf("%s %s", tst.name, tst.domain), func(t *testing.T) { t.Run(fmt.Sprintf("%s %s", tst.name, tst.domain), func(t *testing.T) {

View File

@ -6,6 +6,7 @@ import (
"strings" "strings"
) )
// IpConversion describes an IP conversion.
type IpConversion struct { type IpConversion struct {
Low, High net.IP Low, High net.IP
NewBases []net.IP NewBases []net.IP
@ -21,6 +22,7 @@ func ipToUint(i net.IP) (uint32, error) {
return r, nil return r, nil
} }
// UintToIP convert a 32-bit into into a net.IP.
func UintToIP(u uint32) net.IP { func UintToIP(u uint32) net.IP {
return net.IPv4( return net.IPv4(
byte((u>>24)&255), byte((u>>24)&255),
@ -36,7 +38,7 @@ func DecodeTransformTable(transforms string) ([]IpConversion, error) {
for ri, row := range rows { for ri, row := range rows {
items := strings.Split(row, "~") items := strings.Split(row, "~")
if len(items) != 4 { if len(items) != 4 {
return nil, fmt.Errorf("transform_table rows should have 4 elements. (%v) found in row (%v) of %#v\n", len(items), ri, transforms) return nil, fmt.Errorf("transform_table rows should have 4 elements. (%v) found in row (%v) of %#v", len(items), ri, transforms)
} }
for i, item := range items { for i, item := range items {
items[i] = strings.TrimSpace(item) items[i] = strings.TrimSpace(item)
@ -72,7 +74,7 @@ func DecodeTransformTable(transforms string) ([]IpConversion, error) {
low, _ := ipToUint(con.Low) low, _ := ipToUint(con.Low)
high, _ := ipToUint(con.High) high, _ := ipToUint(con.High)
if low > high { if low > high {
return nil, fmt.Errorf("transform_table Low should be less than High. row (%v) %v>%v (%v)\n", ri, con.Low, con.High, transforms) return nil, fmt.Errorf("transform_table Low should be less than High. row (%v) %v>%v (%v)", ri, con.Low, con.High, transforms)
} }
if len(con.NewBases) > 0 && len(con.NewIPs) > 0 { if len(con.NewBases) > 0 && len(con.NewIPs) > 0 {
return nil, fmt.Errorf("transform_table_rows should only specify one of NewBases or NewIPs, Not both") return nil, fmt.Errorf("transform_table_rows should only specify one of NewBases or NewIPs, Not both")

View File

@ -21,7 +21,7 @@ func TestIPToUint(t *testing.T) {
} }
} }
func Test_DecodeTransformTable_failures(t *testing.T) { func TestDecodeTransformTableFailures(t *testing.T) {
result, err := DecodeTransformTable("1.2.3.4 ~ 3.4.5.6") result, err := DecodeTransformTable("1.2.3.4 ~ 3.4.5.6")
if result != nil { if result != nil {
t.Errorf("expected nil, got (%v)\n", result) t.Errorf("expected nil, got (%v)\n", result)
@ -31,7 +31,7 @@ func Test_DecodeTransformTable_failures(t *testing.T) {
} }
} }
func test_ip(t *testing.T, test string, expected string, actual net.IP) { func testIP(t *testing.T, test string, expected string, actual net.IP) {
if !net.ParseIP(expected).Equal(actual) { if !net.ParseIP(expected).Equal(actual) {
t.Errorf("Test %v: expected Low (%v), got (%v)\n", test, actual, expected) t.Errorf("Test %v: expected Low (%v), got (%v)\n", test, actual, expected)
} }
@ -45,10 +45,10 @@ func Test_DecodeTransformTable_0(t *testing.T) {
if len(result) != 1 { if len(result) != 1 {
t.Errorf("Test %v: expected col length (%v), got (%v)\n", 1, 1, len(result)) t.Errorf("Test %v: expected col length (%v), got (%v)\n", 1, 1, len(result))
} }
test_ip(t, "low", "1.2.3.4", result[0].Low) testIP(t, "low", "1.2.3.4", result[0].Low)
test_ip(t, "high", "2.3.4.5", result[0].High) testIP(t, "high", "2.3.4.5", result[0].High)
test_ip(t, "newBase", "3.4.5.6", result[0].NewBases[0]) testIP(t, "newBase", "3.4.5.6", result[0].NewBases[0])
//test_ip(t, "newIP", "", result[0].NewIPs) // test_ip(t, "newIP", "", result[0].NewIPs)
} }
func Test_DecodeTransformTable_1(t *testing.T) { func Test_DecodeTransformTable_1(t *testing.T) {
@ -59,14 +59,14 @@ func Test_DecodeTransformTable_1(t *testing.T) {
if len(result) != 2 { if len(result) != 2 {
t.Errorf("Test %v: expected col length (%v), got (%v)\n", 1, 2, len(result)) t.Errorf("Test %v: expected col length (%v), got (%v)\n", 1, 2, len(result))
} }
test_ip(t, "Low[0]", "1.2.3.4", result[0].Low) testIP(t, "Low[0]", "1.2.3.4", result[0].Low)
test_ip(t, "High[0]", "2.3.4.5", result[0].High) testIP(t, "High[0]", "2.3.4.5", result[0].High)
test_ip(t, "NewBase[0]", "3.4.5.6", result[0].NewBases[0]) testIP(t, "NewBase[0]", "3.4.5.6", result[0].NewBases[0])
//test_ip(t, "newIP[0]", "", result[0].NewIP) // test_ip(t, "newIP[0]", "", result[0].NewIP)
test_ip(t, "Low[1]", "8.7.6.5", result[1].Low) testIP(t, "Low[1]", "8.7.6.5", result[1].Low)
test_ip(t, "High[1]", "9.8.7.6", result[1].High) testIP(t, "High[1]", "9.8.7.6", result[1].High)
test_ip(t, "NewBase[1]", "7.6.5.4", result[1].NewBases[0]) testIP(t, "NewBase[1]", "7.6.5.4", result[1].NewBases[0])
//test_ip(t, "newIP[1]", "", result[0].NewIP) // test_ip(t, "newIP[1]", "", result[0].NewIP)
} }
func Test_DecodeTransformTable_NewIP(t *testing.T) { func Test_DecodeTransformTable_NewIP(t *testing.T) {
result, err := DecodeTransformTable("1.2.3.4 ~ 2.3.4.5 ~ ~ 3.4.5.6 ") result, err := DecodeTransformTable("1.2.3.4 ~ 2.3.4.5 ~ ~ 3.4.5.6 ")
@ -76,9 +76,9 @@ func Test_DecodeTransformTable_NewIP(t *testing.T) {
if len(result) != 1 { if len(result) != 1 {
t.Errorf("Test %v: expected col length (%v), got (%v)\n", 1, 1, len(result)) t.Errorf("Test %v: expected col length (%v), got (%v)\n", 1, 1, len(result))
} }
test_ip(t, "low", "1.2.3.4", result[0].Low) testIP(t, "low", "1.2.3.4", result[0].Low)
test_ip(t, "high", "2.3.4.5", result[0].High) testIP(t, "high", "2.3.4.5", result[0].High)
test_ip(t, "newIP", "3.4.5.6", result[0].NewIPs[0]) testIP(t, "newIP", "3.4.5.6", result[0].NewIPs[0])
} }
func Test_DecodeTransformTable_order(t *testing.T) { func Test_DecodeTransformTable_order(t *testing.T) {
@ -126,7 +126,7 @@ func Test_TransformIP(t *testing.T) {
High: net.ParseIP("55.255.0.0"), High: net.ParseIP("55.255.0.0"),
NewBases: []net.IP{net.ParseIP("66.0.0.0"), net.ParseIP("77.0.0.0")}, NewBases: []net.IP{net.ParseIP("66.0.0.0"), net.ParseIP("77.0.0.0")},
}} }}
//NO TRANSFORMS ON 99.x.x.x PLZ // NO TRANSFORMS ON 99.x.x.x PLZ
var tests = []struct { var tests = []struct {
experiment string experiment string

View File

@ -1,8 +1,8 @@
//Package all is simply a container to reference all known provider implementations for easy import into other packages // Package all is simply a container to reference all known provider implementations for easy import into other packages
package all package all
import ( import (
//Define all known providers here. They should each register themselves with the providers package via init function. // Define all known providers here. They should each register themselves with the providers package via init function.
_ "github.com/StackExchange/dnscontrol/providers/activedir" _ "github.com/StackExchange/dnscontrol/providers/activedir"
_ "github.com/StackExchange/dnscontrol/providers/bind" _ "github.com/StackExchange/dnscontrol/providers/bind"
_ "github.com/StackExchange/dnscontrol/providers/cloudflare" _ "github.com/StackExchange/dnscontrol/providers/cloudflare"

View File

@ -37,7 +37,7 @@ D('ds.stackexchange.com', REG_NONE,
) )
//records handled by another provider... // records handled by another provider...
); );
``` ```

View File

@ -17,6 +17,7 @@ import (
const zoneDumpFilenamePrefix = "adzonedump" const zoneDumpFilenamePrefix = "adzonedump"
// RecordConfigJson RecordConfig, reconfigured for JSON input/output.
type RecordConfigJson struct { type RecordConfigJson struct {
Name string `json:"hostname"` Name string `json:"hostname"`
Type string `json:"recordtype"` Type string `json:"recordtype"`
@ -25,7 +26,7 @@ type RecordConfigJson struct {
} }
func (c *adProvider) GetNameservers(string) ([]*models.Nameserver, error) { func (c *adProvider) GetNameservers(string) ([]*models.Nameserver, error) {
//TODO: If using AD for publicly hosted zones, probably pull these from config. // TODO: If using AD for publicly hosted zones, probably pull these from config.
return nil, nil return nil, nil
} }
@ -99,11 +100,11 @@ func (c *adProvider) logOutput(s string) error {
// powerShellLogErr logs that a PowerShell command had an error. // powerShellLogErr logs that a PowerShell command had an error.
func (c *adProvider) logErr(e error) error { func (c *adProvider) logErr(e error) error {
err := c.logHelper(fmt.Sprintf("ERROR: %v\r\r", e)) //Log error to powershell.log err := c.logHelper(fmt.Sprintf("ERROR: %v\r\r", e)) // Log error to powershell.log
if err != nil { if err != nil {
return err //Bubble up error created in logHelper return err // Bubble up error created in logHelper
} }
return e //Bubble up original error return e // Bubble up original error
} }
func (c *adProvider) logHelper(s string) error { func (c *adProvider) logHelper(s string) error {
@ -125,17 +126,17 @@ func (c *adProvider) logHelper(s string) error {
func (c *adProvider) powerShellRecord(command string) error { func (c *adProvider) powerShellRecord(command string) error {
recordfile, err := os.OpenFile(c.psOut, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0660) recordfile, err := os.OpenFile(c.psOut, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0660)
if err != nil { if err != nil {
return fmt.Errorf("Can not create/append to %#v: %v\n", c.psOut, err) return fmt.Errorf("can not create/append to %#v: %v", c.psOut, err)
} }
_, err = recordfile.WriteString(command) _, err = recordfile.WriteString(command)
if err != nil { if err != nil {
return fmt.Errorf("Append to %#v failed: %v\n", c.psOut, err) return fmt.Errorf("append to %#v failed: %v", c.psOut, err)
} }
return recordfile.Close() return recordfile.Close()
} }
func (c *adProvider) getExistingRecords(domainname string) ([]*models.RecordConfig, error) { func (c *adProvider) getExistingRecords(domainname string) ([]*models.RecordConfig, error) {
//log.Printf("getExistingRecords(%s)\n", domainname) // log.Printf("getExistingRecords(%s)\n", domainname)
// Get the JSON either from adzonedump or by running a PowerShell script. // Get the JSON either from adzonedump or by running a PowerShell script.
data, err := c.getRecords(domainname) data, err := c.getRecords(domainname)
@ -211,7 +212,7 @@ func (c *adProvider) generatePowerShellCreate(domainname string, rec *models.Rec
case "NS": case "NS":
text = fmt.Sprintf("\r\n"+`echo "Skipping NS update (%v %v)"`+"\r\n", rec.Name, rec.Target) text = fmt.Sprintf("\r\n"+`echo "Skipping NS update (%v %v)"`+"\r\n", rec.Name, rec.Target)
default: default:
panic(fmt.Errorf("ERROR: generatePowerShellCreate() does not yet handle recType=%s recName=%#v content=%#v)\n", rec.Type, rec.Name, content)) panic(fmt.Errorf("generatePowerShellCreate() does not yet handle recType=%s recName=%#v content=%#v)", rec.Type, rec.Name, content))
// We panic so that we quickly find any switch statements // We panic so that we quickly find any switch statements
// that have not been updated for a new RR type. // that have not been updated for a new RR type.
} }
@ -233,7 +234,7 @@ func (c *adProvider) generatePowerShellModify(domainname, recName, recType, oldC
queryField = "HostNameAlias" queryField = "HostNameAlias"
queryContent = `"` + oldContent + `"` queryContent = `"` + oldContent + `"`
default: default:
panic(fmt.Errorf("ERROR: generatePowerShellModify() does not yet handle recType=%s recName=%#v content=(%#v, %#v)\n", recType, recName, oldContent, newContent)) panic(fmt.Errorf("generatePowerShellModify() does not yet handle recType=%s recName=%#v content=(%#v, %#v)", recType, recName, oldContent, newContent))
// We panic so that we quickly find any switch statements // We panic so that we quickly find any switch statements
// that have not been updated for a new RR type. // that have not been updated for a new RR type.
} }

View File

@ -65,6 +65,7 @@ func init() {
providers.RegisterDomainServiceProviderType("BIND", initBind, features) providers.RegisterDomainServiceProviderType("BIND", initBind, features)
} }
// SoaInfo contains the parts of a SOA rtype.
type SoaInfo struct { type SoaInfo struct {
Ns string `json:"master"` Ns string `json:"master"`
Mbox string `json:"mbox"` Mbox string `json:"mbox"`
@ -79,6 +80,7 @@ func (s SoaInfo) String() string {
return fmt.Sprintf("%s %s %d %d %d %d %d", s.Ns, s.Mbox, s.Serial, s.Refresh, s.Retry, s.Expire, s.Minttl) return fmt.Sprintf("%s %s %d %d %d %d %d", s.Ns, s.Mbox, s.Serial, s.Refresh, s.Retry, s.Expire, s.Minttl)
} }
// Bind is the provider handle for the Bind driver.
type Bind struct { type Bind struct {
DefaultNS []string `json:"default_ns"` DefaultNS []string `json:"default_ns"`
DefaultSoa SoaInfo `json:"default_soa"` DefaultSoa SoaInfo `json:"default_soa"`
@ -86,7 +88,7 @@ type Bind struct {
directory string directory string
} }
//var bindSkeletin = flag.String("bind_skeletin", "skeletin/master/var/named/chroot/var/named/master", "") // var bindSkeletin = flag.String("bind_skeletin", "skeletin/master/var/named/chroot/var/named/master", "")
func rrToRecord(rr dns.RR, origin string, replaceSerial uint32) (models.RecordConfig, uint32) { func rrToRecord(rr dns.RR, origin string, replaceSerial uint32) (models.RecordConfig, uint32) {
// Convert's dns.RR into our native data type (models.RecordConfig). // Convert's dns.RR into our native data type (models.RecordConfig).
@ -95,7 +97,7 @@ func rrToRecord(rr dns.RR, origin string, replaceSerial uint32) (models.RecordCo
// replaceSerial != 0, change the serial to replaceSerial. // replaceSerial != 0, change the serial to replaceSerial.
// WARNING(tlim): This assumes SOAs do not have serial=0. // WARNING(tlim): This assumes SOAs do not have serial=0.
// If one is found, we replace it with serial=1. // If one is found, we replace it with serial=1.
var old_serial, new_serial uint32 var oldSerial, newSerial uint32
header := rr.Header() header := rr.Header()
rc := models.RecordConfig{} rc := models.RecordConfig{}
rc.Type = dns.TypeToString[header.Rrtype] rc.Type = dns.TypeToString[header.Rrtype]
@ -121,17 +123,17 @@ func rrToRecord(rr dns.RR, origin string, replaceSerial uint32) (models.RecordCo
case *dns.PTR: case *dns.PTR:
rc.Target = v.Ptr rc.Target = v.Ptr
case *dns.SOA: case *dns.SOA:
old_serial = v.Serial oldSerial = v.Serial
if old_serial == 0 { if oldSerial == 0 {
// For SOA records, we never return a 0 serial number. // For SOA records, we never return a 0 serial number.
old_serial = 1 oldSerial = 1
} }
new_serial = v.Serial newSerial = v.Serial
if (dnsutil.TrimDomainName(rc.Name, origin+".") == "@") && replaceSerial != 0 { if (dnsutil.TrimDomainName(rc.Name, origin+".") == "@") && replaceSerial != 0 {
new_serial = replaceSerial newSerial = replaceSerial
} }
rc.Target = fmt.Sprintf("%v %v %v %v %v %v %v", rc.Target = fmt.Sprintf("%v %v %v %v %v %v %v",
v.Ns, v.Mbox, new_serial, v.Refresh, v.Retry, v.Expire, v.Minttl) v.Ns, v.Mbox, newSerial, v.Refresh, v.Retry, v.Expire, v.Minttl)
case *dns.SRV: case *dns.SRV:
rc.Target = v.Target rc.Target = v.Target
rc.SrvPort = v.Port rc.SrvPort = v.Port
@ -148,7 +150,7 @@ func rrToRecord(rr dns.RR, origin string, replaceSerial uint32) (models.RecordCo
default: default:
log.Fatalf("rrToRecord: Unimplemented zone record type=%s (%v)\n", rc.Type, rr) log.Fatalf("rrToRecord: Unimplemented zone record type=%s (%v)\n", rc.Type, rr)
} }
return rc, old_serial return rc, oldSerial
} }
func makeDefaultSOA(info SoaInfo, origin string) *models.RecordConfig { func makeDefaultSOA(info SoaInfo, origin string) *models.RecordConfig {
@ -184,10 +186,12 @@ func makeDefaultSOA(info SoaInfo, origin string) *models.RecordConfig {
return &soaRec return &soaRec
} }
// GetNameservers returns the nameservers for a domain.
func (c *Bind) GetNameservers(string) ([]*models.Nameserver, error) { func (c *Bind) GetNameservers(string) ([]*models.Nameserver, error) {
return c.nameservers, nil return c.nameservers, nil
} }
// GetDomainCorrections returns a list of corrections to update a domain.
func (c *Bind) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { func (c *Bind) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
dc.Punycode() dc.Punycode()
// Phase 1: Copy everything to []*models.RecordConfig: // Phase 1: Copy everything to []*models.RecordConfig:
@ -228,7 +232,7 @@ func (c *Bind) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correcti
if serial != 0 { if serial != 0 {
// This was an SOA record. Update the serial. // This was an SOA record. Update the serial.
oldSerial = serial oldSerial = serial
newSerial = generate_serial(oldSerial) newSerial = generateSerial(oldSerial)
// Regenerate with new serial: // Regenerate with new serial:
*soaRec, _ = rrToRecord(x.RR, dc.Name, newSerial) *soaRec, _ = rrToRecord(x.RR, dc.Name, newSerial)
rec = *soaRec rec = *soaRec

View File

@ -1,6 +1,7 @@
package bind
// Generate zonefiles. // Generate zonefiles.
// This generates a zonefile that prioritizes beauty over efficiency. // This generates a zonefile that prioritizes beauty over efficiency.
package bind
import ( import (
"bytes" "bytes"
@ -98,10 +99,10 @@ func (z *zoneGenData) Less(i, j int) bool {
return a.String() < b.String() return a.String() < b.String()
} }
// mostCommonTtl returns the most common TTL in a set of records. If there is // mostCommonTTL returns the most common TTL in a set of records. If there is
// a tie, the highest TTL is selected. This makes the results consistent. // a tie, the highest TTL is selected. This makes the results consistent.
// NS records are not included in the analysis because Tom said so. // NS records are not included in the analysis because Tom said so.
func mostCommonTtl(records []dns.RR) uint32 { func mostCommonTTL(records []dns.RR) uint32 {
// Index the TTLs in use: // Index the TTLs in use:
d := make(map[uint32]int) d := make(map[uint32]int)
for _, r := range records { for _, r := range records {
@ -141,11 +142,11 @@ func WriteZoneFile(w io.Writer, records []dns.RR, origin string) error {
// * $TTL is used to eliminate clutter. The most common TTL value is used. // * $TTL is used to eliminate clutter. The most common TTL value is used.
// * "@" is used instead of the apex domain name. // * "@" is used instead of the apex domain name.
defaultTtl := mostCommonTtl(records) defaultTTL := mostCommonTTL(records)
z := &zoneGenData{ z := &zoneGenData{
Origin: dnsutil.AddOrigin(origin, "."), Origin: dnsutil.AddOrigin(origin, "."),
DefaultTtl: defaultTtl, DefaultTtl: defaultTTL,
} }
z.Records = nil z.Records = nil
for _, r := range records { for _, r := range records {
@ -200,9 +201,6 @@ func (z *zoneGenData) generateZoneFileHelper(w io.Writer) error {
// items[4]: the remaining line // items[4]: the remaining line
target := items[4] target := items[4]
//if typeStr == "TXT" {
// fmt.Printf("generateZoneFileHelper.go: target=%#v\n", target)
//}
fmt.Fprintln(w, formatLine([]int{10, 5, 2, 5, 0}, []string{name, ttl, "IN", typeStr, target})) fmt.Fprintln(w, formatLine([]int{10, 5, 2, 5, 0}, []string{name, ttl, "IN", typeStr, target}))
} }
@ -291,12 +289,11 @@ func zoneLabelLess(a, b string) bool {
bu, berr := strconv.ParseUint(bs[j], 10, 64) bu, berr := strconv.ParseUint(bs[j], 10, 64)
if aerr == nil && berr == nil { if aerr == nil && berr == nil {
return au < bu return au < bu
} else { }
// otherwise, compare as strings: // otherwise, compare as strings:
return as[i] < bs[j] return as[i] < bs[j]
} }
} }
}
// The min top elements were equal, so the shorter name is less. // The min top elements were equal, so the shorter name is less.
return ia < ib return ia < ib
} }

View File

@ -47,7 +47,7 @@ func TestMostCommonTtl(t *testing.T) {
// All records are TTL=100 // All records are TTL=100
records = nil records = nil
records, e = append(records, r1, r1, r1), 100 records, e = append(records, r1, r1, r1), 100
g = mostCommonTtl(records) g = mostCommonTTL(records)
if e != g { if e != g {
t.Fatalf("expected %d; got %d\n", e, g) t.Fatalf("expected %d; got %d\n", e, g)
} }
@ -55,7 +55,7 @@ func TestMostCommonTtl(t *testing.T) {
// Mixture of TTLs with an obvious winner. // Mixture of TTLs with an obvious winner.
records = nil records = nil
records, e = append(records, r1, r2, r2), 200 records, e = append(records, r1, r2, r2), 200
g = mostCommonTtl(records) g = mostCommonTTL(records)
if e != g { if e != g {
t.Fatalf("expected %d; got %d\n", e, g) t.Fatalf("expected %d; got %d\n", e, g)
} }
@ -63,7 +63,7 @@ func TestMostCommonTtl(t *testing.T) {
// 3-way tie. Largest TTL should be used. // 3-way tie. Largest TTL should be used.
records = nil records = nil
records, e = append(records, r1, r2, r3), 300 records, e = append(records, r1, r2, r3), 300
g = mostCommonTtl(records) g = mostCommonTTL(records)
if e != g { if e != g {
t.Fatalf("expected %d; got %d\n", e, g) t.Fatalf("expected %d; got %d\n", e, g)
} }
@ -71,7 +71,7 @@ func TestMostCommonTtl(t *testing.T) {
// NS records are ignored. // NS records are ignored.
records = nil records = nil
records, e = append(records, r1, r4, r5), 100 records, e = append(records, r1, r4, r5), 100
g = mostCommonTtl(records) g = mostCommonTTL(records)
if e != g { if e != g {
t.Fatalf("expected %d; got %d\n", e, g) t.Fatalf("expected %d; got %d\n", e, g)
} }
@ -123,7 +123,7 @@ www 300 IN CNAME bosun.org.
} }
func TestWriteZoneFileMx(t *testing.T) { func TestWriteZoneFileMx(t *testing.T) {
//exhibits explicit ttls and long name // exhibits explicit ttls and long name
r1, _ := dns.NewRR(`bosun.org. 300 IN TXT "aaa"`) r1, _ := dns.NewRR(`bosun.org. 300 IN TXT "aaa"`)
r2, _ := dns.NewRR(`bosun.org. 300 IN TXT "bbb"`) r2, _ := dns.NewRR(`bosun.org. 300 IN TXT "bbb"`)
r2.(*dns.TXT).Txt[0] = `b"bb` r2.(*dns.TXT).Txt[0] = `b"bb`
@ -157,7 +157,7 @@ google._domainkey IN TXT "\"foo\""
` `
func TestWriteZoneFileSrv(t *testing.T) { func TestWriteZoneFileSrv(t *testing.T) {
//exhibits explicit ttls and long name // exhibits explicit ttls and long name
r1, _ := dns.NewRR(`bosun.org. 300 IN SRV 10 10 9999 foo.com.`) r1, _ := dns.NewRR(`bosun.org. 300 IN SRV 10 10 9999 foo.com.`)
r2, _ := dns.NewRR(`bosun.org. 300 IN SRV 10 20 5050 foo.com.`) r2, _ := dns.NewRR(`bosun.org. 300 IN SRV 10 20 5050 foo.com.`)
r3, _ := dns.NewRR(`bosun.org. 300 IN SRV 10 10 5050 foo.com.`) r3, _ := dns.NewRR(`bosun.org. 300 IN SRV 10 10 5050 foo.com.`)
@ -182,7 +182,7 @@ var testdataZFSRV = `$TTL 300
` `
func TestWriteZoneFilePtr(t *testing.T) { func TestWriteZoneFilePtr(t *testing.T) {
//exhibits explicit ttls and long name // exhibits explicit ttls and long name
r1, _ := dns.NewRR(`bosun.org. 300 IN PTR chell.bosun.org`) r1, _ := dns.NewRR(`bosun.org. 300 IN PTR chell.bosun.org`)
r2, _ := dns.NewRR(`bosun.org. 300 IN PTR barney.bosun.org.`) r2, _ := dns.NewRR(`bosun.org. 300 IN PTR barney.bosun.org.`)
r3, _ := dns.NewRR(`bosun.org. 300 IN PTR alex.bosun.org.`) r3, _ := dns.NewRR(`bosun.org. 300 IN PTR alex.bosun.org.`)
@ -203,7 +203,7 @@ var testdataZFPTR = `$TTL 300
` `
func TestWriteZoneFileCaa(t *testing.T) { func TestWriteZoneFileCaa(t *testing.T) {
//exhibits explicit ttls and long name // exhibits explicit ttls and long name
r1, _ := dns.NewRR(`bosun.org. 300 IN CAA 0 issuewild ";"`) r1, _ := dns.NewRR(`bosun.org. 300 IN CAA 0 issuewild ";"`)
r2, _ := dns.NewRR(`bosun.org. 300 IN CAA 0 issue "letsencrypt.org"`) r2, _ := dns.NewRR(`bosun.org. 300 IN CAA 0 issue "letsencrypt.org"`)
r3, _ := dns.NewRR(`bosun.org. 300 IN CAA 1 iodef "http://example.com"`) r3, _ := dns.NewRR(`bosun.org. 300 IN CAA 1 iodef "http://example.com"`)

View File

@ -7,10 +7,10 @@ import (
"time" "time"
) )
var nowFunc func() time.Time = time.Now var nowFunc = time.Now
// generate_serial takes an old SOA serial number and increments it. // generateSerial takes an old SOA serial number and increments it.
func generate_serial(old_serial uint32) uint32 { func generateSerial(oldSerial uint32) uint32 {
// Serial numbers are in the format yyyymmddvv // Serial numbers are in the format yyyymmddvv
// where vv is a version count that starts at 01 each day. // where vv is a version count that starts at 01 each day.
// Multiple serial numbers generated on the same day increase vv. // Multiple serial numbers generated on the same day increase vv.
@ -19,9 +19,9 @@ func generate_serial(old_serial uint32) uint32 {
// that is smaller than the old one, we punt and increment the old number. // that is smaller than the old one, we punt and increment the old number.
// At no time will a serial number == 0 be returned. // At no time will a serial number == 0 be returned.
original := old_serial original := oldSerial
old_serialStr := strconv.FormatUint(uint64(old_serial), 10) oldSerialStr := strconv.FormatUint(uint64(oldSerial), 10)
var new_serial uint32 var newSerial uint32
// Make draft new serial number: // Make draft new serial number:
today := nowFunc().UTC() today := nowFunc().UTC()
@ -34,38 +34,38 @@ func generate_serial(old_serial uint32) uint32 {
draft := uint32(todayNum)*100 + version draft := uint32(todayNum)*100 + version
method := "none" // Used only in debugging. method := "none" // Used only in debugging.
if old_serial > draft { if oldSerial > draft {
// If old_serial was really slow, upgrade to new yyyymmddvv standard: // If old_serial was really slow, upgrade to new yyyymmddvv standard:
method = "o>d" method = "o>d"
new_serial = old_serial + 1 newSerial = oldSerial + 1
new_serial = old_serial + 1 newSerial = oldSerial + 1
} else if old_serial == draft { } else if oldSerial == draft {
// Edge case: increment old serial: // Edge case: increment old serial:
method = "o=d" method = "o=d"
new_serial = draft + 1 newSerial = draft + 1
} else if len(old_serialStr) != 10 { } else if len(oldSerialStr) != 10 {
// If old_serial is wrong number of digits, upgrade to yyyymmddvv standard: // If old_serial is wrong number of digits, upgrade to yyyymmddvv standard:
method = "len!=10" method = "len!=10"
new_serial = draft newSerial = draft
} else if strings.HasPrefix(old_serialStr, todayStr) { } else if strings.HasPrefix(oldSerialStr, todayStr) {
// If old_serial just needs to be incremented: // If old_serial just needs to be incremented:
method = "prefix" method = "prefix"
new_serial = old_serial + 1 newSerial = oldSerial + 1
} else { } else {
// First serial number to be requested today: // First serial number to be requested today:
method = "default" method = "default"
new_serial = draft newSerial = draft
} }
if new_serial == 0 { if newSerial == 0 {
// We never return 0 as the serial number. // We never return 0 as the serial number.
new_serial = 1 newSerial = 1
} }
if old_serial == new_serial { if oldSerial == newSerial {
log.Fatalf("%v: old_serial == new_serial (%v == %v) draft=%v method=%v", original, old_serial, new_serial, draft, method) log.Fatalf("%v: old_serial == new_serial (%v == %v) draft=%v method=%v", original, oldSerial, newSerial, draft, method)
} }
if old_serial > new_serial { if oldSerial > newSerial {
log.Fatalf("%v: old_serial > new_serial (%v > %v) draft=%v method=%v", original, old_serial, new_serial, draft, method) log.Fatalf("%v: old_serial > new_serial (%v > %v) draft=%v method=%v", original, oldSerial, newSerial, draft, method)
} }
return new_serial return newSerial
} }

View File

@ -43,7 +43,7 @@ func Test_generate_serial_1(t *testing.T) {
nowFunc = func() time.Time { nowFunc = func() time.Time {
return tst.Today return tst.Today
} }
found := generate_serial(tst.Given) found := generateSerial(tst.Given)
if expected != found { if expected != found {
t.Fatalf("Test:%d/%v: Expected (%d) got (%d)\n", i, tst.Given, expected, found) t.Fatalf("Test:%d/%v: Expected (%d) got (%d)\n", i, tst.Given, expected, found)
} }

View File

@ -4,7 +4,7 @@ import (
"log" "log"
) )
//Capability is a bitmasked set of "features" that a provider supports. Only use constants from this package. // Capability is a bitmasked set of "features" that a provider supports. Only use constants from this package.
type Capability uint32 type Capability uint32
const ( const (
@ -44,6 +44,7 @@ const (
var providerCapabilities = map[string]map[Capability]bool{} var providerCapabilities = map[string]map[Capability]bool{}
// ProviderHasCabability returns true if provider has capability.
func ProviderHasCabability(pType string, cap Capability) bool { func ProviderHasCabability(pType string, cap Capability) bool {
if providerCapabilities[pType] == nil { if providerCapabilities[pType] == nil {
return false return false

View File

@ -48,6 +48,7 @@ func init() {
providers.RegisterCustomRecordType("CF_TEMP_REDIRECT", "CLOUDFLAREAPI", "") providers.RegisterCustomRecordType("CF_TEMP_REDIRECT", "CLOUDFLAREAPI", "")
} }
// CloudflareApi is the handle for API calls.
type CloudflareApi struct { type CloudflareApi struct {
ApiKey string `json:"apikey"` ApiKey string `json:"apikey"`
ApiUser string `json:"apiuser"` ApiUser string `json:"apiuser"`
@ -59,7 +60,7 @@ type CloudflareApi struct {
} }
func labelMatches(label string, matches []string) bool { func labelMatches(label string, matches []string) bool {
//log.Printf("DEBUG: labelMatches(%#v, %#v)\n", label, matches) // log.Printf("DEBUG: labelMatches(%#v, %#v)\n", label, matches)
for _, tst := range matches { for _, tst := range matches {
if label == tst { if label == tst {
return true return true
@ -68,6 +69,7 @@ func labelMatches(label string, matches []string) bool {
return false return false
} }
// GetNameservers returns the nameservers for a domain.
func (c *CloudflareApi) GetNameservers(domain string) ([]*models.Nameserver, error) { func (c *CloudflareApi) GetNameservers(domain string) ([]*models.Nameserver, error) {
if c.domainIndex == nil { if c.domainIndex == nil {
if err := c.fetchDomainList(); err != nil { if err := c.fetchDomainList(); err != nil {
@ -81,6 +83,7 @@ func (c *CloudflareApi) GetNameservers(domain string) ([]*models.Nameserver, err
return models.StringsToNameservers(ns), nil return models.StringsToNameservers(ns), nil
} }
// GetDomainCorrections returns a list of corrections to update a domain.
func (c *CloudflareApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { func (c *CloudflareApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
if c.domainIndex == nil { if c.domainIndex == nil {
if err := c.fetchDomainList(); err != nil { if err := c.fetchDomainList(); err != nil {
@ -275,7 +278,7 @@ func (c *CloudflareApi) preprocessConfig(dc *models.DomainConfig) error {
if rec.Type != "A" { if rec.Type != "A" {
continue continue
} }
//only transform "full" // only transform "full"
if rec.Metadata[metaProxy] != "full" { if rec.Metadata[metaProxy] != "full" {
continue continue
} }
@ -365,7 +368,7 @@ type cfRecord struct {
} }
func (c *cfRecord) toRecord(domain string) *models.RecordConfig { func (c *cfRecord) toRecord(domain string) *models.RecordConfig {
//normalize cname,mx,ns records with dots to be consistent with our config format. // normalize cname,mx,ns records with dots to be consistent with our config format.
if c.Type == "CNAME" || c.Type == "MX" || c.Type == "NS" || c.Type == "SRV" { if c.Type == "CNAME" || c.Type == "MX" || c.Type == "NS" || c.Type == "SRV" {
c.Content = dnsutil.AddOrigin(c.Content+".", domain) c.Content = dnsutil.AddOrigin(c.Content+".", domain)
} }
@ -417,6 +420,7 @@ func getProxyMetadata(r *models.RecordConfig) map[string]string {
} }
} }
// EnsureDomainExists returns an error of domain does not exist.
func (c *CloudflareApi) EnsureDomainExists(domain string) error { func (c *CloudflareApi) EnsureDomainExists(domain string) error {
if _, ok := c.domainIndex[domain]; ok { if _, ok := c.domainIndex[domain]; ok {
return nil return nil

View File

@ -81,12 +81,12 @@ func TestIpRewriting(t *testing.T) {
Given, Expected string Given, Expected string
Proxy string Proxy string
}{ }{
//outside of range // outside of range
{"5.5.5.5", "5.5.5.5", "full"}, {"5.5.5.5", "5.5.5.5", "full"},
{"5.5.5.5", "5.5.5.5", "on"}, {"5.5.5.5", "5.5.5.5", "on"},
// inside range, but not proxied // inside range, but not proxied
{"1.2.3.4", "1.2.3.4", "on"}, {"1.2.3.4", "1.2.3.4", "on"},
//inside range and proxied // inside range and proxied
{"1.2.3.4", "255.255.255.4", "full"}, {"1.2.3.4", "255.255.255.4", "full"},
} }
cf := &CloudflareApi{} cf := &CloudflareApi{}

View File

@ -67,7 +67,7 @@ func (c *CloudflareApi) getRecordsForDomain(id string, domain string) ([]*models
return nil, fmt.Errorf("Error fetching record list cloudflare: %s", stringifyErrors(data.Errors)) return nil, fmt.Errorf("Error fetching record list cloudflare: %s", stringifyErrors(data.Errors))
} }
for _, rec := range data.Result { for _, rec := range data.Result {
//fmt.Printf("REC: %+v\n", rec) // fmt.Printf("REC: %+v\n", rec)
records = append(records, rec.toRecord(domain)) records = append(records, rec.toRecord(domain))
} }
ri := data.ResultInfo ri := data.ResultInfo
@ -76,7 +76,7 @@ func (c *CloudflareApi) getRecordsForDomain(id string, domain string) ([]*models
} }
page++ page++
} }
//fmt.Printf("DEBUG REORDS=%v\n", records) // fmt.Printf("DEBUG REORDS=%v\n", records)
return records, nil return records, nil
} }
@ -202,7 +202,7 @@ func (c *CloudflareApi) createRec(rec *models.RecordConfig, domainID string) []*
func (c *CloudflareApi) modifyRecord(domainID, recID string, proxied bool, rec *models.RecordConfig) error { func (c *CloudflareApi) modifyRecord(domainID, recID string, proxied bool, rec *models.RecordConfig) error {
if domainID == "" || recID == "" { if domainID == "" || recID == "" {
return fmt.Errorf("Cannot modify record if domain or record id are empty.") return fmt.Errorf("cannot modify record if domain or record id are empty")
} }
type record struct { type record struct {
ID string `json:"id"` ID string `json:"id"`
@ -273,7 +273,7 @@ func (c *CloudflareApi) get(endpoint string, target interface{}) error {
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
return fmt.Errorf("Bad status code from cloudflare: %d not 200.", resp.StatusCode) return fmt.Errorf("bad status code from cloudflare: %d not 200", resp.StatusCode)
} }
decoder := json.NewDecoder(resp.Body) decoder := json.NewDecoder(resp.Body)
return decoder.Decode(target) return decoder.Decode(target)
@ -306,7 +306,7 @@ func (c *CloudflareApi) getPageRules(id string, domain string) ([]*models.Record
Name: "@", Name: "@",
NameFQDN: domain, NameFQDN: domain,
Type: "PAGE_RULE", Type: "PAGE_RULE",
//$FROM,$TO,$PRIO,$CODE // $FROM,$TO,$PRIO,$CODE
Target: fmt.Sprintf("%s,%s,%d,%d", pr.Targets[0].Constraint.Value, pr.ForwardingInfo.URL, pr.Priority, pr.ForwardingInfo.StatusCode), Target: fmt.Sprintf("%s,%s,%d,%d", pr.Targets[0].Constraint.Value, pr.ForwardingInfo.URL, pr.Priority, pr.ForwardingInfo.StatusCode),
Original: thisPr, Original: thisPr,
TTL: 1, TTL: 1,
@ -339,7 +339,7 @@ func (c *CloudflareApi) createPageRule(domainID string, target string) error {
} }
func (c *CloudflareApi) sendPageRule(endpoint, method string, data string) error { func (c *CloudflareApi) sendPageRule(endpoint, method string, data string) error {
//from to priority code // from to priority code
parts := strings.Split(data, ",") parts := strings.Split(data, ",")
priority, _ := strconv.Atoi(parts[2]) priority, _ := strconv.Atoi(parts[2])
code, _ := strconv.Atoi(parts[3]) code, _ := strconv.Atoi(parts[3])

View File

@ -19,7 +19,7 @@ func LoadProviderConfigs(fname string) (map[string]map[string]string, error) {
var results = map[string]map[string]string{} var results = map[string]map[string]string{}
dat, err := utfutil.ReadFile(fname, utfutil.POSIX) dat, err := utfutil.ReadFile(fname, utfutil.POSIX)
if err != nil { if err != nil {
//no creds file is ok. Bind requires nothing for example. Individual providers will error if things not found. // no creds file is ok. Bind requires nothing for example. Individual providers will error if things not found.
if os.IsNotExist(err) { if os.IsNotExist(err) {
return results, nil return results, nil
} }

View File

@ -8,21 +8,26 @@ import (
"github.com/StackExchange/dnscontrol/models" "github.com/StackExchange/dnscontrol/models"
) )
// Correlation stores a difference between two domains.
type Correlation struct { type Correlation struct {
d *differ d *differ
Existing *models.RecordConfig Existing *models.RecordConfig
Desired *models.RecordConfig Desired *models.RecordConfig
} }
// Changeset stores many Correlation.
type Changeset []Correlation type Changeset []Correlation
// Differ is an interface for computing the difference between two zones.
type Differ interface { type Differ interface {
//IncrementalDiff performs a diff on a record-by-record basis, and returns a sets for which records need to be created, deleted, or modified. // IncrementalDiff performs a diff on a record-by-record basis, and returns a sets for which records need to be created, deleted, or modified.
IncrementalDiff(existing []*models.RecordConfig) (unchanged, create, toDelete, modify Changeset) IncrementalDiff(existing []*models.RecordConfig) (unchanged, create, toDelete, modify Changeset)
// ChangedGroups performs a diff more appropriate for providers with a "RecordSet" model, where all records with the same name and type are grouped. // ChangedGroups performs a diff more appropriate for providers with a "RecordSet" model, where all records with the same name and type are grouped.
// Individual record changes are often not useful in such scenarios. Instead we return a map of record keys to a list of change descriptions within that group. // Individual record changes are often not useful in such scenarios. Instead we return a map of record keys to a list of change descriptions within that group.
ChangedGroups(existing []*models.RecordConfig) map[models.RecordKey][]string ChangedGroups(existing []*models.RecordConfig) map[models.RecordKey][]string
} }
// New is a constructor for a Differ.
func New(dc *models.DomainConfig, extraValues ...func(*models.RecordConfig) map[string]string) Differ { func New(dc *models.DomainConfig, extraValues ...func(*models.RecordConfig) map[string]string) Differ {
return &differ{ return &differ{
dc: dc, dc: dc,
@ -53,7 +58,7 @@ func (d *differ) IncrementalDiff(existing []*models.RecordConfig) (unchanged, cr
modify = Changeset{} modify = Changeset{}
desired := d.dc.Records desired := d.dc.Records
//sort existing and desired by name // sort existing and desired by name
type key struct { type key struct {
name, rType string name, rType string
} }
@ -67,7 +72,7 @@ func (d *differ) IncrementalDiff(existing []*models.RecordConfig) (unchanged, cr
k := key{d.NameFQDN, d.Type} k := key{d.NameFQDN, d.Type}
desiredByNameAndType[k] = append(desiredByNameAndType[k], d) desiredByNameAndType[k] = append(desiredByNameAndType[k], d)
} }
//if NO_PURGE is set, just remove anything that is only in existing. // if NO_PURGE is set, just remove anything that is only in existing.
if d.dc.KeepUnknown { if d.dc.KeepUnknown {
for k := range existingByNameAndType { for k := range existingByNameAndType {
if _, ok := desiredByNameAndType[k]; !ok { if _, ok := desiredByNameAndType[k]; !ok {
@ -80,12 +85,12 @@ func (d *differ) IncrementalDiff(existing []*models.RecordConfig) (unchanged, cr
// Each iteration is only for a single type/name record set // Each iteration is only for a single type/name record set
for key, existingRecords := range existingByNameAndType { for key, existingRecords := range existingByNameAndType {
desiredRecords := desiredByNameAndType[key] desiredRecords := desiredByNameAndType[key]
//first look through records that are the same target on both sides. Those are either modifications or unchanged // first look through records that are the same target on both sides. Those are either modifications or unchanged
for i := len(existingRecords) - 1; i >= 0; i-- { for i := len(existingRecords) - 1; i >= 0; i-- {
ex := existingRecords[i] ex := existingRecords[i]
for j, de := range desiredRecords { for j, de := range desiredRecords {
if de.Target == ex.Target { if de.Target == ex.Target {
//they're either identical or should be a modification of each other (ttl or metadata changes) // they're either identical or should be a modification of each other (ttl or metadata changes)
if d.content(de) == d.content(ex) { if d.content(de) == d.content(ex) {
unchanged = append(unchanged, Correlation{d, ex, de}) unchanged = append(unchanged, Correlation{d, ex, de})
} else { } else {
@ -124,7 +129,7 @@ func (d *differ) IncrementalDiff(existing []*models.RecordConfig) (unchanged, cr
delete(desiredLookup, norm) delete(desiredLookup, norm)
} }
} }
//sort records by normalized text. Keeps behaviour deterministic // sort records by normalized text. Keeps behaviour deterministic
existingStrings, desiredStrings := sortedKeys(existingLookup), sortedKeys(desiredLookup) existingStrings, desiredStrings := sortedKeys(existingLookup), sortedKeys(desiredLookup)
// Modifications. Take 1 from each side. // Modifications. Take 1 from each side.
for len(desiredStrings) > 0 && len(existingStrings) > 0 { for len(desiredStrings) > 0 && len(existingStrings) > 0 {
@ -146,7 +151,7 @@ func (d *differ) IncrementalDiff(existing []*models.RecordConfig) (unchanged, cr
delete(desiredByNameAndType, key) delete(desiredByNameAndType, key)
} }
//any name/type sets not already processed are pure additions // any name/type sets not already processed are pure additions
for name := range existingByNameAndType { for name := range existingByNameAndType {
delete(desiredByNameAndType, name) delete(desiredByNameAndType, name)
} }

View File

@ -24,6 +24,7 @@ Info required in `creds.json`:
*/ */
// DoApi is the handle for operations.
type DoApi struct { type DoApi struct {
client *godo.Client client *godo.Client
} }
@ -34,9 +35,10 @@ var defaultNameServerNames = []string{
"ns3.digitalocean.com", "ns3.digitalocean.com",
} }
// NewDo creates a DO-specific DNS provider.
func NewDo(m map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) { func NewDo(m map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
if m["token"] == "" { if m["token"] == "" {
return nil, fmt.Errorf("Digitalocean Token must be provided.") return nil, fmt.Errorf("no Digitalocean token provided")
} }
ctx := context.Background() ctx := context.Background()
@ -54,7 +56,7 @@ func NewDo(m map[string]string, metadata json.RawMessage) (providers.DNSServiceP
return nil, err return nil, err
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Digitalocean Token is not valid.") return nil, fmt.Errorf("token for digitalocean is not valid")
} }
return api, nil return api, nil
@ -70,6 +72,7 @@ func init() {
providers.RegisterDomainServiceProviderType("DIGITALOCEAN", NewDo, features) providers.RegisterDomainServiceProviderType("DIGITALOCEAN", NewDo, features)
} }
// EnsureDomainExists returns an error if domain doesn't exist.
func (api *DoApi) EnsureDomainExists(domain string) error { func (api *DoApi) EnsureDomainExists(domain string) error {
ctx := context.Background() ctx := context.Background()
_, resp, err := api.client.Domains.Get(ctx, domain) _, resp, err := api.client.Domains.Get(ctx, domain)
@ -79,15 +82,16 @@ func (api *DoApi) EnsureDomainExists(domain string) error {
IPAddress: "", IPAddress: "",
}) })
return err return err
} else {
return err
} }
return err
} }
// GetNameservers returns the nameservers for domain.
func (api *DoApi) GetNameservers(domain string) ([]*models.Nameserver, error) { func (api *DoApi) GetNameservers(domain string) ([]*models.Nameserver, error) {
return models.StringsToNameservers(defaultNameServerNames), nil return models.StringsToNameservers(defaultNameServerNames), nil
} }
// GetDomainCorrections returns a list of corretions for the domain.
func (api *DoApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { func (api *DoApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
ctx := context.Background() ctx := context.Background()
dc.Punycode() dc.Punycode()

View File

@ -40,16 +40,19 @@ var defaultNameServerNames = []string{
"ns4.dnsimple.com", "ns4.dnsimple.com",
} }
// DnsimpleApi is the handle for this provider.
type DnsimpleApi struct { type DnsimpleApi struct {
AccountToken string // The account access token AccountToken string // The account access token
BaseURL string // An alternate base URI BaseURL string // An alternate base URI
accountId string // Account id cache accountID string // Account id cache
} }
// GetNameservers returns the name servers for a domain.
func (c *DnsimpleApi) GetNameservers(domainName string) ([]*models.Nameserver, error) { func (c *DnsimpleApi) GetNameservers(domainName string) ([]*models.Nameserver, error) {
return models.StringsToNameservers(defaultNameServerNames), nil return models.StringsToNameservers(defaultNameServerNames), nil
} }
// GetDomainCorrections returns corrections that update a domain.
func (c *DnsimpleApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { func (c *DnsimpleApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
corrections := []*models.Correction{} corrections := []*models.Correction{}
dc.Punycode() dc.Punycode()
@ -129,6 +132,7 @@ func (c *DnsimpleApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C
return corrections, nil return corrections, nil
} }
// GetRegistrarCorrections returns corrections that update a domain's registrar.
func (c *DnsimpleApi) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { func (c *DnsimpleApi) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
corrections := []*models.Correction{} corrections := []*models.Correction{}
@ -168,8 +172,8 @@ func (c *DnsimpleApi) getClient() *dnsimpleapi.Client {
return client return client
} }
func (c *DnsimpleApi) getAccountId() (string, error) { func (c *DnsimpleApi) getAccountID() (string, error) {
if c.accountId == "" { if c.accountID == "" {
client := c.getClient() client := c.getClient()
whoamiResponse, err := client.Identity.Whoami() whoamiResponse, err := client.Identity.Whoami()
if err != nil { if err != nil {
@ -178,15 +182,15 @@ func (c *DnsimpleApi) getAccountId() (string, error) {
if whoamiResponse.Data.User != nil && whoamiResponse.Data.Account == nil { if whoamiResponse.Data.User != nil && whoamiResponse.Data.Account == nil {
return "", fmt.Errorf("DNSimple token appears to be a user token. Please supply an account token") return "", fmt.Errorf("DNSimple token appears to be a user token. Please supply an account token")
} }
c.accountId = strconv.Itoa(whoamiResponse.Data.Account.ID) c.accountID = strconv.Itoa(whoamiResponse.Data.Account.ID)
} }
return c.accountId, nil return c.accountID, nil
} }
func (c *DnsimpleApi) getRecords(domainName string) ([]dnsimpleapi.ZoneRecord, error) { func (c *DnsimpleApi) getRecords(domainName string) ([]dnsimpleapi.ZoneRecord, error) {
client := c.getClient() client := c.getClient()
accountId, err := c.getAccountId() accountID, err := c.getAccountID()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -195,7 +199,7 @@ func (c *DnsimpleApi) getRecords(domainName string) ([]dnsimpleapi.ZoneRecord, e
recs := []dnsimpleapi.ZoneRecord{} recs := []dnsimpleapi.ZoneRecord{}
opts.Page = 1 opts.Page = 1
for { for {
recordsResponse, err := client.Zones.ListRecords(accountId, domainName, opts) recordsResponse, err := client.Zones.ListRecords(accountID, domainName, opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -216,27 +220,26 @@ func (c *DnsimpleApi) getRecords(domainName string) ([]dnsimpleapi.ZoneRecord, e
func (c *DnsimpleApi) getNameservers(domainName string) ([]string, error) { func (c *DnsimpleApi) getNameservers(domainName string) ([]string, error) {
client := c.getClient() client := c.getClient()
accountId, err := c.getAccountId() accountID, err := c.getAccountID()
if err != nil { if err != nil {
return nil, err return nil, err
} }
domainResponse, err := client.Domains.GetDomain(accountId, domainName) domainResponse, err := client.Domains.GetDomain(accountID, domainName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if domainResponse.Data.State == stateRegistered { if domainResponse.Data.State == stateRegistered {
delegationResponse, err := client.Registrar.GetDomainDelegation(accountId, domainName) delegationResponse, err := client.Registrar.GetDomainDelegation(accountID, domainName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return *delegationResponse.Data, nil return *delegationResponse.Data, nil
} else {
return defaultNameServerNames, nil
} }
return defaultNameServerNames, nil
} }
// Returns a function that can be invoked to change the delegation of the domain to the given name server names. // Returns a function that can be invoked to change the delegation of the domain to the given name server names.
@ -244,14 +247,14 @@ func (c *DnsimpleApi) updateNameserversFunc(nameServerNames []string, domainName
return func() error { return func() error {
client := c.getClient() client := c.getClient()
accountId, err := c.getAccountId() accountID, err := c.getAccountID()
if err != nil { if err != nil {
return err return err
} }
nameServers := dnsimpleapi.Delegation(nameServerNames) nameServers := dnsimpleapi.Delegation(nameServerNames)
_, err = client.Registrar.ChangeDomainDelegation(accountId, domainName, &nameServers) _, err = client.Registrar.ChangeDomainDelegation(accountID, domainName, &nameServers)
if err != nil { if err != nil {
return err return err
} }
@ -265,7 +268,7 @@ func (c *DnsimpleApi) createRecordFunc(rc *models.RecordConfig, domainName strin
return func() error { return func() error {
client := c.getClient() client := c.getClient()
accountId, err := c.getAccountId() accountID, err := c.getAccountID()
if err != nil { if err != nil {
return err return err
} }
@ -276,7 +279,7 @@ func (c *DnsimpleApi) createRecordFunc(rc *models.RecordConfig, domainName strin
TTL: int(rc.TTL), TTL: int(rc.TTL),
Priority: int(rc.MxPreference), Priority: int(rc.MxPreference),
} }
_, err = client.Zones.CreateRecord(accountId, domainName, record) _, err = client.Zones.CreateRecord(accountID, domainName, record)
if err != nil { if err != nil {
return err return err
} }
@ -286,16 +289,16 @@ func (c *DnsimpleApi) createRecordFunc(rc *models.RecordConfig, domainName strin
} }
// Returns a function that can be invoked to delete a record in a zone. // Returns a function that can be invoked to delete a record in a zone.
func (c *DnsimpleApi) deleteRecordFunc(recordId int, domainName string) func() error { func (c *DnsimpleApi) deleteRecordFunc(recordID int, domainName string) func() error {
return func() error { return func() error {
client := c.getClient() client := c.getClient()
accountId, err := c.getAccountId() accountID, err := c.getAccountID()
if err != nil { if err != nil {
return err return err
} }
_, err = client.Zones.DeleteRecord(accountId, domainName, recordId) _, err = client.Zones.DeleteRecord(accountID, domainName, recordID)
if err != nil { if err != nil {
return err return err
} }
@ -310,7 +313,7 @@ func (c *DnsimpleApi) updateRecordFunc(old *dnsimpleapi.ZoneRecord, rc *models.R
return func() error { return func() error {
client := c.getClient() client := c.getClient()
accountId, err := c.getAccountId() accountID, err := c.getAccountID()
if err != nil { if err != nil {
return err return err
} }
@ -323,7 +326,7 @@ func (c *DnsimpleApi) updateRecordFunc(old *dnsimpleapi.ZoneRecord, rc *models.R
Priority: int(rc.MxPreference), Priority: int(rc.MxPreference),
} }
_, err = client.Zones.UpdateRecord(accountId, domainName, old.ID, record) _, err = client.Zones.UpdateRecord(accountID, domainName, old.ID, record)
if err != nil { if err != nil {
return err return err
} }
@ -346,7 +349,7 @@ func newProvider(m map[string]string, metadata json.RawMessage) (*DnsimpleApi, e
api := &DnsimpleApi{} api := &DnsimpleApi{}
api.AccountToken = m["token"] api.AccountToken = m["token"]
if api.AccountToken == "" { if api.AccountToken == "" {
return nil, fmt.Errorf("DNSimple token must be provided.") return nil, fmt.Errorf("missing DNSimple token")
} }
if m["baseurl"] != "" { if m["baseurl"] != "" {

View File

@ -40,6 +40,7 @@ func init() {
providers.RegisterRegistrarType("GANDI", newReg) providers.RegisterRegistrarType("GANDI", newReg)
} }
// GandiApi is the API handle for this module.
type GandiApi struct { type GandiApi struct {
ApiKey string ApiKey string
domainIndex map[string]int64 // Map of domainname to index domainIndex map[string]int64 // Map of domainname to index
@ -62,6 +63,7 @@ func (c *GandiApi) getDomainInfo(domain string) (*gandidomain.DomainInfo, error)
return c.fetchDomainInfo(domain) return c.fetchDomainInfo(domain)
} }
// GetNameservers returns the nameservers for domain.
func (c *GandiApi) GetNameservers(domain string) ([]*models.Nameserver, error) { func (c *GandiApi) GetNameservers(domain string) ([]*models.Nameserver, error) {
domaininfo, err := c.getDomainInfo(domain) domaininfo, err := c.getDomainInfo(domain)
if err != nil { if err != nil {
@ -74,6 +76,7 @@ func (c *GandiApi) GetNameservers(domain string) ([]*models.Nameserver, error) {
return ns, nil return ns, nil
} }
// GetDomainCorrections returns a list of corrections recommended for this domain.
func (c *GandiApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { func (c *GandiApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
dc.Punycode() dc.Punycode()
dc.CombineSRVs() dc.CombineSRVs()
@ -168,12 +171,13 @@ func newGandi(m map[string]string, metadata json.RawMessage) (*GandiApi, error)
api := &GandiApi{} api := &GandiApi{}
api.ApiKey = m["apikey"] api.ApiKey = m["apikey"]
if api.ApiKey == "" { if api.ApiKey == "" {
return nil, fmt.Errorf("Gandi apikey must be provided.") return nil, fmt.Errorf("missing Gandi apikey")
} }
return api, nil return api, nil
} }
// GetRegistrarCorrections returns a list of corrections for this registrar.
func (c *GandiApi) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { func (c *GandiApi) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
domaininfo, err := c.getDomainInfo(dc.Name) domaininfo, err := c.getDomainInfo(dc.Name)
if err != nil { if err != nil {

View File

@ -71,10 +71,10 @@ func (c *GandiApi) listZones() ([]*gandizone.ZoneInfoBase, error) {
} }
// setZone assigns a particular zone to a domain. // setZone assigns a particular zone to a domain.
func (c *GandiApi) setZones(domainname string, zone_id int64) (*gandidomain.DomainInfo, error) { func (c *GandiApi) setZones(domainname string, zoneID int64) (*gandidomain.DomainInfo, error) {
gc := gandiclient.New(c.ApiKey, gandiclient.Production) gc := gandiclient.New(c.ApiKey, gandiclient.Production)
zone := gandizone.New(gc) zone := gandizone.New(gc)
return zone.Set(domainname, zone_id) return zone.Set(domainname, zoneID)
} }
// getZoneInfo gets ZoneInfo about a zone. // getZoneInfo gets ZoneInfo about a zone.
@ -92,12 +92,12 @@ func (c *GandiApi) createZone(name string) (*gandizone.ZoneInfo, error) {
} }
func (c *GandiApi) getEditableZone(domainname string, zoneinfo *gandizone.ZoneInfo) (int64, error) { func (c *GandiApi) getEditableZone(domainname string, zoneinfo *gandizone.ZoneInfo) (int64, error) {
var zone_id int64 var zoneID int64
if zoneinfo.Domains < 2 { if zoneinfo.Domains < 2 {
// If there is only on{ domain linked to this zone, use it. // If there is only on{ domain linked to this zone, use it.
zone_id = zoneinfo.Id zoneID = zoneinfo.Id
fmt.Printf("Using zone id=%d named %#v\n", zone_id, zoneinfo.Name) fmt.Printf("Using zone id=%d named %#v\n", zoneID, zoneinfo.Name)
return zone_id, nil return zoneID, nil
} }
// We can't use the zone_id given to us. Let's make/find a new one. // We can't use the zone_id given to us. Let's make/find a new one.
@ -108,39 +108,39 @@ func (c *GandiApi) getEditableZone(domainname string, zoneinfo *gandizone.ZoneIn
zonename := fmt.Sprintf("%s dnscontrol", domainname) zonename := fmt.Sprintf("%s dnscontrol", domainname)
for _, z := range zones { for _, z := range zones {
if z.Name == zonename { if z.Name == zonename {
zone_id = z.Id zoneID = z.Id
fmt.Printf("Recycling zone id=%d named %#v\n", zone_id, z.Name) fmt.Printf("Recycling zone id=%d named %#v\n", zoneID, z.Name)
return zone_id, nil return zoneID, nil
} }
} }
zoneinfo, err = c.createZone(zonename) zoneinfo, err = c.createZone(zonename)
if err != nil { if err != nil {
return 0, err return 0, err
} }
zone_id = zoneinfo.Id zoneID = zoneinfo.Id
fmt.Printf("Created zone id=%d named %#v\n", zone_id, zoneinfo.Name) fmt.Printf("Created zone id=%d named %#v\n", zoneID, zoneinfo.Name)
return zone_id, nil return zoneID, nil
} }
// makeEditableZone // makeEditableZone
func (c *GandiApi) makeEditableZone(zone_id int64) (int64, error) { func (c *GandiApi) makeEditableZone(zoneID int64) (int64, error) {
gc := gandiclient.New(c.ApiKey, gandiclient.Production) gc := gandiclient.New(c.ApiKey, gandiclient.Production)
version := gandiversion.New(gc) version := gandiversion.New(gc)
return version.New(zone_id, 0) return version.New(zoneID, 0)
} }
// setZoneRecords // setZoneRecords
func (c *GandiApi) setZoneRecords(zone_id, version_id int64, records []gandirecord.RecordSet) ([]*gandirecord.RecordInfo, error) { func (c *GandiApi) setZoneRecords(zoneID, versionID int64, records []gandirecord.RecordSet) ([]*gandirecord.RecordInfo, error) {
gc := gandiclient.New(c.ApiKey, gandiclient.Production) gc := gandiclient.New(c.ApiKey, gandiclient.Production)
record := gandirecord.New(gc) record := gandirecord.New(gc)
return record.SetRecords(zone_id, version_id, records) return record.SetRecords(zoneID, versionID, records)
} }
// activateVersion // activateVersion
func (c *GandiApi) activateVersion(zone_id, version_id int64) (bool, error) { func (c *GandiApi) activateVersion(zoneID, versionID int64) (bool, error) {
gc := gandiclient.New(c.ApiKey, gandiclient.Production) gc := gandiclient.New(c.ApiKey, gandiclient.Production)
version := gandiversion.New(gc) version := gandiversion.New(gc)
return version.Set(zone_id, version_id) return version.Set(zoneID, versionID)
} }
func (c *GandiApi) createGandiZone(domainname string, zoneID int64, records []gandirecord.RecordSet) error { func (c *GandiApi) createGandiZone(domainname string, zoneID int64, records []gandirecord.RecordSet) error {
@ -150,7 +150,7 @@ func (c *GandiApi) createGandiZone(domainname string, zoneID int64, records []ga
if err != nil { if err != nil {
return err return err
} }
//fmt.Println("ZONEINFO:", zoneinfo) // fmt.Println("ZONEINFO:", zoneinfo)
zoneID, err = c.getEditableZone(domainname, zoneinfo) zoneID, err = c.getEditableZone(domainname, zoneinfo)
if err != nil { if err != nil {
return err return err

View File

@ -114,7 +114,7 @@ func (g *gcloud) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correc
if err != nil { if err != nil {
return nil, err return nil, err
} }
//convert to dnscontrol RecordConfig format // convert to dnscontrol RecordConfig format
existingRecords := []*models.RecordConfig{} existingRecords := []*models.RecordConfig{}
oldRRs := map[key]*dns.ResourceRecordSet{} oldRRs := map[key]*dns.ResourceRecordSet{}
for _, set := range rrs { for _, set := range rrs {
@ -168,7 +168,7 @@ func (g *gcloud) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correc
if old, ok := oldRRs[ck]; ok { if old, ok := oldRRs[ck]; ok {
chg.Deletions = append(chg.Deletions, old) chg.Deletions = append(chg.Deletions, old)
} }
//collect records to replace with // collect records to replace with
newRRs := &dns.ResourceRecordSet{ newRRs := &dns.ResourceRecordSet{
Name: ck.Name, Name: ck.Name,
Type: ck.Type, Type: ck.Type,
@ -240,7 +240,7 @@ func (g *gcloud) EnsureDomainExists(domain string) error {
Name: strings.Replace(domain, ".", "-", -1), Name: strings.Replace(domain, ".", "-", -1),
Description: "zone added by dnscontrol", Description: "zone added by dnscontrol",
} }
g.zones = nil //reset cache g.zones = nil // reset cache
_, err = g.client.ManagedZones.Create(g.project, mz).Do() _, err = g.client.ManagedZones.Create(g.project, mz).Do()
return err return err
} }

View File

@ -173,10 +173,10 @@ func (c *LinodeApi) handleErrors(resp *http.Response) error {
errs := &errorResponse{} errs := &errorResponse{}
if err := decoder.Decode(errs); err != nil { if err := decoder.Decode(errs); err != nil {
return fmt.Errorf("Bad status code from Linode: %d not 200. Failed to decode response.", resp.StatusCode) return fmt.Errorf("bad status code from Linode: %d not 200. Failed to decode response", resp.StatusCode)
} }
buf := bytes.NewBufferString(fmt.Sprintf("Bad status code from Linode: %d not 200.", resp.StatusCode)) buf := bytes.NewBufferString(fmt.Sprintf("bad status code from Linode: %d not 200", resp.StatusCode))
for _, err := range errs.Errors { for _, err := range errs.Errors {
buf.WriteString("\n- ") buf.WriteString("\n- ")

View File

@ -45,6 +45,7 @@ var allowedTTLValues = []uint32{
var srvRegexp = regexp.MustCompile(`^_(?P<Service>\w+)\.\_(?P<Protocol>\w+)$`) var srvRegexp = regexp.MustCompile(`^_(?P<Service>\w+)\.\_(?P<Protocol>\w+)$`)
// LinodeApi is the handle for this provider.
type LinodeApi struct { type LinodeApi struct {
client *http.Client client *http.Client
baseURL *url.URL baseURL *url.URL
@ -59,9 +60,10 @@ var defaultNameServerNames = []string{
"ns5.linode.com", "ns5.linode.com",
} }
// NewLinode creates the provider.
func NewLinode(m map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) { func NewLinode(m map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
if m["token"] == "" { if m["token"] == "" {
return nil, fmt.Errorf("Linode Token must be provided.") return nil, fmt.Errorf("Missing Linode token")
} }
ctx := context.Background() ctx := context.Background()
@ -95,10 +97,12 @@ func init() {
providers.RegisterDomainServiceProviderType("LINODE", NewLinode, features) providers.RegisterDomainServiceProviderType("LINODE", NewLinode, features)
} }
// GetNameservers returns the nameservers for a domain.
func (api *LinodeApi) GetNameservers(domain string) ([]*models.Nameserver, error) { func (api *LinodeApi) GetNameservers(domain string) ([]*models.Nameserver, error) {
return models.StringsToNameservers(defaultNameServerNames), nil return models.StringsToNameservers(defaultNameServerNames), nil
} }
// GetDomainCorrections returns the corrections for a domain.
func (api *LinodeApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { func (api *LinodeApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
dc, err := dc.Copy() dc, err := dc.Copy()
if err != nil { if err != nil {
@ -293,9 +297,8 @@ func fixTarget(target, domain string) string {
// Linode always wants a fully qualified target name // Linode always wants a fully qualified target name
if target[len(target)-1] == '.' { if target[len(target)-1] == '.' {
return target[:len(target)-1] return target[:len(target)-1]
} else {
return fmt.Sprintf("%s.%s", target, domain)
} }
return fmt.Sprintf("%s.%s", target, domain)
} }
func fixTTL(ttl uint32) uint32 { func fixTTL(ttl uint32) uint32 {

View File

@ -17,8 +17,10 @@ import (
"github.com/miekg/dns/dnsutil" "github.com/miekg/dns/dnsutil"
) )
// NamecheapDefaultNs lists the default nameservers for this provider.
var NamecheapDefaultNs = []string{"dns1.registrar-servers.com", "dns2.registrar-servers.com"} var NamecheapDefaultNs = []string{"dns1.registrar-servers.com", "dns2.registrar-servers.com"}
// Namecheap is the handle for this provider.
type Namecheap struct { type Namecheap struct {
ApiKey string ApiKey string
ApiUser string ApiUser string
@ -57,7 +59,7 @@ func newProvider(m map[string]string, metadata json.RawMessage) (*Namecheap, err
api := &Namecheap{} api := &Namecheap{}
api.ApiUser, api.ApiKey = m["apiuser"], m["apikey"] api.ApiUser, api.ApiKey = m["apiuser"], m["apikey"]
if api.ApiKey == "" || api.ApiUser == "" { if api.ApiKey == "" || api.ApiUser == "" {
return nil, fmt.Errorf("Namecheap apikey and apiuser must be provided.") return nil, fmt.Errorf("missing Namecheap apikey and apiuser")
} }
api.client = nc.NewClient(api.ApiUser, api.ApiKey, api.ApiUser) api.client = nc.NewClient(api.ApiUser, api.ApiKey, api.ApiUser)
// if BaseURL is specified in creds, use that url // if BaseURL is specified in creds, use that url
@ -104,6 +106,7 @@ func doWithRetry(f func() error) {
} }
} }
// GetDomainCorrections returns the corrections for the domain.
func (n *Namecheap) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { func (n *Namecheap) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
dc.Punycode() dc.Punycode()
sld, tld := splitDomain(dc.Name) sld, tld := splitDomain(dc.Name)
@ -221,6 +224,7 @@ func (n *Namecheap) generateRecords(dc *models.DomainConfig) error {
return err return err
} }
// GetNameservers returns the nameservers for a domain.
func (n *Namecheap) GetNameservers(domainName string) ([]*models.Nameserver, error) { func (n *Namecheap) GetNameservers(domainName string) ([]*models.Nameserver, error) {
// return default namecheap nameservers // return default namecheap nameservers
ns := NamecheapDefaultNs ns := NamecheapDefaultNs
@ -228,6 +232,7 @@ func (n *Namecheap) GetNameservers(domainName string) ([]*models.Nameserver, err
return models.StringsToNameservers(ns), nil return models.StringsToNameservers(ns), nil
} }
// GetRegistrarCorrections returns corrections to update nameservers.
func (n *Namecheap) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { func (n *Namecheap) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
var info *nc.DomainInfo var info *nc.DomainInfo
var err error var err error

View File

@ -1,4 +1,4 @@
//Package namedotcom implements a registrar that uses the name.com api to set name servers. It will self register it's providers when imported. // Package namedotcom implements a registrar that uses the name.com api to set name servers. It will self register it's providers when imported.
package namedotcom package namedotcom
import ( import (
@ -11,7 +11,7 @@ import (
"github.com/StackExchange/dnscontrol/providers" "github.com/StackExchange/dnscontrol/providers"
) )
const defaultApiBase = "https://api.name.com/api" const defaultAPIBase = "https://api.name.com/api"
type nameDotCom struct { type nameDotCom struct {
APIUrl string `json:"apiurl"` APIUrl string `json:"apiurl"`
@ -40,10 +40,10 @@ func newProvider(conf map[string]string) (*nameDotCom, error) {
api := &nameDotCom{} api := &nameDotCom{}
api.APIUser, api.APIKey, api.APIUrl = conf["apiuser"], conf["apikey"], conf["apiurl"] api.APIUser, api.APIKey, api.APIUrl = conf["apiuser"], conf["apikey"], conf["apiurl"]
if api.APIKey == "" || api.APIUser == "" { if api.APIKey == "" || api.APIUser == "" {
return nil, fmt.Errorf("Name.com apikey and apiuser must be provided.") return nil, fmt.Errorf("missing Name.com apikey or apiuser")
} }
if api.APIUrl == "" { if api.APIUrl == "" {
api.APIUrl = defaultApiBase api.APIUrl = defaultAPIBase
} }
return api, nil return api, nil
} }
@ -53,9 +53,7 @@ func init() {
providers.RegisterDomainServiceProviderType("NAMEDOTCOM", newDsp, features) providers.RegisterDomainServiceProviderType("NAMEDOTCOM", newDsp, features)
} }
/// // various http helpers for interacting with api
//various http helpers for interacting with api
///
func (n *nameDotCom) addAuth(r *http.Request) { func (n *nameDotCom) addAuth(r *http.Request) {
r.Header.Add("Api-Username", n.APIUser) r.Header.Add("Api-Username", n.APIUser)
@ -82,7 +80,7 @@ func (r *apiResult) getErr() error {
return nil return nil
} }
//perform http GET and unmarshal response json into target struct // perform http GET and unmarshal response json into target struct
func (n *nameDotCom) get(url string, target interface{}) error { func (n *nameDotCom) get(url string, target interface{}) error {
req, err := http.NewRequest("GET", url, nil) req, err := http.NewRequest("GET", url, nil)
if err != nil { if err != nil {

View File

@ -12,9 +12,9 @@ import (
var nsRegex = regexp.MustCompile(`ns([1-4])[a-z]{3}\.name\.com`) var nsRegex = regexp.MustCompile(`ns([1-4])[a-z]{3}\.name\.com`)
func (n *nameDotCom) GetNameservers(domain string) ([]*models.Nameserver, error) { func (n *nameDotCom) GetNameservers(domain string) ([]*models.Nameserver, error) {
//This is an interesting edge case. Name.com expects you to SET the nameservers to ns[1-4].name.com, // This is an interesting edge case. Name.com expects you to SET the nameservers to ns[1-4].name.com,
//but it will internally set it to ns1xyz.name.com, where xyz is a uniqueish 3 letters. // but it will internally set it to ns1xyz.name.com, where xyz is a uniqueish 3 letters.
//In order to avoid endless loops, we will use the unique nameservers if present, or else the generic ones if not. // In order to avoid endless loops, we will use the unique nameservers if present, or else the generic ones if not.
nss, err := n.getNameserversRaw(domain) nss, err := n.getNameserversRaw(domain)
if err != nil { if err != nil {
return nil, err return nil, err
@ -22,7 +22,7 @@ func (n *nameDotCom) GetNameservers(domain string) ([]*models.Nameserver, error)
toUse := []string{"ns1.name.com", "ns2.name.com", "ns3.name.com", "ns4.name.com"} toUse := []string{"ns1.name.com", "ns2.name.com", "ns3.name.com", "ns4.name.com"}
for _, ns := range nss { for _, ns := range nss {
if matches := nsRegex.FindStringSubmatch(ns); len(matches) == 2 && len(matches[1]) == 1 { if matches := nsRegex.FindStringSubmatch(ns); len(matches) == 2 && len(matches[1]) == 1 {
idx := matches[1][0] - '1' //regex ensures proper range idx := matches[1][0] - '1' // regex ensures proper range
toUse[idx] = matches[0] toUse[idx] = matches[0]
} }
} }
@ -66,7 +66,7 @@ func (n *nameDotCom) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models
return nil, nil return nil, nil
} }
//even if you provide them "ns1.name.com", they will set it to "ns1qrt.name.com". This will match that pattern to see if defaults are in use. // even if you provide them "ns1.name.com", they will set it to "ns1qrt.name.com". This will match that pattern to see if defaults are in use.
var defaultNsRegexp = regexp.MustCompile(`ns1[a-z]{0,3}\.name\.com,ns2[a-z]{0,3}\.name\.com,ns3[a-z]{0,3}\.name\.com,ns4[a-z]{0,3}\.name\.com`) var defaultNsRegexp = regexp.MustCompile(`ns1[a-z]{0,3}\.name\.com,ns2[a-z]{0,3}\.name\.com,ns3[a-z]{0,3}\.name\.com,ns4[a-z]{0,3}\.name\.com`)
func (n *nameDotCom) apiGetDomain(domain string) string { func (n *nameDotCom) apiGetDomain(domain string) string {
@ -91,9 +91,6 @@ func (n *nameDotCom) updateNameservers(ns []string, domain string) func() error
if err != nil { if err != nil {
return err return err
} }
if err = resp.getErr(); err != nil { return resp.getErr()
return err
}
return nil
} }
} }

View File

@ -83,7 +83,7 @@ func TestGetCorrections(t *testing.T) {
{`"bar.ns.tld","foo.ns.tld"`, 0}, {`"bar.ns.tld","foo.ns.tld"`, 0},
{`"foo.ns.tld"`, 1}, {`"foo.ns.tld"`, 1},
{`"1.ns.aaa","2.ns.www"`, 1}, {`"1.ns.aaa","2.ns.www"`, 1},
{"ERR", -1}, //-1 means we expect an error {"ERR", -1}, // -1 means we expect an error
{"MSGERR", -1}, {"MSGERR", -1},
} { } {
setup() setup()
@ -155,12 +155,12 @@ func TestGetNameservers(t *testing.T) {
for i, test := range []struct { for i, test := range []struct {
givenNs, expected string givenNs, expected string
}{ }{
//empty or external dsp, use ns1-4.name.com // empty or external dsp, use ns1-4.name.com
{"", d}, {"", d},
{`"foo.ns.tld","bar.ns.tld"`, d}, {`"foo.ns.tld","bar.ns.tld"`, d},
//if already on name.com, use the existing nameservers // if already on name.com, use the existing nameservers
{`"ns1aaa.name.com","ns2bbb.name.com","ns3ccc.name.com","ns4ddd.name.com"`, "ns1aaa.name.com,ns2bbb.name.com,ns3ccc.name.com,ns4ddd.name.com"}, {`"ns1aaa.name.com","ns2bbb.name.com","ns3ccc.name.com","ns4ddd.name.com"`, "ns1aaa.name.com,ns2bbb.name.com,ns3ccc.name.com,ns4ddd.name.com"},
//also handle half and half // also handle half and half
{`"ns1aaa.name.com","ns2bbb.name.com","ns3ccc.aws.net","ns4ddd.awsdns.org"`, "ns1aaa.name.com,ns2bbb.name.com,ns3.name.com,ns4.name.com"}, {`"ns1aaa.name.com","ns2bbb.name.com","ns3ccc.aws.net","ns4ddd.awsdns.org"`, "ns1aaa.name.com,ns2bbb.name.com,ns3.name.com,ns4.name.com"},
{`"nsa.azuredns.com","ns2b.gandhi.net","ns3ccc.name.com","ns4ddd.name.com"`, "ns1.name.com,ns2.name.com,ns3ccc.name.com,ns4ddd.name.com"}, {`"nsa.azuredns.com","ns2b.gandhi.net","ns3ccc.name.com","ns4ddd.name.com"`, "ns1.name.com,ns2.name.com,ns3ccc.name.com,ns4ddd.name.com"},
} { } {

View File

@ -160,7 +160,7 @@ func (n *nameDotCom) createRecord(rc *models.RecordConfig, domain string) error
if target[len(target)-1] == '.' { if target[len(target)-1] == '.' {
target = target[:len(target)-1] target = target[:len(target)-1]
} else { } else {
return fmt.Errorf("Unexpected. CNAME/MX/NS target did not end with dot.\n") return fmt.Errorf("unexpected: CNAME/MX/NS target did not end with dot")
} }
} }
dat := struct { dat := struct {

View File

@ -137,7 +137,7 @@ func (c *ovhProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C
rec := del.Existing.Original.(*Record) rec := del.Existing.Original.(*Record)
corrections = append(corrections, &models.Correction{ corrections = append(corrections, &models.Correction{
Msg: del.String(), Msg: del.String(),
F: c.deleteRecordFunc(rec.Id, dc.Name), F: c.deleteRecordFunc(rec.ID, dc.Name),
}) })
} }

View File

@ -2,10 +2,12 @@ package ovh
import ( import (
"fmt" "fmt"
"github.com/StackExchange/dnscontrol/models" "github.com/StackExchange/dnscontrol/models"
"github.com/miekg/dns/dnsutil" "github.com/miekg/dns/dnsutil"
) )
// Void an empty structure.
type Void struct { type Void struct {
} }
@ -30,6 +32,7 @@ func (c *ovhProvider) fetchZones() error {
return nil return nil
} }
// Zone describes the attributes of a DNS zone.
type Zone struct { type Zone struct {
LastUpdate string `json:"lastUpdate,omitempty"` LastUpdate string `json:"lastUpdate,omitempty"`
HasDNSAnycast bool `json:"hasDNSAnycast,omitempty"` HasDNSAnycast bool `json:"hasDNSAnycast,omitempty"`
@ -49,17 +52,18 @@ func (c *ovhProvider) fetchZone(fqdn string) (*Zone, error) {
return &response, nil return &response, nil
} }
// Record describes a DNS record.
type Record struct { type Record struct {
Target string `json:"target,omitempty"` Target string `json:"target,omitempty"`
Zone string `json:"zone,omitempty"` Zone string `json:"zone,omitempty"`
TTL uint32 `json:"ttl,omitempty"` TTL uint32 `json:"ttl,omitempty"`
FieldType string `json:"fieldType,omitempty"` FieldType string `json:"fieldType,omitempty"`
Id int64 `json:"id,omitempty"` ID int64 `json:"id,omitempty"`
SubDomain string `json:"subDomain,omitempty"` SubDomain string `json:"subDomain,omitempty"`
} }
type records struct { type records struct {
recordsId []int recordsID []int
} }
func (c *ovhProvider) fetchRecords(fqdn string) ([]*Record, error) { func (c *ovhProvider) fetchRecords(fqdn string) ([]*Record, error) {
@ -130,13 +134,13 @@ func (c *ovhProvider) updateRecordFunc(old *Record, rc *models.RecordConfig, fqd
Target: rc.Content(), Target: rc.Content(),
TTL: rc.TTL, TTL: rc.TTL,
Zone: fqdn, Zone: fqdn,
Id: old.Id, ID: old.ID,
} }
if record.SubDomain == "@" { if record.SubDomain == "@" {
record.SubDomain = "" record.SubDomain = ""
} }
return c.client.Call("PUT", fmt.Sprintf("/domain/zone/%s/record/%d", fqdn, old.Id), &record, &Void{}) return c.client.Call("PUT", fmt.Sprintf("/domain/zone/%s/record/%d", fqdn, old.ID), &record, &Void{})
} }
} }
@ -155,24 +159,25 @@ func (c *ovhProvider) fetchNS(fqdn string) ([]string, error) {
return zone.NameServers, nil return zone.NameServers, nil
} }
// CurrentNameServer stores information about nameservers.
type CurrentNameServer struct { type CurrentNameServer struct {
ToDelete bool `json:"toDelete,omitempty"` ToDelete bool `json:"toDelete,omitempty"`
Ip string `json:"ip,omitempty"` IP string `json:"ip,omitempty"`
IsUsed bool `json:"isUsed,omitempty"` IsUsed bool `json:"isUsed,omitempty"`
Id int `json:"id,omitempty"` ID int `json:"id,omitempty"`
Host string `json:"host,omitempty"` Host string `json:"host,omitempty"`
} }
// Retrieve the NS currently being deployed to the registrar // Retrieve the NS currently being deployed to the registrar
func (c *ovhProvider) fetchRegistrarNS(fqdn string) ([]string, error) { func (c *ovhProvider) fetchRegistrarNS(fqdn string) ([]string, error) {
var nameServersId []int var nameServersID []int
err := c.client.Call("GET", "/domain/"+fqdn+"/nameServer", nil, &nameServersId) err := c.client.Call("GET", "/domain/"+fqdn+"/nameServer", nil, &nameServersID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var nameServers []string var nameServers []string
for _, id := range nameServersId { for _, id := range nameServersID {
var ns CurrentNameServer var ns CurrentNameServer
err = c.client.Call("GET", fmt.Sprintf("/domain/%s/nameServer/%d", fqdn, id), nil, &ns) err = c.client.Call("GET", fmt.Sprintf("/domain/%s/nameServer/%d", fqdn, id), nil, &ns)
if err != nil { if err != nil {
@ -189,15 +194,18 @@ func (c *ovhProvider) fetchRegistrarNS(fqdn string) ([]string, error) {
return nameServers, nil return nameServers, nil
} }
// DomainNS describes a domain's NS in ovh's protocol.
type DomainNS struct { type DomainNS struct {
Host string `json:"host,omitempty"` Host string `json:"host,omitempty"`
Ip string `json:"ip,omitempty"` IP string `json:"ip,omitempty"`
} }
// UpdateNS describes a list of nameservers in ovh's protocol.
type UpdateNS struct { type UpdateNS struct {
NameServers []DomainNS `json:"nameServers"` NameServers []DomainNS `json:"nameServers"`
} }
// Task describes a task in ovh's protocol.
type Task struct { type Task struct {
Function string `json:"function,omitempty"` Function string `json:"function,omitempty"`
Status string `json:"status,omitempty"` Status string `json:"status,omitempty"`
@ -206,12 +214,13 @@ type Task struct {
CreationDate string `json:"creationDate,omitempty"` CreationDate string `json:"creationDate,omitempty"`
Comment string `json:"comment,omitempty"` Comment string `json:"comment,omitempty"`
TodoDate string `json:"todoDate,omitempty"` TodoDate string `json:"todoDate,omitempty"`
Id int64 `json:"id,omitempty"` ID int64 `json:"id,omitempty"`
CanCancel bool `json:"canCancel,omitempty"` CanCancel bool `json:"canCancel,omitempty"`
DoneDate string `json:"doneDate,omitempty"` DoneDate string `json:"doneDate,omitempty"`
CanRelaunch bool `json:"canRelaunch,omitempty"` CanRelaunch bool `json:"canRelaunch,omitempty"`
} }
// Domain describes a domain in ovh's protocol.
type Domain struct { type Domain struct {
NameServerType string `json:"nameServerType,omitempty"` NameServerType string `json:"nameServerType,omitempty"`
TransferLockStatus string `json:"transferLockStatus,omitempty"` TransferLockStatus string `json:"transferLockStatus,omitempty"`

View File

@ -8,34 +8,36 @@ import (
"github.com/StackExchange/dnscontrol/models" "github.com/StackExchange/dnscontrol/models"
) )
//Registrar is an interface for a domain registrar. It can return a list of needed corrections to be applied in the future. // Registrar is an interface for a domain registrar. It can return a list of needed corrections to be applied in the future.
type Registrar interface { type Registrar interface {
GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error)
} }
//DNSServiceProvider is able to generate a set of corrections that need to be made to correct records for a domain // DNSServiceProvider is able to generate a set of corrections that need to be made to correct records for a domain
type DNSServiceProvider interface { type DNSServiceProvider interface {
GetNameservers(domain string) ([]*models.Nameserver, error) GetNameservers(domain string) ([]*models.Nameserver, error)
GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error)
} }
//DomainCreator should be implemented by providers that have the ability to add domains to an account. the create-domains command // DomainCreator should be implemented by providers that have the ability to add domains to an account. the create-domains command
//can be run to ensure all domains are present before running preview/push // can be run to ensure all domains are present before running preview/push
type DomainCreator interface { type DomainCreator interface {
EnsureDomainExists(domain string) error EnsureDomainExists(domain string) error
} }
//RegistrarInitializer is a function to create a registrar. Function will be passed the unprocessed json payload from the configuration file for the given provider. // RegistrarInitializer is a function to create a registrar. Function will be passed the unprocessed json payload from the configuration file for the given provider.
type RegistrarInitializer func(map[string]string) (Registrar, error) type RegistrarInitializer func(map[string]string) (Registrar, error)
// RegistrarTypes stores initializer for each registrar.
var RegistrarTypes = map[string]RegistrarInitializer{} var RegistrarTypes = map[string]RegistrarInitializer{}
//DspInitializer is a function to create a DNS service provider. Function will be passed the unprocessed json payload from the configuration file for the given provider. // DspInitializer is a function to create a DNS service provider. Function will be passed the unprocessed json payload from the configuration file for the given provider.
type DspInitializer func(map[string]string, json.RawMessage) (DNSServiceProvider, error) type DspInitializer func(map[string]string, json.RawMessage) (DNSServiceProvider, error)
// DNSProviderTypes stores initializer for each DSP.
var DNSProviderTypes = map[string]DspInitializer{} var DNSProviderTypes = map[string]DspInitializer{}
//RegisterRegistrarType adds a registrar type to the registry by providing a suitable initialization function. // RegisterRegistrarType adds a registrar type to the registry by providing a suitable initialization function.
func RegisterRegistrarType(name string, init RegistrarInitializer, pm ...ProviderMetadata) { func RegisterRegistrarType(name string, init RegistrarInitializer, pm ...ProviderMetadata) {
if _, ok := RegistrarTypes[name]; ok { if _, ok := RegistrarTypes[name]; ok {
log.Fatalf("Cannot register registrar type %s multiple times", name) log.Fatalf("Cannot register registrar type %s multiple times", name)
@ -44,7 +46,7 @@ func RegisterRegistrarType(name string, init RegistrarInitializer, pm ...Provide
unwrapProviderCapabilities(name, pm) unwrapProviderCapabilities(name, pm)
} }
//RegisterDomainServiceProviderType adds a dsp to the registry with the given initialization function. // RegisterDomainServiceProviderType adds a dsp to the registry with the given initialization function.
func RegisterDomainServiceProviderType(name string, init DspInitializer, pm ...ProviderMetadata) { func RegisterDomainServiceProviderType(name string, init DspInitializer, pm ...ProviderMetadata) {
if _, ok := DNSProviderTypes[name]; ok { if _, ok := DNSProviderTypes[name]; ok {
log.Fatalf("Cannot register registrar type %s multiple times", name) log.Fatalf("Cannot register registrar type %s multiple times", name)
@ -56,11 +58,12 @@ func RegisterDomainServiceProviderType(name string, init DspInitializer, pm ...P
func createRegistrar(rType string, config map[string]string) (Registrar, error) { func createRegistrar(rType string, config map[string]string) (Registrar, error) {
initer, ok := RegistrarTypes[rType] initer, ok := RegistrarTypes[rType]
if !ok { if !ok {
return nil, fmt.Errorf("Registrar type %s not declared.", rType) return nil, fmt.Errorf("registrar type %s not declared", rType)
} }
return initer(config) return initer(config)
} }
// CreateDNSProvider returnsa DSP's initializer.
func CreateDNSProvider(dType string, config map[string]string, meta json.RawMessage) (DNSServiceProvider, error) { func CreateDNSProvider(dType string, config map[string]string, meta json.RawMessage) (DNSServiceProvider, error) {
initer, ok := DNSProviderTypes[dType] initer, ok := DNSProviderTypes[dType]
if !ok { if !ok {
@ -69,8 +72,8 @@ func CreateDNSProvider(dType string, config map[string]string, meta json.RawMess
return initer(config, meta) return initer(config, meta)
} }
//CreateRegistrars will load all registrars from the dns config, and create instances of the correct type using data from // CreateRegistrars will load all registrars from the dns config, and create instances of the correct type using data from
//the provider config to load relevant keys and options. // the provider config to load relevant keys and options.
func CreateRegistrars(d *models.DNSConfig, providerConfigs map[string]map[string]string) (map[string]Registrar, error) { func CreateRegistrars(d *models.DNSConfig, providerConfigs map[string]map[string]string) (map[string]Registrar, error) {
regs := map[string]Registrar{} regs := map[string]Registrar{}
for _, reg := range d.Registrars { for _, reg := range d.Registrars {
@ -87,6 +90,7 @@ func CreateRegistrars(d *models.DNSConfig, providerConfigs map[string]map[string
return regs, nil return regs, nil
} }
// CreateDsps creates a DSP.
func CreateDsps(d *models.DNSConfig, providerConfigs map[string]map[string]string) (map[string]DNSServiceProvider, error) { func CreateDsps(d *models.DNSConfig, providerConfigs map[string]map[string]string) (map[string]DNSServiceProvider, error) {
dsps := map[string]DNSServiceProvider{} dsps := map[string]DNSServiceProvider{}
for _, dsp := range d.DNSProviders { for _, dsp := range d.DNSProviders {
@ -103,14 +107,17 @@ func CreateDsps(d *models.DNSConfig, providerConfigs map[string]map[string]strin
// None is a basic provider type that does absolutely nothing. Can be useful as a placeholder for third parties or unimplemented providers. // None is a basic provider type that does absolutely nothing. Can be useful as a placeholder for third parties or unimplemented providers.
type None struct{} type None struct{}
// GetRegistrarCorrections returns corrections to update registrars.
func (n None) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { func (n None) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
return nil, nil return nil, nil
} }
// GetNameservers returns the current nameservers for a domain.
func (n None) GetNameservers(string) ([]*models.Nameserver, error) { func (n None) GetNameservers(string) ([]*models.Nameserver, error) {
return nil, nil return nil, nil
} }
// GetDomainCorrections returns corrections to update a domain.
func (n None) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { func (n None) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
return nil, nil return nil, nil
} }
@ -121,6 +128,7 @@ func init() {
}) })
} }
// CustomRType stores an rtype that is only valid for this DSP.
type CustomRType struct { type CustomRType struct {
Name string Name string
Provider string Provider string

View File

@ -33,7 +33,7 @@ func newRoute53Dsp(conf map[string]string, metadata json.RawMessage) (providers.
} }
func newRoute53(m map[string]string, metadata json.RawMessage) (*route53Provider, error) { func newRoute53(m map[string]string, metadata json.RawMessage) (*route53Provider, error) {
keyId, secretKey := m["KeyId"], m["SecretKey"] keyID, secretKey := m["KeyId"], m["SecretKey"]
// Route53 uses a global endpoint and route53domains // Route53 uses a global endpoint and route53domains
// currently only has a single regional endpoint in us-east-1 // currently only has a single regional endpoint in us-east-1
@ -42,8 +42,8 @@ func newRoute53(m map[string]string, metadata json.RawMessage) (*route53Provider
Region: aws.String("us-east-1"), Region: aws.String("us-east-1"),
} }
if keyId != "" || secretKey != "" { if keyID != "" || secretKey != "" {
config.Credentials = credentials.NewStaticCredentials(keyId, secretKey, "") config.Credentials = credentials.NewStaticCredentials(keyID, secretKey, "")
} }
sess := session.New(config) sess := session.New(config)
@ -102,7 +102,7 @@ func (r *route53Provider) getZones() error {
return nil return nil
} }
//map key for grouping records // map key for grouping records
type key struct { type key struct {
Name, Type string Name, Type string
} }
@ -176,7 +176,7 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
// Normalize // Normalize
models.PostProcessRecords(existingRecords) models.PostProcessRecords(existingRecords)
//diff // diff
differ := diff.New(dc) differ := diff.New(dc)
_, create, delete, modify := differ.IncrementalDiff(existingRecords) _, create, delete, modify := differ.IncrementalDiff(existingRecords)
@ -196,7 +196,7 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
} }
updates := map[key][]*models.RecordConfig{} updates := map[key][]*models.RecordConfig{}
//for each name we need to update, collect relevant records from dc // for each name we need to update, collect relevant records from dc
for k := range namesToUpdate { for k := range namesToUpdate {
updates[k] = nil updates[k] = nil
for _, rc := range dc.Records { for _, rc := range dc.Records {
@ -227,7 +227,7 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
} else { } else {
changes = append(changes, chg) changes = append(changes, chg)
changeDesc += strings.Join(namesToUpdate[k], "\n") + "\n" changeDesc += strings.Join(namesToUpdate[k], "\n") + "\n"
//on change or create, just build a new record set from our desired state // on change or create, just build a new record set from our desired state
chg.Action = sPtr("UPSERT") chg.Action = sPtr("UPSERT")
rrset = &r53.ResourceRecordSet{ rrset = &r53.ResourceRecordSet{
Name: sPtr(k.Name), Name: sPtr(k.Name),
@ -241,7 +241,7 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
} }
rrset.ResourceRecords = append(rrset.ResourceRecords, rr) rrset.ResourceRecords = append(rrset.ResourceRecords, rr)
i := int64(r.TTL) i := int64(r.TTL)
rrset.TTL = &i //TODO: make sure that ttls are consistent within a set rrset.TTL = &i // TODO: make sure that ttls are consistent within a set
} }
} }
chg.ResourceRecordSet = rrset chg.ResourceRecordSet = rrset
@ -367,13 +367,13 @@ func (r *route53Provider) fetchRecordSets(zoneID *string) ([]*r53.ResourceRecord
return records, nil return records, nil
} }
//we have to process names from route53 to match what we expect and to remove their odd octal encoding // we have to process names from route53 to match what we expect and to remove their odd octal encoding
func unescape(s *string) string { func unescape(s *string) string {
if s == nil { if s == nil {
return "" return ""
} }
name := strings.TrimSuffix(*s, ".") name := strings.TrimSuffix(*s, ".")
name = strings.Replace(name, `\052`, "*", -1) //TODO: escape all octal sequences name = strings.Replace(name, `\052`, "*", -1) // TODO: escape all octal sequences
return name return name
} }

View File

@ -17,6 +17,7 @@ import (
"github.com/softlayer/softlayer-go/session" "github.com/softlayer/softlayer-go/session"
) )
// SoftLayer is the protocol handle for this provider.
type SoftLayer struct { type SoftLayer struct {
Session *session.Session Session *session.Session
} }
@ -36,7 +37,7 @@ func newReg(conf map[string]string, _ json.RawMessage) (providers.DNSServiceProv
return nil, fmt.Errorf("SoftLayer UserName and APIKey must be provided") return nil, fmt.Errorf("SoftLayer UserName and APIKey must be provided")
} }
//s.Debug = true // s.Debug = true
api := &SoftLayer{ api := &SoftLayer{
Session: s, Session: s,
@ -45,12 +46,14 @@ func newReg(conf map[string]string, _ json.RawMessage) (providers.DNSServiceProv
return api, nil return api, nil
} }
// GetNameservers returns the nameservers for a domain.
func (s *SoftLayer) GetNameservers(domain string) ([]*models.Nameserver, error) { func (s *SoftLayer) GetNameservers(domain string) ([]*models.Nameserver, error) {
// Always use the same nameservers for softlayer // Always use the same nameservers for softlayer
nservers := []string{"ns1.softlayer.com", "ns2.softlayer.com"} nservers := []string{"ns1.softlayer.com", "ns2.softlayer.com"}
return models.StringsToNameservers(nservers), nil return models.StringsToNameservers(nservers), nil
} }
// GetDomainCorrections returns corrections to update a domain.
func (s *SoftLayer) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { func (s *SoftLayer) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
corrections := []*models.Correction{} corrections := []*models.Correction{}
@ -174,16 +177,16 @@ func (s *SoftLayer) getExistingRecords(domain *datatypes.Dns_Domain) ([]*models.
} }
func (s *SoftLayer) createRecordFunc(desired *models.RecordConfig, domain *datatypes.Dns_Domain) func() error { func (s *SoftLayer) createRecordFunc(desired *models.RecordConfig, domain *datatypes.Dns_Domain) func() error {
var ttl, preference, domainId int = int(desired.TTL), int(desired.MxPreference), *domain.Id var ttl, preference, domainID int = int(desired.TTL), int(desired.MxPreference), *domain.Id
var weight, priority, port int = int(desired.SrvWeight), int(desired.SrvPriority), int(desired.SrvPort) var weight, priority, port int = int(desired.SrvWeight), int(desired.SrvPriority), int(desired.SrvPort)
var host, data, newType string = desired.Name, desired.Target, desired.Type var host, data, newType string = desired.Name, desired.Target, desired.Type
var err error = nil var err error
srvRegexp := regexp.MustCompile(`^_(?P<Service>\w+)\.\_(?P<Protocol>\w+)$`) srvRegexp := regexp.MustCompile(`^_(?P<Service>\w+)\.\_(?P<Protocol>\w+)$`)
return func() error { return func() error {
newRecord := datatypes.Dns_Domain_ResourceRecord{ newRecord := datatypes.Dns_Domain_ResourceRecord{
DomainId: &domainId, DomainId: &domainID,
Ttl: &ttl, Ttl: &ttl,
Type: &newType, Type: &newType,
Data: &data, Data: &data,
@ -232,11 +235,11 @@ func (s *SoftLayer) createRecordFunc(desired *models.RecordConfig, domain *datat
} }
} }
func (s *SoftLayer) deleteRecordFunc(resId int) func() error { func (s *SoftLayer) deleteRecordFunc(resID int) func() error {
// seems to be no problem deleting MX and SRV records via common interface // seems to be no problem deleting MX and SRV records via common interface
return func() error { return func() error {
_, err := services.GetDnsDomainResourceRecordService(s.Session). _, err := services.GetDnsDomainResourceRecordService(s.Session).
Id(resId). Id(resID).
DeleteObject() DeleteObject()
return err return err
@ -248,8 +251,8 @@ func (s *SoftLayer) updateRecordFunc(existing *datatypes.Dns_Domain_ResourceReco
var priority, weight, port int = int(desired.SrvPriority), int(desired.SrvWeight), int(desired.SrvPort) var priority, weight, port int = int(desired.SrvPriority), int(desired.SrvWeight), int(desired.SrvPort)
return func() error { return func() error {
var changes bool = false var changes = false
var err error = nil var err error
switch desired.Type { switch desired.Type {
case "MX": case "MX":
@ -277,7 +280,7 @@ func (s *SoftLayer) updateRecordFunc(existing *datatypes.Dns_Domain_ResourceReco
} }
if !changes { if !changes {
return fmt.Errorf("Error: Didn't find changes when I expect some.") return fmt.Errorf("didn't find changes when I expect some")
} }
_, err = service.Id(*existing.Id).EditObject(&updated) _, err = service.Id(*existing.Id).EditObject(&updated)
@ -320,7 +323,7 @@ func (s *SoftLayer) updateRecordFunc(existing *datatypes.Dns_Domain_ResourceReco
// delete and recreate? // delete and recreate?
if !changes { if !changes {
return fmt.Errorf("Error: Didn't find changes when I expect some.") return fmt.Errorf("didn't find changes when I expect some")
} }
_, err = service.Id(*existing.Id).EditObject(&updated) _, err = service.Id(*existing.Id).EditObject(&updated)
@ -345,7 +348,7 @@ func (s *SoftLayer) updateRecordFunc(existing *datatypes.Dns_Domain_ResourceReco
} }
if !changes { if !changes {
return fmt.Errorf("Error: Didn't find changes when I expect some.") return fmt.Errorf("didn't find changes when I expect some")
} }
_, err = service.Id(*existing.Id).EditObject(&updated) _, err = service.Id(*existing.Id).EditObject(&updated)