mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-01-11 18:08:57 +08:00
60470a3886
Signed-off-by: Amelia Aronsohn <squirrel@wearing.black> Co-authored-by: Tom Limoncelli <tal@whatexit.org> Co-authored-by: Grégoire Henry <hnrgrgr@users.noreply.github.com> Co-authored-by: Amelia Aronsohn <squirrel@wearing.black> Co-authored-by: Kai Schwarz <kschwarz@hexonet.net> Co-authored-by: Asif Nawaz <asif.nawaz@centralnic.com> Co-authored-by: imlonghao <git@imlonghao.com> Co-authored-by: Will Power <1619102+willpower232@users.noreply.github.com>
228 lines
5.8 KiB
Go
228 lines
5.8 KiB
Go
package packetframe
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/StackExchange/dnscontrol/v3/models"
|
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
|
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
|
)
|
|
|
|
// packetframeProvider is the handle for this provider.
|
|
type packetframeProvider struct {
|
|
client *http.Client
|
|
baseURL *url.URL
|
|
token string
|
|
domainIndex map[string]zoneInfo
|
|
}
|
|
|
|
// newPacketframe creates the provider.
|
|
func newPacketframe(m map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
|
|
if m["token"] == "" {
|
|
return nil, fmt.Errorf("missing Packetframe token")
|
|
}
|
|
|
|
baseURL, err := url.Parse(defaultBaseURL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid base URL for Packetframe")
|
|
}
|
|
client := http.Client{}
|
|
|
|
api := &packetframeProvider{client: &client, baseURL: baseURL, token: m["token"]}
|
|
|
|
return api, nil
|
|
}
|
|
|
|
var features = providers.DocumentationNotes{
|
|
providers.CanGetZones: providers.Unimplemented(),
|
|
providers.CanUsePTR: providers.Can(),
|
|
providers.CanUseSRV: providers.Can(),
|
|
providers.DocDualHost: providers.Cannot(),
|
|
providers.DocOfficiallySupported: providers.Cannot(),
|
|
}
|
|
|
|
func init() {
|
|
fns := providers.DspFuncs{
|
|
Initializer: newPacketframe,
|
|
RecordAuditor: AuditRecords,
|
|
}
|
|
providers.RegisterDomainServiceProviderType("PACKETFRAME", fns, features)
|
|
}
|
|
|
|
// GetNameservers returns the nameservers for a domain.
|
|
func (api *packetframeProvider) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
|
return models.ToNameservers(defaultNameServerNames)
|
|
}
|
|
|
|
func (api *packetframeProvider) getZone(domain string) (*zoneInfo, error) {
|
|
if api.domainIndex == nil {
|
|
if err := api.fetchDomainList(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
z, ok := api.domainIndex[domain+"."]
|
|
if !ok {
|
|
return nil, fmt.Errorf("%q not a zone in Packetframe account", domain)
|
|
}
|
|
|
|
return &z, nil
|
|
}
|
|
|
|
// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
|
|
func (api *packetframeProvider) GetZoneRecords(domain string) (models.Records, error) {
|
|
|
|
zone, err := api.getZone(domain)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("no such zone %q in Packetframe account", domain)
|
|
}
|
|
|
|
records, err := api.getRecords(zone.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not load records for domain %q", domain)
|
|
}
|
|
|
|
existingRecords := make([]*models.RecordConfig, len(records))
|
|
|
|
dc := models.DomainConfig{
|
|
Name: domain,
|
|
}
|
|
|
|
for i := range records {
|
|
existingRecords[i] = toRc(&dc, &records[i])
|
|
}
|
|
|
|
return existingRecords, nil
|
|
}
|
|
|
|
// GetZoneRecordsCorrections returns a list of corrections that will turn existing records into dc.Records.
|
|
func (api *packetframeProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, existingRecords models.Records) ([]*models.Correction, error) {
|
|
zone, err := api.getZone(dc.Name)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("no such zone %q in Packetframe account", dc.Name)
|
|
}
|
|
|
|
var corrections []*models.Correction
|
|
var differ diff.Differ
|
|
if !diff2.EnableDiff2 {
|
|
differ = diff.New(dc)
|
|
} else {
|
|
differ = diff.NewCompat(dc)
|
|
}
|
|
_, create, dels, modify, err := differ.IncrementalDiff(existingRecords)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, m := range create {
|
|
req, err := toReq(zone.ID, dc, m.Desired)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
corr := &models.Correction{
|
|
Msg: m.String(),
|
|
F: func() error {
|
|
_, err := api.createRecord(req)
|
|
return err
|
|
},
|
|
}
|
|
corrections = append(corrections, corr)
|
|
}
|
|
|
|
for _, m := range dels {
|
|
original := m.Existing.Original.(*domainRecord)
|
|
if original.ID == "0" { // Skip the default nameservers
|
|
continue
|
|
}
|
|
|
|
corr := &models.Correction{
|
|
Msg: m.String(),
|
|
F: func() error {
|
|
err := api.deleteRecord(zone.ID, original.ID)
|
|
return err
|
|
},
|
|
}
|
|
corrections = append(corrections, corr)
|
|
}
|
|
|
|
for _, m := range modify {
|
|
original := m.Existing.Original.(*domainRecord)
|
|
if original.ID == "0" { // Skip the default nameservers
|
|
continue
|
|
}
|
|
|
|
req, _ := toReq(zone.ID, dc, m.Desired)
|
|
req.ID = original.ID
|
|
corr := &models.Correction{
|
|
Msg: m.String(),
|
|
F: func() error {
|
|
err := api.modifyRecord(req)
|
|
return err
|
|
},
|
|
}
|
|
corrections = append(corrections, corr)
|
|
}
|
|
|
|
return corrections, nil
|
|
}
|
|
|
|
func toReq(zoneID string, dc *models.DomainConfig, rc *models.RecordConfig) (*domainRecord, error) {
|
|
req := &domainRecord{
|
|
Type: rc.Type,
|
|
TTL: int(rc.TTL),
|
|
Label: rc.GetLabel(),
|
|
Zone: zoneID,
|
|
}
|
|
|
|
switch rc.Type { // #rtype_variations
|
|
case "A", "AAAA", "PTR", "TXT", "CNAME", "NS":
|
|
req.Value = rc.GetTargetField()
|
|
case "MX":
|
|
req.Value = fmt.Sprintf("%d %s", rc.MxPreference, rc.GetTargetField())
|
|
case "SRV":
|
|
req.Value = fmt.Sprintf("%d %d %d %s", rc.SrvPriority, rc.SrvWeight, rc.SrvPort, rc.GetTargetField())
|
|
default:
|
|
return nil, fmt.Errorf("packetframe.toReq rtype %q unimplemented", rc.Type)
|
|
}
|
|
|
|
return req, nil
|
|
}
|
|
|
|
func toRc(dc *models.DomainConfig, r *domainRecord) *models.RecordConfig {
|
|
rc := &models.RecordConfig{
|
|
Type: r.Type,
|
|
TTL: uint32(r.TTL),
|
|
Original: r,
|
|
}
|
|
|
|
label := strings.TrimSuffix(r.Label, dc.Name+".")
|
|
label = strings.TrimSuffix(label, ".")
|
|
if label == "" {
|
|
label = "@"
|
|
}
|
|
rc.SetLabel(label, dc.Name)
|
|
|
|
switch rtype := r.Type; rtype { // #rtype_variations
|
|
case "TXT":
|
|
rc.SetTargetTXT(r.Value)
|
|
case "SRV":
|
|
spl := strings.Split(r.Value, " ")
|
|
prio, _ := strconv.ParseUint(spl[0], 10, 16)
|
|
weight, _ := strconv.ParseUint(spl[1], 10, 16)
|
|
port, _ := strconv.ParseUint(spl[2], 10, 16)
|
|
rc.SetTargetSRV(uint16(prio), uint16(weight), uint16(port), spl[3])
|
|
case "MX":
|
|
spl := strings.Split(r.Value, " ")
|
|
prio, _ := strconv.ParseUint(spl[0], 10, 16)
|
|
rc.SetTargetMX(uint16(prio), spl[1])
|
|
default:
|
|
rc.SetTarget(r.Value)
|
|
}
|
|
|
|
return rc
|
|
}
|