Compare commits
No commits in common. "master" and "v2.0.0" have entirely different histories.
@ -7,7 +7,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/libdns/libdns"
|
"github.com/libdns/libdns"
|
||||||
"github.com/libdns/vultr/v2"
|
|
||||||
|
"github.com/libdns/vultr"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -21,7 +22,6 @@ func main() {
|
|||||||
fmt.Printf("ZONE not set\n")
|
fmt.Printf("ZONE not set\n")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
shouldCleanup := os.Getenv("DELETE_RECORDS") == "true"
|
|
||||||
|
|
||||||
provider := vultr.Provider{APIToken: token}
|
provider := vultr.Provider{APIToken: token}
|
||||||
|
|
||||||
@ -44,46 +44,42 @@ func main() {
|
|||||||
for _, record := range records {
|
for _, record := range records {
|
||||||
fmt.Printf("%s (.%s): %s, %s\n", record.RR().Name, zone, record.RR().Data, record.RR().Type)
|
fmt.Printf("%s (.%s): %s, %s\n", record.RR().Name, zone, record.RR().Data, record.RR().Type)
|
||||||
|
|
||||||
|
recordId, err := vultr.GetRecordID(record)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("ERROR: %s\n", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
if record.RR().Name == testName {
|
if record.RR().Name == testName {
|
||||||
testId = record.(vultr.VultrRecord).ID
|
testId = recordId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if testId != "" {
|
if testId != "" {
|
||||||
if shouldCleanup {
|
// fmt.Printf("Delete entry for %s (id:%s)\n", testName, testId)
|
||||||
fmt.Printf("Delete entry for %s (id:%s)\n", testName, testId)
|
// _, err = provider.DeleteRecords(context.TODO(), zone, []libdns.Record{libdns.Record{
|
||||||
_, err = provider.DeleteRecords(context.TODO(), zone, []libdns.Record{vultr.VultrRecord{
|
// ID: testId,
|
||||||
ID: testId,
|
// }})
|
||||||
}})
|
// if err != nil {
|
||||||
if err != nil {
|
// fmt.Printf("ERROR: %s\n", err.Error())
|
||||||
fmt.Printf("ERROR: %s\n", err.Error())
|
// }
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Set only works if we have a record.ID
|
// Set only works if we have a record.ID
|
||||||
fmt.Printf("Replacing entry for %s\n", testName)
|
fmt.Printf("Replacing entry for %s\n", testName)
|
||||||
_, err = provider.SetRecords(context.TODO(), zone, []libdns.Record{vultr.VultrRecord{
|
_, err = provider.SetRecords(context.TODO(), zone, []libdns.Record{libdns.TXT{
|
||||||
Record: libdns.RR{
|
|
||||||
Name: testName,
|
Name: testName,
|
||||||
Type: "TXT",
|
Text: fmt.Sprintf("Replacement test entry created by libdns %s", time.Now()),
|
||||||
Data: fmt.Sprintf("Replacement test entry created by libdns %s", time.Now()),
|
TTL: time.Duration(30) * time.Second,
|
||||||
TTL: time.Duration(90) * time.Second,
|
ProviderData: testId,
|
||||||
},
|
|
||||||
ID: testId,
|
|
||||||
}})
|
}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("ERROR: %s\n", err.Error())
|
fmt.Printf("ERROR: %s\n", err.Error())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Creating new entry for %s\n", testName)
|
fmt.Printf("Creating new entry for %s\n", testName)
|
||||||
_, err = provider.AppendRecords(context.TODO(), zone, []libdns.Record{vultr.VultrRecord{
|
_, err = provider.AppendRecords(context.TODO(), zone, []libdns.Record{libdns.RR{
|
||||||
Record: libdns.RR{
|
|
||||||
Type: "TXT",
|
Type: "TXT",
|
||||||
Name: testName,
|
Name: testName,
|
||||||
Data: fmt.Sprintf("This is a test entry created by libdns %s", time.Now()),
|
Data: fmt.Sprintf("This is a test entry created by libdns %s", time.Now()),
|
||||||
TTL: time.Duration(60) * time.Second,
|
TTL: time.Duration(30) * time.Second,
|
||||||
},
|
|
||||||
ID: testId,
|
|
||||||
}})
|
}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("ERROR: %s\n", err.Error())
|
fmt.Printf("ERROR: %s\n", err.Error())
|
||||||
|
|||||||
55
client.go
55
client.go
@ -2,7 +2,6 @@ package vultr
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
@ -28,6 +27,9 @@ func (p *Provider) getClient() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) getDNSEntries(ctx context.Context, domain string) ([]libdns.Record, error) {
|
func (p *Provider) getDNSEntries(ctx context.Context, domain string) ([]libdns.Record, error) {
|
||||||
|
p.client.mutex.Lock()
|
||||||
|
defer p.client.mutex.Unlock()
|
||||||
|
|
||||||
p.getClient()
|
p.getClient()
|
||||||
|
|
||||||
listOptions := &govultr.ListOptions{}
|
listOptions := &govultr.ListOptions{}
|
||||||
@ -40,7 +42,11 @@ func (p *Provider) getDNSEntries(ctx context.Context, domain string) ([]libdns.R
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, entry := range dns_entries {
|
for _, entry := range dns_entries {
|
||||||
record := fromAPIRecord(entry, domain)
|
record, err := libdnsRecord(entry, domain)
|
||||||
|
if err != nil {
|
||||||
|
return records, err
|
||||||
|
}
|
||||||
|
|
||||||
records = append(records, record)
|
records = append(records, record)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,14 +66,22 @@ func (p *Provider) addDNSRecord(ctx context.Context, domain string, r libdns.Rec
|
|||||||
|
|
||||||
p.getClient()
|
p.getClient()
|
||||||
|
|
||||||
domainRecordReq := toDomainRecordReq(r)
|
rr := r.RR()
|
||||||
|
|
||||||
rec, _, err := p.client.vultr.DomainRecord.Create(ctx, domain, &domainRecordReq)
|
domainRecordReq, err := vultrRecordReq(rr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return r, err
|
return r, err
|
||||||
}
|
}
|
||||||
|
|
||||||
record := fromLibdnsRecord(r, rec.ID)
|
rec, _, err := p.client.vultr.DomainRecord.Create(ctx, domain, &domainRecordReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
record, err := libdnsRecord(*rec, domain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return record, nil
|
return record, nil
|
||||||
}
|
}
|
||||||
@ -78,19 +92,9 @@ func (p *Provider) removeDNSRecord(ctx context.Context, domain string, record li
|
|||||||
|
|
||||||
p.getClient()
|
p.getClient()
|
||||||
|
|
||||||
recordId, err := getRecordId(record)
|
recordId, err := GetRecordID(record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// try to get the ID from API if we don't have it
|
return record, err
|
||||||
records, err := p.getDNSEntries(ctx, domain)
|
|
||||||
if err != nil {
|
|
||||||
return record, fmt.Errorf("could not get record ID from API")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rec := range records {
|
|
||||||
if rec.RR().Name == record.RR().Name {
|
|
||||||
recordId = rec.(VultrRecord).ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = p.client.vultr.DomainRecord.Delete(ctx, domain, recordId)
|
err = p.client.vultr.DomainRecord.Delete(ctx, domain, recordId)
|
||||||
@ -107,22 +111,15 @@ func (p *Provider) updateDNSRecord(ctx context.Context, domain string, record li
|
|||||||
|
|
||||||
p.getClient()
|
p.getClient()
|
||||||
|
|
||||||
recordId, err := getRecordId(record)
|
recordId, err := GetRecordID(record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// try to get the ID from API if we don't have it
|
return record, err
|
||||||
records, err := p.getDNSEntries(ctx, domain)
|
|
||||||
if err != nil {
|
|
||||||
return record, fmt.Errorf("could not get record ID from API")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rec := range records {
|
domainRecordReq, err := vultrRecordReq(record)
|
||||||
if rec.RR().Data == record.RR().Data {
|
if err != nil {
|
||||||
recordId = rec.(VultrRecord).ID
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
domainRecordReq := toDomainRecordReq(record)
|
|
||||||
|
|
||||||
err = p.client.vultr.DomainRecord.Update(ctx, domain, recordId, &domainRecordReq)
|
err = p.client.vultr.DomainRecord.Update(ctx, domain, recordId, &domainRecordReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
204
helpers.go
204
helpers.go
@ -2,6 +2,8 @@ package vultr
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -9,84 +11,158 @@ import (
|
|||||||
"github.com/vultr/govultr/v3"
|
"github.com/vultr/govultr/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type VultrRecord struct {
|
// Converts `govultr.DomainRecord` to `libdns.Record“
|
||||||
Record libdns.RR
|
// Taken from libdns/cloudflare, adapted for Vultr's specific format
|
||||||
ID string
|
func libdnsRecord(r govultr.DomainRecord, zone string) (libdns.Record, error) {
|
||||||
}
|
|
||||||
|
|
||||||
func (r VultrRecord) RR() libdns.RR {
|
|
||||||
return r.Record
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converts a govultr.DomainRecord to libdns.Record
|
|
||||||
// Taken from libdns/digitalocean
|
|
||||||
func fromAPIRecord(r govultr.DomainRecord, zone string) VultrRecord {
|
|
||||||
name := libdns.RelativeName(r.Name, zone)
|
name := libdns.RelativeName(r.Name, zone)
|
||||||
ttl := time.Duration(r.TTL) * time.Second
|
ttl := time.Duration(r.TTL) * time.Second
|
||||||
|
|
||||||
// Vultr uses a custom priority field for MX and SRV records
|
switch r.Type {
|
||||||
data := r.Data
|
case "A", "AAAA":
|
||||||
if r.Type == "MX" || r.Type == "SRV" {
|
addr, err := netip.ParseAddr(r.Data)
|
||||||
data = fmt.Sprintf("%d %s", r.Priority, r.Data)
|
if err != nil {
|
||||||
|
return libdns.Address{}, fmt.Errorf("invalid IP address %q: %v", r.Data, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return VultrRecord{
|
return libdns.Address{
|
||||||
Record: libdns.RR{
|
Name: name,
|
||||||
|
TTL: ttl,
|
||||||
|
IP: addr,
|
||||||
|
ProviderData: r.ID,
|
||||||
|
}, nil
|
||||||
|
case "CAA":
|
||||||
|
dataParts := strings.SplitN(r.Data, " ", 3)
|
||||||
|
if len(dataParts) < 3 {
|
||||||
|
return libdns.SRV{}, fmt.Errorf("record %v does not contain enough data fields; expected format: '<flags> <tag> <value>'", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
flags, err := strconv.Atoi(dataParts[0])
|
||||||
|
if err != nil {
|
||||||
|
return libdns.SRV{}, fmt.Errorf("record %v contains invalid value for flags: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return libdns.CAA{
|
||||||
|
Name: name,
|
||||||
|
TTL: ttl,
|
||||||
|
Flags: uint8(flags),
|
||||||
|
Tag: dataParts[1],
|
||||||
|
Value: dataParts[2],
|
||||||
|
ProviderData: r.ID,
|
||||||
|
}, nil
|
||||||
|
case "CNAME":
|
||||||
|
return libdns.CNAME{
|
||||||
|
Name: name,
|
||||||
|
TTL: ttl,
|
||||||
|
Target: r.Data,
|
||||||
|
ProviderData: r.ID,
|
||||||
|
}, nil
|
||||||
|
case "MX":
|
||||||
|
return libdns.MX{
|
||||||
|
Name: name,
|
||||||
|
TTL: ttl,
|
||||||
|
Preference: uint16(r.Priority),
|
||||||
|
Target: r.Data,
|
||||||
|
ProviderData: r.ID,
|
||||||
|
}, nil
|
||||||
|
case "NS":
|
||||||
|
return libdns.NS{
|
||||||
|
Name: name,
|
||||||
|
TTL: ttl,
|
||||||
|
Target: r.Data,
|
||||||
|
ProviderData: r.ID,
|
||||||
|
}, nil
|
||||||
|
case "SRV":
|
||||||
|
// Vultr doesn't append the zone to the SRV record name, so we just need
|
||||||
|
// to parse 2 parts
|
||||||
|
parts := strings.SplitN(r.Name, ".", 2)
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return libdns.SRV{}, fmt.Errorf("name %v does not contain enough fields; expected format: '_service._proto.name'", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
dataParts := strings.SplitN(r.Data, " ", 3)
|
||||||
|
if len(dataParts) < 3 {
|
||||||
|
return libdns.SRV{}, fmt.Errorf("record %v does not contain enough data fields; expected format: 'weight port target'", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
weight, err := strconv.Atoi(dataParts[0])
|
||||||
|
if err != nil {
|
||||||
|
return libdns.SRV{}, fmt.Errorf("record %v contains invalid value for weight: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := strconv.Atoi(dataParts[1])
|
||||||
|
if err != nil {
|
||||||
|
return libdns.SRV{}, fmt.Errorf("record %v contains invalid value for port: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return libdns.SRV{
|
||||||
|
Service: strings.TrimPrefix(parts[0], "_"),
|
||||||
|
Transport: strings.TrimPrefix(parts[1], "_"),
|
||||||
|
Name: zone,
|
||||||
|
TTL: ttl,
|
||||||
|
Priority: uint16(r.Priority),
|
||||||
|
Weight: uint16(weight),
|
||||||
|
Port: uint16(port),
|
||||||
|
Target: dataParts[2],
|
||||||
|
ProviderData: r.ID,
|
||||||
|
}, nil
|
||||||
|
case "TXT":
|
||||||
|
return libdns.TXT{
|
||||||
|
Name: name,
|
||||||
|
TTL: ttl,
|
||||||
|
Text: r.Data,
|
||||||
|
ProviderData: r.ID,
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return libdns.RR{
|
||||||
Name: name,
|
Name: name,
|
||||||
TTL: ttl,
|
TTL: ttl,
|
||||||
Type: r.Type,
|
Type: r.Type,
|
||||||
Data: data,
|
Data: r.Data,
|
||||||
},
|
}.Parse()
|
||||||
ID: r.ID,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts a libdns.Record to VultrRecord with an optional ID
|
// Converts `libdns.Record` to `govultr.DomainRecordReq`, to be used with API
|
||||||
func fromLibdnsRecord(r libdns.Record, id string) VultrRecord {
|
// requests.
|
||||||
rr := r.RR()
|
func vultrRecordReq(r libdns.Record) (govultr.DomainRecordReq, error) {
|
||||||
return VultrRecord{
|
|
||||||
Record: rr,
|
|
||||||
ID: id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converts a libdns.Record to a govultr.DomainRecordReq
|
|
||||||
func toDomainRecordReq(r libdns.Record) govultr.DomainRecordReq {
|
|
||||||
data := r.RR().Data
|
|
||||||
var priority int
|
|
||||||
|
|
||||||
// Vultr uses a custom priority field for MX and SRV records
|
|
||||||
if rec, ok := r.RR().Parse(); ok == nil {
|
|
||||||
if r.RR().Type == "MX" {
|
|
||||||
mx := rec.(libdns.MX)
|
|
||||||
priority = int(mx.Preference)
|
|
||||||
data = mx.Target
|
|
||||||
} else if r.RR().Type == "SRV" {
|
|
||||||
srv := rec.(libdns.SRV)
|
|
||||||
priority = int(srv.Priority)
|
|
||||||
data = data[strings.Index(data, " ")+1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rr := r.RR()
|
|
||||||
return govultr.DomainRecordReq{
|
return govultr.DomainRecordReq{
|
||||||
Name: rr.Name,
|
Name: r.RR().Name,
|
||||||
Type: rr.Type,
|
Type: r.RR().Type,
|
||||||
TTL: int(rr.TTL.Seconds()),
|
TTL: int(r.RR().TTL.Seconds()),
|
||||||
Data: data,
|
Data: r.RR().Data,
|
||||||
Priority: &priority,
|
}, nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRecordId(r libdns.Record) (string, error) {
|
func GetRecordID(r libdns.Record) (string, error) {
|
||||||
var id string
|
var recordId string
|
||||||
if vr, err := r.(VultrRecord); err {
|
|
||||||
id = vr.ID
|
switch r := r.(type) {
|
||||||
|
case libdns.Address:
|
||||||
|
recordId = r.ProviderData.(string)
|
||||||
|
return recordId, nil
|
||||||
|
case libdns.CAA:
|
||||||
|
recordId = r.ProviderData.(string)
|
||||||
|
return recordId, nil
|
||||||
|
case libdns.CNAME:
|
||||||
|
recordId = r.ProviderData.(string)
|
||||||
|
return recordId, nil
|
||||||
|
case libdns.MX:
|
||||||
|
recordId = r.ProviderData.(string)
|
||||||
|
return recordId, nil
|
||||||
|
case libdns.NS:
|
||||||
|
recordId = r.ProviderData.(string)
|
||||||
|
return recordId, nil
|
||||||
|
case libdns.SRV:
|
||||||
|
recordId = r.ProviderData.(string)
|
||||||
|
return recordId, nil
|
||||||
|
case libdns.ServiceBinding:
|
||||||
|
recordId = r.ProviderData.(string)
|
||||||
|
return recordId, nil
|
||||||
|
case libdns.TXT:
|
||||||
|
recordId = r.ProviderData.(string)
|
||||||
|
return recordId, nil
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
if id == "" {
|
return "", fmt.Errorf("libdns record has no provider record ID")
|
||||||
return "", fmt.Errorf("record has no ID: %v", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
return id, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user