mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
Rename source files that don't match the name/nameProvider.go standard. (#261)
This commit is contained in:
333
providers/vultr/vultrProvider.go
Normal file
333
providers/vultr/vultrProvider.go
Normal file
@@ -0,0 +1,333 @@
|
||||
package vultr
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
"github.com/StackExchange/dnscontrol/providers"
|
||||
"github.com/StackExchange/dnscontrol/providers/diff"
|
||||
"github.com/miekg/dns/dnsutil"
|
||||
|
||||
vultr "github.com/JamesClonk/vultr/lib"
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
Vultr API DNS provider:
|
||||
|
||||
Info required in `creds.json`:
|
||||
- token
|
||||
|
||||
*/
|
||||
|
||||
var docNotes = providers.DocumentationNotes{
|
||||
providers.DocCreateDomains: providers.Can(),
|
||||
providers.DocOfficiallySupported: providers.Cannot(),
|
||||
providers.CanUseAlias: providers.Cannot(),
|
||||
providers.CanUseTLSA: providers.Cannot(),
|
||||
providers.CanUsePTR: providers.Cannot(),
|
||||
}
|
||||
|
||||
func init() {
|
||||
providers.RegisterDomainServiceProviderType("VULTR", NewVultr, providers.CanUseSRV, providers.CanUseCAA, docNotes)
|
||||
}
|
||||
|
||||
// VultrApi represents the Vultr DNSServiceProvider
|
||||
type VultrApi struct {
|
||||
client *vultr.Client
|
||||
token string
|
||||
}
|
||||
|
||||
// defaultNS are the default nameservers for Vultr
|
||||
var defaultNS = []string{
|
||||
"ns1.vultr.com",
|
||||
"ns2.vultr.com",
|
||||
}
|
||||
|
||||
// NewVultr initializes a Vultr DNSServiceProvider
|
||||
func NewVultr(m map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
|
||||
api := &VultrApi{
|
||||
token: m["token"],
|
||||
}
|
||||
|
||||
if api.token == "" {
|
||||
return nil, fmt.Errorf("Vultr API token is required")
|
||||
}
|
||||
|
||||
api.client = vultr.NewClient(api.token, nil)
|
||||
|
||||
// Validate token
|
||||
_, err := api.client.GetAccountInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return api, nil
|
||||
}
|
||||
|
||||
// GetDomainCorrections gets the corrections for a DomainConfig
|
||||
func (api *VultrApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
dc.Punycode()
|
||||
|
||||
ok, err := api.isDomainInAccount(dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s is not a domain in the Vultr account", dc.Name)
|
||||
}
|
||||
|
||||
records, err := api.client.GetDNSRecords(dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
curRecords := make([]*models.RecordConfig, len(records))
|
||||
for i := range records {
|
||||
r, err := toRecordConfig(dc, &records[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
curRecords[i] = r
|
||||
}
|
||||
|
||||
differ := diff.New(dc)
|
||||
_, create, delete, modify := differ.IncrementalDiff(curRecords)
|
||||
|
||||
corrections := []*models.Correction{}
|
||||
|
||||
for _, mod := range delete {
|
||||
id := mod.Existing.Original.(*vultr.DNSRecord).RecordID
|
||||
corrections = append(corrections, &models.Correction{
|
||||
Msg: fmt.Sprintf("%s; Vultr RecordID: %v", mod.String(), id),
|
||||
F: func() error {
|
||||
return api.client.DeleteDNSRecord(dc.Name, id)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for _, mod := range create {
|
||||
r := toVultrRecord(dc, mod.Desired)
|
||||
corrections = append(corrections, &models.Correction{
|
||||
Msg: mod.String(),
|
||||
F: func() error {
|
||||
return api.client.CreateDNSRecord(dc.Name, r.Name, r.Type, r.Data, r.Priority, r.TTL)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for _, mod := range modify {
|
||||
id := mod.Existing.Original.(*vultr.DNSRecord).RecordID
|
||||
r := toVultrRecord(dc, mod.Desired)
|
||||
r.RecordID = id
|
||||
corrections = append(corrections, &models.Correction{
|
||||
Msg: fmt.Sprintf("%s; Vultr RecordID: %v", mod.String(), id),
|
||||
F: func() error {
|
||||
return api.client.UpdateDNSRecord(dc.Name, *r)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return corrections, nil
|
||||
}
|
||||
|
||||
// GetNameservers gets the Vultr nameservers for a domain
|
||||
func (api *VultrApi) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
||||
return models.StringsToNameservers(defaultNS), nil
|
||||
}
|
||||
|
||||
// EnsureDomainExists adds a domain to the Vutr DNS service if it does not exist
|
||||
func (api *VultrApi) EnsureDomainExists(domain string) error {
|
||||
ok, err := api.isDomainInAccount(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
// Vultr requires an initial IP, use a dummy one
|
||||
err := api.client.CreateDNSDomain(domain, "127.0.0.1")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok, err := api.isDomainInAccount(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("Unexpected error adding domain %s to Vultr account", domain)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *VultrApi) isDomainInAccount(domain string) (bool, error) {
|
||||
domains, err := api.client.GetDNSDomains()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var vd *vultr.DNSDomain
|
||||
for _, d := range domains {
|
||||
if d.Domain == domain {
|
||||
vd = &d
|
||||
}
|
||||
}
|
||||
|
||||
if vd == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// toRecordConfig converts a Vultr DNSRecord to a RecordConfig #rtype_variations
|
||||
func toRecordConfig(dc *models.DomainConfig, r *vultr.DNSRecord) (*models.RecordConfig, error) {
|
||||
// Turns r.Name into a FQDN
|
||||
// Vultr uses "" as the apex domain, instead of "@", and this handles it fine.
|
||||
name := dnsutil.AddOrigin(r.Name, dc.Name)
|
||||
|
||||
data := r.Data
|
||||
// Make target into a FQDN if it is a CNAME, NS, MX, or SRV
|
||||
if r.Type == "CNAME" || r.Type == "NS" || r.Type == "MX" {
|
||||
if !strings.HasSuffix(data, ".") {
|
||||
data = data + "."
|
||||
}
|
||||
data = dnsutil.AddOrigin(data, dc.Name)
|
||||
}
|
||||
// Remove quotes if it is a TXT
|
||||
if r.Type == "TXT" {
|
||||
if !strings.HasPrefix(data, `"`) || !strings.HasSuffix(data, `"`) {
|
||||
return nil, errors.New("Unexpected lack of quotes in TXT record from Vultr")
|
||||
}
|
||||
data = data[1 : len(data)-1]
|
||||
}
|
||||
|
||||
rc := &models.RecordConfig{
|
||||
NameFQDN: name,
|
||||
Type: r.Type,
|
||||
Target: data,
|
||||
TTL: uint32(r.TTL),
|
||||
Original: r,
|
||||
}
|
||||
|
||||
if r.Type == "MX" {
|
||||
rc.MxPreference = uint16(r.Priority)
|
||||
}
|
||||
|
||||
if r.Type == "SRV" {
|
||||
rc.SrvPriority = uint16(r.Priority)
|
||||
|
||||
// Vultr returns in the format "[weight] [port] [target]"
|
||||
splitData := strings.SplitN(rc.Target, " ", 3)
|
||||
if len(splitData) != 3 {
|
||||
return nil, fmt.Errorf("Unexpected data for SRV record returned by Vultr")
|
||||
}
|
||||
|
||||
weight, err := strconv.ParseUint(splitData[0], 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rc.SrvWeight = uint16(weight)
|
||||
|
||||
port, err := strconv.ParseUint(splitData[1], 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rc.SrvPort = uint16(port)
|
||||
|
||||
target := splitData[2]
|
||||
if !strings.HasSuffix(target, ".") {
|
||||
target = target + "."
|
||||
}
|
||||
rc.Target = dnsutil.AddOrigin(target, dc.Name)
|
||||
}
|
||||
|
||||
if r.Type == "CAA" {
|
||||
// Vultr returns in the format "[flag] [tag] [value]"
|
||||
splitData := strings.SplitN(rc.Target, " ", 3)
|
||||
if len(splitData) != 3 {
|
||||
return nil, fmt.Errorf("Unexpected data for CAA record returned by Vultr")
|
||||
}
|
||||
|
||||
flag, err := strconv.ParseUint(splitData[0], 10, 8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rc.CaaFlag = uint8(flag)
|
||||
|
||||
rc.CaaTag = splitData[1]
|
||||
|
||||
value := splitData[2]
|
||||
if strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) {
|
||||
value = value[1 : len(value)-1]
|
||||
}
|
||||
if strings.HasPrefix(value, `'`) && strings.HasSuffix(value, `'`) {
|
||||
value = value[1 : len(value)-1]
|
||||
}
|
||||
rc.Target = value
|
||||
}
|
||||
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
// toVultrRecord converts a RecordConfig converted by toRecordConfig back to a Vultr DNSRecord #rtype_variations
|
||||
func toVultrRecord(dc *models.DomainConfig, rc *models.RecordConfig) *vultr.DNSRecord {
|
||||
name := dnsutil.TrimDomainName(rc.NameFQDN, dc.Name)
|
||||
|
||||
// Vultr uses a blank string to represent the apex domain
|
||||
if name == "@" {
|
||||
name = ""
|
||||
}
|
||||
|
||||
data := rc.Target
|
||||
|
||||
// Vultr does not use a period suffix for the server for CNAME, NS, or MX
|
||||
if strings.HasSuffix(data, ".") {
|
||||
data = data[:len(data)-1]
|
||||
}
|
||||
// Vultr needs TXT record in quotes
|
||||
if rc.Type == "TXT" {
|
||||
data = fmt.Sprintf(`"%s"`, data)
|
||||
}
|
||||
|
||||
priority := 0
|
||||
|
||||
if rc.Type == "MX" {
|
||||
priority = int(rc.MxPreference)
|
||||
}
|
||||
|
||||
if rc.Type == "SRV" {
|
||||
priority = int(rc.SrvPriority)
|
||||
}
|
||||
|
||||
r := &vultr.DNSRecord{
|
||||
Type: rc.Type,
|
||||
Name: name,
|
||||
Data: data,
|
||||
TTL: int(rc.TTL),
|
||||
Priority: priority,
|
||||
}
|
||||
|
||||
if rc.Type == "SRV" {
|
||||
target := rc.Target
|
||||
if strings.HasSuffix(target, ".") {
|
||||
target = target[:len(target)-1]
|
||||
}
|
||||
|
||||
r.Data = fmt.Sprintf("%v %v %s", rc.SrvWeight, rc.SrvPort, target)
|
||||
}
|
||||
|
||||
if rc.Type == "CAA" {
|
||||
r.Data = fmt.Sprintf(`%v %s "%s"`, rc.CaaFlag, rc.CaaTag, rc.Target)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
Reference in New Issue
Block a user