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

DNSimple: Implement GetZones and ListZones (#637)

* Update to latest dnsimple-go

* Implement GetZoneRecords

* Better naming

* Return NS records in GetZoneRecords

* Be clearer with the comment.

As an employee I confirm this is exactly how this works. No guessing needed.

* Respect that Puncycode encoding can blow up

* Implement ListZones and the ZoneLister Interface

* Categorize DNSIMPLE

* Update docs with go generate

* vendor modules

* Don't store intermediary Zone data
This commit is contained in:
Amelia Aronsohn
2020-02-20 11:52:19 -08:00
committed by GitHub
parent ca99517ced
commit b45c6b6b6c
12 changed files with 115 additions and 45 deletions

View File

@ -922,8 +922,8 @@
<td class="success"> <td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
</td> </td>
<td class="info"> <td class="success">
<i class="fa fa-circle-o text-info" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
</td> </td>
<td class="info"> <td class="info">
<i class="fa fa-circle-o text-info" aria-hidden="true"></i> <i class="fa fa-circle-o text-info" aria-hidden="true"></i>

View File

@ -92,14 +92,14 @@ Pick a similar provider as your base. Providers basically fall
into three general categories: into three general categories:
* **zone:** The API requires you to upload the entire zone every time. (BIND). * **zone:** The API requires you to upload the entire zone every time. (BIND).
* **incremental-record:** The API lets you add/change/delete individual DNS records. (ACTIVEDIR, CLOUDFLARE, NAMEDOTCOM, GCLOUD, ROUTE53) * **incremental-record:** The API lets you add/change/delete individual DNS records. (ACTIVEDIR, CLOUDFLARE, DNSIMPLE, NAMEDOTCOM, GCLOUD, ROUTE53)
* **incremental-label:** Like incremental-record, but if there are * **incremental-label:** Like incremental-record, but if there are
multiple records on a label (for example, example www.example.com multiple records on a label (for example, example www.example.com
has A and MX records), you have to replace all the records at that has A and MX records), you have to replace all the records at that
label. (GANDI_V5) label. (GANDI_V5)
* **incremental-label-type:** Like incremental-record, but updates to any records at a label have to be done by type. For example, if a label (www.example.com) has many A and MX records, even the smallest change to one of the A records requires replacing all the A records. Any changes to the MX records requires replacing all the MX records. If an A record is converted to a CNAME, one must remove all the A records in one call, and add the CNAME record with another call. This is deceptively difficult to get right; if you have the voice between incremental-label-type and incremental-label, pick incremental-label. * **incremental-label-type:** Like incremental-record, but updates to any records at a label have to be done by type. For example, if a label (www.example.com) has many A and MX records, even the smallest change to one of the A records requires replacing all the A records. Any changes to the MX records requires replacing all the MX records. If an A record is converted to a CNAME, one must remove all the A records in one call, and add the CNAME record with another call. This is deceptively difficult to get right; if you have the voice between incremental-label-type and incremental-label, pick incremental-label.
TODO: Categorize DNSIMPLE, NAMECHEAP TODO: Categorize NAMECHEAP
All providers use the "diff" module to detect differences. It takes All providers use the "diff" module to detect differences. It takes
two zones and returns records that are unchanged, created, deleted, two zones and returns records that are unchanged, created, deleted,

2
go.mod
View File

@ -16,7 +16,7 @@ require (
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/dgrijalva/jwt-go v3.2.1-0.20190620180102-5e25c22bd5d6+incompatible // indirect github.com/dgrijalva/jwt-go v3.2.1-0.20190620180102-5e25c22bd5d6+incompatible // indirect
github.com/digitalocean/godo v1.30.0 github.com/digitalocean/godo v1.30.0
github.com/dnsimple/dnsimple-go v0.20.1-0.20181001130357-234ec949d37c github.com/dnsimple/dnsimple-go v0.31.0
github.com/exoscale/egoscale v0.10.5 github.com/exoscale/egoscale v0.10.5
github.com/go-acme/lego v2.7.2+incompatible github.com/go-acme/lego v2.7.2+incompatible
github.com/gobwas/glob v0.2.4-0.20181002190808-e7a84e9525fe github.com/gobwas/glob v0.2.4-0.20181002190808-e7a84e9525fe

5
go.sum
View File

@ -35,7 +35,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55 h1:jbGlDKdzAZ92NzK65hUP98ri0/r50vVVvmZsFP/nIqo= github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55 h1:jbGlDKdzAZ92NzK65hUP98ri0/r50vVVvmZsFP/nIqo=
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55/go.mod h1:GCzqZQHydohgVLSIqRKZeTt8IGb1Y4NaFfim3H40uUI= github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55/go.mod h1:GCzqZQHydohgVLSIqRKZeTt8IGb1Y4NaFfim3H40uUI=
github.com/StackExchange/dnscontrol v0.2.8 h1:7jviqDH9cIqRSRpH0UxgmpT7a8CwEhs9mLHBhoYhXo8=
github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d h1:WtAMR0fPCOfK7TPGZ8ZpLLY18HRvL7XJ3xcs0wnREgo= github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d h1:WtAMR0fPCOfK7TPGZ8ZpLLY18HRvL7XJ3xcs0wnREgo=
github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d/go.mod h1:WML6KOYjeU8N6YyusMjj2qRvaPNUEvrQvaxuFcMRFJY= github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d/go.mod h1:WML6KOYjeU8N6YyusMjj2qRvaPNUEvrQvaxuFcMRFJY=
github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE=
@ -66,8 +65,8 @@ github.com/digitalocean/godo v1.30.0 h1:4Zb+xBlKMXKg772eyQk6px3sk9RhWj/CR75tQ375
github.com/digitalocean/godo v1.30.0/go.mod h1:iJnN9rVu6K5LioLxLimlq0uRI+y/eAQjROUmeU/r0hY= github.com/digitalocean/godo v1.30.0/go.mod h1:iJnN9rVu6K5LioLxLimlq0uRI+y/eAQjROUmeU/r0hY=
github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4=
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/dnsimple/dnsimple-go v0.20.1-0.20181001130357-234ec949d37c h1:Llo2tjvv1SspjFXiM8NJyxirMPTZvNPk3lLCPf61COo= github.com/dnsimple/dnsimple-go v0.31.0 h1:I1T+AxBQfhovyyfGSJ4CSUeH0iQejLArsUlhSQKw8WI=
github.com/dnsimple/dnsimple-go v0.20.1-0.20181001130357-234ec949d37c/go.mod h1:0FYu4qVNv/UcfZPNwa9zi68IkggJu3TIwM54D7rhmI4= github.com/dnsimple/dnsimple-go v0.31.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/exoscale/egoscale v0.10.5 h1:pV1gDCsXPi9vfbZ1TIMz7mNGEDMiVHlQlbcJp1qxIaU= github.com/exoscale/egoscale v0.10.5 h1:pV1gDCsXPi9vfbZ1TIMz7mNGEDMiVHlQlbcJp1qxIaU=

View File

@ -25,7 +25,7 @@ var features = providers.DocumentationNotes{
providers.DocCreateDomains: providers.Cannot(), providers.DocCreateDomains: providers.Cannot(),
providers.DocDualHost: providers.Cannot("DNSimple does not allow sufficient control over the apex NS records"), providers.DocDualHost: providers.Cannot("DNSimple does not allow sufficient control over the apex NS records"),
providers.DocOfficiallySupported: providers.Cannot(), providers.DocOfficiallySupported: providers.Cannot(),
providers.CanGetZones: providers.Unimplemented(), providers.CanGetZones: providers.Can(),
} }
func init() { func init() {
@ -56,24 +56,14 @@ func (c *DnsimpleApi) GetNameservers(domainName string) ([]*models.Nameserver, e
// GetZoneRecords gets the records of a zone and returns them in RecordConfig format. // GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
func (client *DnsimpleApi) GetZoneRecords(domain string) (models.Records, error) { func (client *DnsimpleApi) GetZoneRecords(domain string) (models.Records, error) {
return nil, fmt.Errorf("not implemented") records, err := client.getRecords(domain)
// This enables the get-zones subcommand.
// Implement this by extracting the code from GetDomainCorrections into
// a single function. For most providers this should be relatively easy.
}
// GetDomainCorrections returns corrections that update a domain.
func (c *DnsimpleApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
corrections := []*models.Correction{}
dc.Punycode()
records, err := c.getRecords(dc.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var actual []*models.RecordConfig var cleanedRecords models.Records
for _, r := range records { for _, r := range records {
if r.Type == "SOA" || r.Type == "NS" { if r.Type == "SOA" {
continue continue
} }
if r.Name == "" { if r.Name == "" {
@ -82,8 +72,8 @@ func (c *DnsimpleApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C
if r.Type == "CNAME" || r.Type == "MX" || r.Type == "ALIAS" { if r.Type == "CNAME" || r.Type == "MX" || r.Type == "ALIAS" {
r.Content += "." r.Content += "."
} }
// dnsimple adds these odd txt records that mirror the alias records. // DNSimple adds TXT records that mirror the alias records.
// they seem to manage them on deletes and things, so we'll just pretend they don't exist // They manage them on ALIAS updates, so pretend they don't exist
if r.Type == "TXT" && strings.HasPrefix(r.Content, "ALIAS for ") { if r.Type == "TXT" && strings.HasPrefix(r.Content, "ALIAS for ") {
continue continue
} }
@ -91,7 +81,7 @@ func (c *DnsimpleApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C
TTL: uint32(r.TTL), TTL: uint32(r.TTL),
Original: r, Original: r,
} }
rec.SetLabel(r.Name, dc.Name) rec.SetLabel(r.Name, domain)
switch rtype := r.Type; rtype { switch rtype := r.Type; rtype {
case "ALIAS", "URL": case "ALIAS", "URL":
rec.Type = r.Type rec.Type = r.Type
@ -109,12 +99,29 @@ func (c *DnsimpleApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C
panic(fmt.Errorf("unparsable record received from dnsimple: %w", err)) panic(fmt.Errorf("unparsable record received from dnsimple: %w", err))
} }
default: default:
if err := rec.PopulateFromString(r.Type, r.Content, dc.Name); err != nil { if err := rec.PopulateFromString(r.Type, r.Content, domain); err != nil {
panic(fmt.Errorf("unparsable record received from dnsimple: %w", err)) panic(fmt.Errorf("unparsable record received from dnsimple: %w", err))
} }
} }
actual = append(actual, rec) cleanedRecords = append(cleanedRecords, rec)
} }
return cleanedRecords, nil
}
// GetDomainCorrections returns corrections that update a domain.
func (c *DnsimpleApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
corrections := []*models.Correction{}
err := dc.Punycode()
if err != nil {
return nil, err
}
records, err := c.GetZoneRecords(dc.Name)
if err != nil {
return nil, err
}
actual := removeNS(records)
removeOtherNS(dc) removeOtherNS(dc)
// Normalize // Normalize
@ -151,6 +158,16 @@ func (c *DnsimpleApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C
return corrections, nil return corrections, nil
} }
func removeNS(records models.Records) models.Records {
var noNameServers models.Records
for _, r := range records {
if r.Type != "NS" {
noNameServers = append(noNameServers, r)
}
}
return noNameServers
}
// GetRegistrarCorrections returns corrections that update a domain's registrar. // 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{}
@ -360,6 +377,34 @@ func (c *DnsimpleApi) updateRecordFunc(old *dnsimpleapi.ZoneRecord, rc *models.R
} }
} }
// Returns all the zones in an account
func (c *DnsimpleApi) ListZones() ([]string, error) {
client := c.getClient()
accountID, err := c.getAccountID()
if err != nil {
return nil, err
}
var zones []string
opts := &dnsimpleapi.ZoneListOptions{}
opts.Page = 1
for {
zonesResponse, err := client.Zones.ListZones(accountID, opts)
if err != nil {
return nil, err
}
for _, zone := range zonesResponse.Data {
zones = append(zones, zone.Name)
}
pg := zonesResponse.Pagination
if pg.CurrentPage == pg.TotalPages {
break
}
opts.Page++
}
return zones, nil
}
// constructors // constructors
func newReg(conf map[string]string) (providers.Registrar, error) { func newReg(conf map[string]string) (providers.Registrar, error) {

View File

@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2014-2018 Aetrion LLC dba DNSimple Copyright (c) 2014-2020 DNSimple Corporation
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -23,7 +23,7 @@ const (
// This is a pro-forma convention given that Go dependencies // This is a pro-forma convention given that Go dependencies
// tends to be fetched directly from the repo. // tends to be fetched directly from the repo.
// It is also used in the user-agent identify the client. // It is also used in the user-agent identify the client.
Version = "0.20.0" Version = "0.31.0"
// defaultBaseURL to the DNSimple production API. // defaultBaseURL to the DNSimple production API.
defaultBaseURL = "https://api.dnsimple.com" defaultBaseURL = "https://api.dnsimple.com"
@ -146,7 +146,7 @@ func formatUserAgent(customUserAgent string) string {
return defaultUserAgent return defaultUserAgent
} }
return fmt.Sprintf("%s %s", defaultUserAgent, customUserAgent) return fmt.Sprintf("%s %s", customUserAgent, defaultUserAgent)
} }
func versioned(path string) string { func versioned(path string) string {

View File

@ -129,18 +129,10 @@ func (s *DomainsService) DeleteDomain(accountID string, domainIdentifier string)
return domainResponse, nil return domainResponse, nil
} }
// ResetDomainToken resets the domain token. // DEPRECATED
// //
// See https://developer.dnsimple.com/v2/domains/#reset-token // See https://developer.dnsimple.com/v2/domains/#reset-token
func (s *DomainsService) ResetDomainToken(accountID string, domainIdentifier string) (*domainResponse, error) { func (s *DomainsService) ResetDomainToken(accountID string, domainIdentifier string) (*domainResponse, error) {
path := versioned(domainPath(accountID, domainIdentifier) + "/token") // noop
domainResponse := &domainResponse{} return &domainResponse{}, nil
resp, err := s.client.post(path, nil, domainResponse)
if err != nil {
return nil, err
}
domainResponse.HttpResponse = resp
return domainResponse, nil
} }

View File

@ -62,7 +62,7 @@ type DomainPremiumPriceOptions struct {
Action string `url:"action,omitempty"` Action string `url:"action,omitempty"`
} }
// Gets the premium price for a domain. // GetDomainPremiumPrice gets the premium price for a domain.
// //
// You must specify an action to get the price for. Valid actions are: // You must specify an action to get the price for. Valid actions are:
// - registration // - registration

View File

@ -14,12 +14,30 @@ type WhoisPrivacy struct {
UpdatedAt string `json:"updated_at,omitempty"` UpdatedAt string `json:"updated_at,omitempty"`
} }
// WhoisPrivacyRenewal represents a whois privacy renewal in DNSimple.
type WhoisPrivacyRenewal struct {
ID int64 `json:"id,omitempty"`
DomainID int64 `json:"domain_id,omitempty"`
WhoisPrivacyID int64 `json:"whois_privacy_id,omitempty"`
State string `json:"string,omitempty"`
Enabled bool `json:"enabled,omitempty"`
ExpiresOn string `json:"expires_on,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
}
// whoisPrivacyResponse represents a response from an API method that returns a WhoisPrivacy struct. // whoisPrivacyResponse represents a response from an API method that returns a WhoisPrivacy struct.
type whoisPrivacyResponse struct { type whoisPrivacyResponse struct {
Response Response
Data *WhoisPrivacy `json:"data"` Data *WhoisPrivacy `json:"data"`
} }
// whoisPrivacyRenewalResponse represents a response from an API method that returns a WhoisPrivacyRenewal struct.
type whoisPrivacyRenewalResponse struct {
Response
Data *WhoisPrivacyRenewal `json:"data"`
}
// GetWhoisPrivacy gets the whois privacy for the domain. // GetWhoisPrivacy gets the whois privacy for the domain.
// //
// See https://developer.dnsimple.com/v2/registrar/whois-privacy/#get // See https://developer.dnsimple.com/v2/registrar/whois-privacy/#get
@ -52,7 +70,7 @@ func (s *RegistrarService) EnableWhoisPrivacy(accountID string, domainName strin
return privacyResponse, nil return privacyResponse, nil
} }
// DisablePrivacy disables the whois privacy for the domain. // DisableWhoisPrivacy disables the whois privacy for the domain.
// //
// See https://developer.dnsimple.com/v2/registrar/whois-privacy/#enable // See https://developer.dnsimple.com/v2/registrar/whois-privacy/#enable
func (s *RegistrarService) DisableWhoisPrivacy(accountID string, domainName string) (*whoisPrivacyResponse, error) { func (s *RegistrarService) DisableWhoisPrivacy(accountID string, domainName string) (*whoisPrivacyResponse, error) {
@ -67,3 +85,19 @@ func (s *RegistrarService) DisableWhoisPrivacy(accountID string, domainName stri
privacyResponse.HttpResponse = resp privacyResponse.HttpResponse = resp
return privacyResponse, nil return privacyResponse, nil
} }
// RenewWhoisPrivacy renews the whois privacy for the domain.
//
// See https://developer.dnsimple.com/v2/registrar/whois-privacy/#renew
func (s *RegistrarService) RenewWhoisPrivacy(accountID string, domainName string) (*whoisPrivacyRenewalResponse, error) {
path := versioned(fmt.Sprintf("/%v/registrar/domains/%v/whois_privacy/renewals", accountID, domainName))
privacyRenewalResponse := &whoisPrivacyRenewalResponse{}
resp, err := s.client.post(path, nil, privacyRenewalResponse)
if err != nil {
return nil, err
}
privacyRenewalResponse.HttpResponse = resp
return privacyRenewalResponse, nil
}

View File

@ -100,7 +100,7 @@ func (s *TldsService) GetTld(tld string) (*tldResponse, error) {
return tldResponse, nil return tldResponse, nil
} }
// GetTld fetches the extended attributes of a TLD. // GetTldExtendedAttributes fetches the extended attributes of a TLD.
// //
// See https://developer.dnsimple.com/v2/tlds/#get // See https://developer.dnsimple.com/v2/tlds/#get
func (s *TldsService) GetTldExtendedAttributes(tld string) (*tldExtendedAttributesResponse, error) { func (s *TldsService) GetTldExtendedAttributes(tld string) (*tldExtendedAttributesResponse, error) {

2
vendor/modules.txt vendored
View File

@ -77,7 +77,7 @@ github.com/dgrijalva/jwt-go
github.com/digitalocean/godo github.com/digitalocean/godo
# github.com/dimchansky/utfbom v1.1.0 # github.com/dimchansky/utfbom v1.1.0
github.com/dimchansky/utfbom github.com/dimchansky/utfbom
# github.com/dnsimple/dnsimple-go v0.20.1-0.20181001130357-234ec949d37c # github.com/dnsimple/dnsimple-go v0.31.0
github.com/dnsimple/dnsimple-go/dnsimple github.com/dnsimple/dnsimple-go/dnsimple
# github.com/exoscale/egoscale v0.10.5 # github.com/exoscale/egoscale v0.10.5
github.com/exoscale/egoscale github.com/exoscale/egoscale