mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-09-12 16:14:42 +08:00
AUTODNS: Enable "get-zones" (ListZones, EnsureZoneExists, GetRegistrarCorrections) (#3568)
Co-authored-by: Tom Limoncelli <tlimoncelli@stackoverflow.com>
This commit is contained in:
parent
277a260d64
commit
be081cddad
4 changed files with 179 additions and 11 deletions
|
@ -4,9 +4,11 @@ import (
|
|||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
|
@ -62,7 +64,8 @@ func (api *autoDNSProvider) request(method string, requestPath string, data inte
|
|||
|
||||
responseText, _ := io.ReadAll(response.Body)
|
||||
|
||||
if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusTooManyRequests {
|
||||
// FUTUREWORK: 202 starts a long-running task. Should we instead poll here until task is completed?
|
||||
if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusAccepted && response.StatusCode != http.StatusTooManyRequests {
|
||||
return nil, errors.New("Request to " + requestURL.Path + " failed: " + string(responseText))
|
||||
}
|
||||
|
||||
|
@ -99,9 +102,12 @@ func (api *autoDNSProvider) findZoneSystemNameServer(domain string) (*models.Nam
|
|||
}
|
||||
|
||||
var responseObject JSONResponseDataZone
|
||||
_ = json.Unmarshal(responseData, &responseObject)
|
||||
if err := json.Unmarshal(responseData, &responseObject); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(responseObject.Data) != 1 {
|
||||
return nil, errors.New("Domain " + domain + " could not be found in AutoDNS")
|
||||
return nil, fmt.Errorf("Zone "+domain+" could not be found in AutoDNS: %w", os.ErrNotExist)
|
||||
}
|
||||
|
||||
systemNameServer := &models.Nameserver{Name: responseObject.Data[0].SystemNameServer}
|
||||
|
@ -109,6 +115,42 @@ func (api *autoDNSProvider) findZoneSystemNameServer(domain string) (*models.Nam
|
|||
return systemNameServer, nil
|
||||
}
|
||||
|
||||
func (api *autoDNSProvider) createZone(domain string, zone *Zone) (*Zone, error) {
|
||||
responseData, err := api.request("POST", "zone", zone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var responseObject JSONResponseDataZone
|
||||
if err := json.Unmarshal(responseData, &responseObject); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(responseObject.Data) != 1 {
|
||||
return nil, errors.New("Zone " + domain + " not returned")
|
||||
}
|
||||
|
||||
return responseObject.Data[0], nil
|
||||
}
|
||||
|
||||
func (api *autoDNSProvider) getDomain(domain string) (*Domain, error) {
|
||||
responseData, err := api.request("GET", "domain/"+domain, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var responseObject JSONResponseDataDomain
|
||||
if err := json.Unmarshal(responseData, &responseObject); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(responseObject.Data) != 1 {
|
||||
return nil, fmt.Errorf("Domain "+domain+" could not be found in AutoDNS: %w", os.ErrNotExist)
|
||||
}
|
||||
|
||||
return responseObject.Data[0], nil
|
||||
}
|
||||
|
||||
func (api *autoDNSProvider) getZone(domain string) (*Zone, error) {
|
||||
systemNameServer, err := api.findZoneSystemNameServer(domain)
|
||||
if err != nil {
|
||||
|
@ -124,14 +166,32 @@ func (api *autoDNSProvider) getZone(domain string) (*Zone, error) {
|
|||
|
||||
var responseObject JSONResponseDataZone
|
||||
// make sure that the response is valid, the zone is in AutoDNS but we're not sure the returned data meets our expectation
|
||||
unmErr := json.Unmarshal(responseData, &responseObject)
|
||||
if unmErr != nil {
|
||||
return nil, unmErr
|
||||
if err := json.Unmarshal(responseData, &responseObject); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return responseObject.Data[0], nil
|
||||
}
|
||||
|
||||
func (api *autoDNSProvider) getZones() ([]string, error) {
|
||||
responseData, err := api.request("POST", "zone/_search", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var responseObject JSONResponseDataZone
|
||||
if err := json.Unmarshal(responseData, &responseObject); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(responseObject.Data))
|
||||
for _, zone := range responseObject.Data {
|
||||
names = append(names, zone.Origin)
|
||||
}
|
||||
|
||||
return names, nil
|
||||
}
|
||||
|
||||
func (api *autoDNSProvider) updateZone(domain string, resourceRecords []*ResourceRecord, nameServers []*models.Nameserver, zoneTTL uint32) error {
|
||||
systemNameServer, err := api.findZoneSystemNameServer(domain)
|
||||
if err != nil {
|
||||
|
@ -172,3 +232,12 @@ func (api *autoDNSProvider) updateZone(domain string, resourceRecords []*Resourc
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *autoDNSProvider) updateDomain(name string, domain *Domain) error {
|
||||
_, err := api.request("PUT", "domain/"+name, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@ import (
|
|||
func AuditRecords(records []*models.RecordConfig) []error {
|
||||
a := rejectif.Auditor{}
|
||||
|
||||
a.Add("MX", rejectif.MxNull) // Last verified 2022-03-25
|
||||
a.Add("MX", rejectif.MxNull) // Last verified 2022-03-25
|
||||
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2025-05-13
|
||||
|
||||
return a.Audit(records)
|
||||
}
|
||||
|
|
|
@ -6,13 +6,16 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v4/models"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/diff2"
|
||||
"github.com/StackExchange/dnscontrol/v4/providers"
|
||||
"github.com/StackExchange/dnscontrol/v4/providers/bind"
|
||||
)
|
||||
|
||||
var features = providers.DocumentationNotes{
|
||||
|
@ -41,15 +44,19 @@ func init() {
|
|||
const providerName = "AUTODNS"
|
||||
const providerMaintainer = "@arnoschoon"
|
||||
fns := providers.DspFuncs{
|
||||
Initializer: New,
|
||||
Initializer: func(settings map[string]string, _ json.RawMessage) (providers.DNSServiceProvider, error) {
|
||||
return new(settings), nil
|
||||
},
|
||||
RecordAuditor: AuditRecords,
|
||||
}
|
||||
providers.RegisterRegistrarType(providerName, func(settings map[string]string) (providers.Registrar, error) {
|
||||
return new(settings), nil
|
||||
}, features)
|
||||
providers.RegisterDomainServiceProviderType(providerName, fns, features)
|
||||
providers.RegisterMaintainer(providerName, providerMaintainer)
|
||||
}
|
||||
|
||||
// New creates a new API handle.
|
||||
func New(settings map[string]string, _ json.RawMessage) (providers.DNSServiceProvider, error) {
|
||||
func new(settings map[string]string) *autoDNSProvider {
|
||||
api := &autoDNSProvider{}
|
||||
|
||||
api.baseURL = url.URL{
|
||||
|
@ -68,7 +75,7 @@ func New(settings map[string]string, _ json.RawMessage) (providers.DNSServicePro
|
|||
"X-Domainrobot-Context": []string{settings["context"]},
|
||||
}
|
||||
|
||||
return api, nil
|
||||
return api
|
||||
}
|
||||
|
||||
// GetZoneRecordsCorrections returns a list of corrections that will turn existing records into dc.Records.
|
||||
|
@ -243,6 +250,74 @@ func (api *autoDNSProvider) GetZoneRecords(domain string, meta map[string]string
|
|||
return existingRecords, nil
|
||||
}
|
||||
|
||||
func (api *autoDNSProvider) EnsureZoneExists(domain string) error {
|
||||
// try to get zone
|
||||
_, err := api.getZone(domain)
|
||||
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = api.createZone(domain, &Zone{
|
||||
Origin: domain,
|
||||
NameServers: []*models.Nameserver{
|
||||
{Name: "a.ns14.net"}, {Name: "b.ns14.net"},
|
||||
{Name: "c.ns14.net"}, {Name: "d.ns14.net"},
|
||||
},
|
||||
Soa: &bind.SoaDefaults{
|
||||
Expire: 1209600,
|
||||
Refresh: 43200,
|
||||
Retry: 7200,
|
||||
TTL: 86400,
|
||||
},
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (api *autoDNSProvider) ListZones() ([]string, error) {
|
||||
return api.getZones()
|
||||
}
|
||||
|
||||
func (api *autoDNSProvider) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
domain, err := api.getDomain(dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existingNs := make([]string, 0, len(domain.NameServers))
|
||||
for _, ns := range domain.NameServers {
|
||||
existingNs = append(existingNs, ns.Name)
|
||||
}
|
||||
sort.Strings(existingNs)
|
||||
existing := strings.Join(existingNs, ",")
|
||||
|
||||
desiredNs := models.NameserversToStrings(dc.Nameservers)
|
||||
sort.Strings(desiredNs)
|
||||
desired := strings.Join(desiredNs, ",")
|
||||
|
||||
if existing != desired {
|
||||
return []*models.Correction{
|
||||
{
|
||||
Msg: fmt.Sprintf("Change Nameservers from '%s' to '%s'", existing, desired),
|
||||
F: func() error {
|
||||
nameservers := make([]*NameServer, 0, len(desiredNs))
|
||||
for _, name := range desiredNs {
|
||||
nameservers = append(nameservers, &NameServer{
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
return api.updateDomain(dc.Name, &Domain{
|
||||
NameServers: nameservers,
|
||||
})
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func toRecordConfig(domain string, record *ResourceRecord) (*models.RecordConfig, error) {
|
||||
rc := &models.RecordConfig{
|
||||
Type: record.Type,
|
||||
|
|
|
@ -61,8 +61,31 @@ type Zone struct {
|
|||
SystemNameServer string `json:"virtualNameServer,omitempty"`
|
||||
}
|
||||
|
||||
// Domain represents the Domain in API calls.
|
||||
// These are only present for domains where AUTODNS also is a registrar.
|
||||
type Domain struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
NameServers []*NameServer `json:"nameServers"`
|
||||
Zone *Zone `json:"zone,omitempty"`
|
||||
}
|
||||
|
||||
type NameServer struct {
|
||||
// Host name of the nameserver written as a Fully-Qualified-Domain-Name (FQDN).
|
||||
Name string `json:"name"`
|
||||
// Time-to-live value of the nameservers in seconds
|
||||
TTL uint64 `json:"ttl,omitempty"`
|
||||
// IPv4 and IPv6 addresses of the name server. For GLUE records only; optional. The values for the IP addresses are only relevant for domain operations and are only used there in the case of glue name servers.
|
||||
IPAddresses []string `json:"ipAddresses,omitempty"`
|
||||
}
|
||||
|
||||
// JSONResponseDataZone represents the response to the DataZone call.
|
||||
type JSONResponseDataZone struct {
|
||||
// The data for the response. The type of the objects are depending on the request and are also specified in the responseObject value of the response.
|
||||
Data []*Zone `json:"data"`
|
||||
}
|
||||
|
||||
type JSONResponseDataDomain struct {
|
||||
// The data for the response. The type of the objects are depending on the request and are also specified in the responseObject value of the response.
|
||||
Data []*Domain `json:"data"`
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue