commit ab4c4a2c9ee138bda3c659e95a84c475caf16146 Author: Alexandre Oliveira Date: Mon Nov 2 18:44:07 2020 +0100 Initial commit diff --git a/client.go b/client.go new file mode 100644 index 0000000..b7d9e37 --- /dev/null +++ b/client.go @@ -0,0 +1,99 @@ +package vultr + +import ( + "context" + "strconv" + "sync" + "time" + + "github.com/vultr/govultr" + "github.com/libdns/libdns" +) + +type Client struct { + client *govultr.Client + mutex sync.Mutex +} + +func (p *Provider) getClient() error { + if p.client == nil { + p.client = govultr.NewClient(nil, p.APIToken) + } + + return nil +} + +func (p *Provider) getDNSEntries(ctx context.Context, domain string) ([]libdns.Record, error) { + p.mutex.Lock() + defer p.mutex.Unlock() + + p.getClient() + + var records []libdns.Record + dns_entries, err := p.client.DNSRecord.List(ctx, domain) + if err != nil { + return records, err + } + + for _, entry := range dns_entries { + record := libdns.Record{ + Name: entry.Name, + Value: entry.Data, + Type: entry.Type, + TTL: time.Duration(entry.TTL) * time.Second, + ID: strconv.Itoa(entry.RecordID), + } + records = append(records, record) + } + + return records, nil +} + +func (p *Provider) addDNSRecord(ctx context.Context, domain string, record libdns.Record) (libdns.Record, error) { + p.mutex.Lock() + defer p.mutex.Unlock() + + p.getClient() + + err := p.client.DNSRecord.Create(ctx, domain, record.Type, record.Name, record.Value, int(record.TTL.Seconds()), 0) + if err != nil { + return record, err + } + + return record, nil +} + +func (p *Provider) removeDNSRecord(ctx context.Context, domain string, record libdns.Record) (libdns.Record, error) { + p.mutex.Lock() + defer p.mutex.Unlock() + + p.getClient() + + err := p.client.DNSRecord.Delete(ctx, domain, record.ID) + if err != nil { + return record, err + } + + return record, nil +} + +func (p *Provider) updateDNSRecord(ctx context.Context, domain string, record libdns.Record) (libdns.Record, error) { + p.mutex.Lock() + defer p.mutex.Unlock() + + p.getClient() + + entry := govultr.DNSRecord{ + Name: record.Name, + Data: record.Value, + Type: record.Type, + TTL: int(record.TTL.Seconds()), + } + + err := p.client.DNSRecord.Update(ctx, domain, &entry) + if err != nil { + return record, err + } + + return record, nil +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d469912 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module example.org/libdns/vultr + +go 1.15 + +require ( + github.com/libdns/libdns v0.1.0 + github.com/vultr/govultr v1.0.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3d9ce8e --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-retryablehttp v0.6.7 h1:8/CAEZt/+F7kR7GevNHulKkUjLht3CPmn7egmhieNKo= +github.com/hashicorp/go-retryablehttp v0.6.7/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/libdns/libdns v0.1.0 h1:0ctCOrVJsVzj53mop1angHp/pE3hmAhP7KiHvR0HD04= +github.com/libdns/libdns v0.1.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/vultr/govultr v1.0.0 h1:yeJrYp9wyA4xXaQZ7eOL2u1wKn2JU79HjRevwvpxbJ4= +github.com/vultr/govultr v1.0.0/go.mod h1:wZZXZbYbqyY1n3AldoeYNZK4Wnmmoq6dNFkvd5TV3ss= diff --git a/provider.go b/provider.go new file mode 100644 index 0000000..ec8875e --- /dev/null +++ b/provider.go @@ -0,0 +1,90 @@ +package vultr + +import ( + "context" + "strings" + "time" + + "github.com/libdns/libdns" +) + +// Provider implements the libdns interfaces for DigitalOcean +type Provider struct { + Client + // APIToken is the DigitalOcean API token - see https://www.digitalocean.com/docs/apis-clis/api/create-personal-access-token/ + APIToken string `json:"auth_token"` +} + +// unFQDN trims any trailing "." from fqdn. DigitalOcean's API does not use FQDNs. +func (p *Provider) unFQDN(fqdn string) string { + return strings.TrimSuffix(fqdn, ".") +} + +// GetRecords lists all the records in the zone. +func (p *Provider) GetRecords(ctx context.Context, zone string) ([]libdns.Record, error) { + records, err := p.getDNSEntries(ctx, p.unFQDN(zone)) + if err != nil { + return nil, err + } + + return records, nil +} + +// AppendRecords adds records to the zone. It returns the records that were added. +func (p *Provider) AppendRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) { + var appendedRecords []libdns.Record + + for _, record := range records { + newRecord, err := p.addDNSRecord(ctx, p.unFQDN(zone), record) + if err != nil { + return nil, err + } + newRecord.TTL = time.Duration(newRecord.TTL) * time.Second + appendedRecords = append(appendedRecords, newRecord) + } + + return appendedRecords, nil +} + +// DeleteRecords deletes the records from the zone. +func (p *Provider) DeleteRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) { + var deletedRecords []libdns.Record + + for _, record := range records { + deletedRecord, err := p.removeDNSRecord(ctx, p.unFQDN(zone), record) + if err != nil { + return nil, err + } + deletedRecord.TTL = time.Duration(deletedRecord.TTL) * time.Second + deletedRecords = append(deletedRecords, deletedRecord) + } + + return deletedRecords, nil +} + +// SetRecords sets the records in the zone, either by updating existing records +// or creating new ones. It returns the updated records. +func (p *Provider) SetRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) { + var setRecords []libdns.Record + + for _, record := range records { + // TODO: if there is no ID, look up the Name, and fill it in, or call + // newRecord, err := p.addDNSEntry(ctx, zone, record) + setRecord, err := p.updateDNSRecord(ctx, p.unFQDN(zone), record) + if err != nil { + return setRecords, err + } + setRecord.TTL = time.Duration(setRecord.TTL) * time.Second + setRecords = append(setRecords, setRecord) + } + + return setRecords, nil +} + +// Interface guards +var ( + _ libdns.RecordGetter = (*Provider)(nil) + _ libdns.RecordAppender = (*Provider)(nil) + _ libdns.RecordSetter = (*Provider)(nil) + _ libdns.RecordDeleter = (*Provider)(nil) +)