mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-09-30 08:54:26 +08:00
This should enable the diff2 code to be inserted with good "git blame" results for new code. I'm adding this early to catch any problems early.
247 lines
6.1 KiB
Go
247 lines
6.1 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]zone
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
|
|
func (api *packetframeProvider) GetZoneRecords(domain string) (models.Records, error) {
|
|
|
|
if api.domainIndex == nil {
|
|
if err := api.fetchDomainList(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
zone, ok := api.domainIndex[domain+"."]
|
|
if !ok {
|
|
return nil, fmt.Errorf("%q not a zone 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
|
|
}
|
|
|
|
// GetDomainCorrections returns the corrections for a domain.
|
|
func (api *packetframeProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
|
dc, err := dc.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dc.Punycode()
|
|
|
|
if api.domainIndex == nil {
|
|
if err := api.fetchDomainList(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
zone, ok := api.domainIndex[dc.Name+"."]
|
|
if !ok {
|
|
return nil, fmt.Errorf("no such zone %q in Packetframe account", dc.Name)
|
|
}
|
|
|
|
records, err := api.getRecords(zone.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not load records for domain %q", dc.Name)
|
|
}
|
|
|
|
existingRecords := make([]*models.RecordConfig, len(records))
|
|
|
|
for i := range records {
|
|
existingRecords[i] = toRc(dc, &records[i])
|
|
}
|
|
|
|
// Normalize
|
|
models.PostProcessRecords(existingRecords)
|
|
|
|
var corrections []*models.Correction
|
|
if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
|
|
|
|
differ := diff.New(dc)
|
|
_, create, delete, 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 delete {
|
|
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
|
|
}
|
|
|
|
// Insert Future diff2 version here.
|
|
|
|
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.SetTargetTXTString(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
|
|
}
|