refactor
This commit is contained in:
parent
49563af412
commit
555c553686
18 changed files with 481 additions and 742 deletions
24
app/app.go
24
app/app.go
|
|
@ -6,13 +6,12 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"dnsupdater/provider/manager"
|
dnsservice "dnsupdater/dns/service"
|
||||||
|
|
||||||
"dnsupdater/ip"
|
"dnsupdater/ip"
|
||||||
"dnsupdater/ip/resolver"
|
"dnsupdater/ip/resolver"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Constant name for the virtual WAN interface
|
// WAN_IFACE Name for the virtual WAN interface
|
||||||
const WAN_IFACE = "wan"
|
const WAN_IFACE = "wan"
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
|
|
@ -20,8 +19,8 @@ type App struct {
|
||||||
|
|
||||||
cacheDefaultCallback ip.CacheDefaultCallback
|
cacheDefaultCallback ip.CacheDefaultCallback
|
||||||
|
|
||||||
// Updater manager
|
// DNS service manager
|
||||||
ProviderManager *manager.Manager
|
DnsServiceMgr *dnsservice.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeCacheCallback(service resolver.Service) ip.CacheDefaultCallback {
|
func makeCacheCallback(service resolver.Service) ip.CacheDefaultCallback {
|
||||||
|
|
@ -36,23 +35,22 @@ func makeCacheCallback(service resolver.Service) ip.CacheDefaultCallback {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApp(config *Config) (*App, error) {
|
func NewApp(config *Config) (*App, error) {
|
||||||
providerMgr := manager.New()
|
dnsServiceMgr := dnsservice.NewManager()
|
||||||
// providerMgr.Register("digitalocean", digitalocean.New(config.Services.DigitalOcean.Token))
|
err := dnsServiceMgr.RegisterFromConfig(config.Providers)
|
||||||
err := providerMgr.RegisterFromConfig(config.Providers)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
service := resolver.Get(config.Services.IPLookup)
|
ipService := resolver.Get(config.Services.IPLookup)
|
||||||
|
|
||||||
if service == nil {
|
if ipService == nil {
|
||||||
return nil, fmt.Errorf("Failed to load lookup service: %s", config.Services.IPLookup)
|
return nil, fmt.Errorf("failed to load lookup service: %s", config.Services.IPLookup)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &App{
|
return &App{
|
||||||
ProviderManager: providerMgr,
|
DnsServiceMgr: dnsServiceMgr,
|
||||||
cache: ip.NewCache(),
|
cache: ip.NewCache(),
|
||||||
cacheDefaultCallback: makeCacheCallback(service),
|
cacheDefaultCallback: makeCacheCallback(ipService),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
45
app/updater.go
Normal file
45
app/updater.go
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"dnsupdater/dns"
|
||||||
|
"dnsupdater/dns/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Updater struct {
|
||||||
|
service service.Service
|
||||||
|
// cache map[string][]dns.Record
|
||||||
|
cache dns.DomainRecordCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpdater(service service.Service) *Updater {
|
||||||
|
return &Updater{
|
||||||
|
service: service,
|
||||||
|
cache: *dns.NewDomainRecordCache(service.List),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u Updater) find(domain string, record_name string) (*dns.Record, error) {
|
||||||
|
records, err := u.cache.Get(domain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if rec, found := records.FindByName(record_name); found {
|
||||||
|
return &rec, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("could not find record %s", record_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u Updater) Update(domain, record string, ip net.IP) error {
|
||||||
|
var err error
|
||||||
|
var r *dns.Record
|
||||||
|
if r, err = u.find(domain, record); err == nil {
|
||||||
|
// Update if needed.
|
||||||
|
if r.Ip == nil || !r.Ip.Equal(ip) {
|
||||||
|
return u.service.Update(domain, r.Id, ip.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"dnsupdater/app"
|
App "dnsupdater/app"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
@ -21,28 +21,30 @@ func main() {
|
||||||
TimeFormat: time.RFC3339,
|
TimeFormat: time.RFC3339,
|
||||||
})
|
})
|
||||||
|
|
||||||
config, err := app.LoadConfig(*configFile)
|
config, err := App.LoadConfig(*configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Str("file", *configFile).Msg("Failed to load config")
|
log.Fatal().Err(err).Str("file", *configFile).Msg("Failed to load config")
|
||||||
}
|
}
|
||||||
|
|
||||||
app, err := app.NewApp(config)
|
app, err := App.NewApp(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Failed to initialize application")
|
log.Fatal().Err(err).Msg("Failed to initialize application")
|
||||||
}
|
}
|
||||||
|
|
||||||
for service_name, domains := range config.Updates {
|
for service_name, domains := range config.Updates {
|
||||||
|
|
||||||
// Get service
|
// Get DNS Service
|
||||||
service := app.ProviderManager.Get(service_name)
|
dnsService := app.DnsServiceMgr.Get(service_name)
|
||||||
|
|
||||||
if service == nil {
|
if dnsService == nil {
|
||||||
log.Warn().Str("service", service_name).Msg("Invalid service")
|
log.Warn().Str("service", service_name).Msg("Invalid DNS service")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info().Str("service", service_name).Msg("Begin update for service")
|
log.Info().Str("service", service_name).Msg("Begin update for service")
|
||||||
|
|
||||||
|
updater := App.NewUpdater(dnsService)
|
||||||
|
|
||||||
for domain, records := range domains {
|
for domain, records := range domains {
|
||||||
for name, data := range records {
|
for name, data := range records {
|
||||||
|
|
||||||
|
|
@ -61,7 +63,7 @@ func main() {
|
||||||
|
|
||||||
logger = logger.With().IPAddr("ip", ip).Logger()
|
logger = logger.With().IPAddr("ip", ip).Logger()
|
||||||
|
|
||||||
err = service.Update(domain, name, ip)
|
err = updater.Update(domain, name, ip)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error().Err(err).Msg("Failed to update record")
|
logger.Error().Err(err).Msg("Failed to update record")
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
28
dns/cache.go
Normal file
28
dns/cache.go
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
type DomainRecordCache struct {
|
||||||
|
domains map[string]RecordList
|
||||||
|
fetcher Fetcher
|
||||||
|
}
|
||||||
|
|
||||||
|
type Fetcher func(domain string) (RecordList, error)
|
||||||
|
|
||||||
|
func NewDomainRecordCache(fetcher Fetcher) *DomainRecordCache {
|
||||||
|
return &DomainRecordCache{
|
||||||
|
fetcher: fetcher,
|
||||||
|
domains: make(map[string]RecordList),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DomainRecordCache) Get(domain string) (RecordList, error) {
|
||||||
|
records, ok := c.domains[domain]
|
||||||
|
if !ok {
|
||||||
|
var err error
|
||||||
|
records, err = c.fetcher(domain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.domains[domain] = records
|
||||||
|
}
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
26
dns/record.go
Normal file
26
dns/record.go
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Record struct {
|
||||||
|
Id string // Internal id.
|
||||||
|
Name string
|
||||||
|
Ip net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecordList []Record
|
||||||
|
|
||||||
|
func (l *RecordList) Add(record Record) {
|
||||||
|
*l = append(*l, record)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l RecordList) FindByName(name string) (Record, bool) {
|
||||||
|
for _, record := range l {
|
||||||
|
if record.Name == name {
|
||||||
|
return record, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Record{}, false
|
||||||
|
}
|
||||||
|
|
@ -81,6 +81,10 @@ func (m mock) EditRecord(_ context.Context, domain string, id int, req *godo.Dom
|
||||||
m.t.Error("EditRecord called with empty request")
|
m.t.Error("EditRecord called with empty request")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if m.edit_record_error != nil {
|
||||||
|
return nil, nil, m.edit_record_error
|
||||||
|
}
|
||||||
|
|
||||||
assert.Equal(m.t, m.edit_record_request, req)
|
assert.Equal(m.t, m.edit_record_request, req)
|
||||||
|
|
||||||
record := godo.DomainRecord{
|
record := godo.DomainRecord{
|
||||||
|
|
@ -96,7 +100,7 @@ func (m mock) EditRecord(_ context.Context, domain string, id int, req *godo.Dom
|
||||||
Tag: req.Tag,
|
Tag: req.Tag,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &record, nil, m.edit_record_error
|
return &record, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m mock) CreateRecord(context.Context, string, *godo.DomainRecordEditRequest) (*godo.DomainRecord, *godo.Response, error) {
|
func (m mock) CreateRecord(context.Context, string, *godo.DomainRecordEditRequest) (*godo.DomainRecord, *godo.Response, error) {
|
||||||
67
dns/service/digitalocean/service.go
Normal file
67
dns/service/digitalocean/service.go
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
package digitalocean
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"dnsupdater/dns"
|
||||||
|
|
||||||
|
"github.com/digitalocean/godo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
api godo.DomainsService
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(token string) Service {
|
||||||
|
return Service{
|
||||||
|
api: godo.NewFromToken(token).Domains,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Factory(args map[string]any) (any, error) {
|
||||||
|
t, ok := args["token"]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("did not find token")
|
||||||
|
}
|
||||||
|
|
||||||
|
token, ok := t.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("token must be a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
return New(token), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Service) List(domain_name string) (dns.RecordList, error) {
|
||||||
|
fetchedRecords, _, err := d.api.RecordsByType(context.Background(), domain_name, "A", &godo.ListOptions{
|
||||||
|
PerPage: 50,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
records := dns.RecordList{}
|
||||||
|
for _, rec := range fetchedRecords {
|
||||||
|
records.Add(dns.Record{
|
||||||
|
Id: strconv.Itoa(rec.ID),
|
||||||
|
Name: rec.Name,
|
||||||
|
Ip: net.ParseIP(rec.Data),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Service) Update(domain, recordID, ip string) error {
|
||||||
|
id, err := strconv.Atoi(recordID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = d.api.EditRecord(context.Background(), domain, id, &godo.DomainRecordEditRequest{
|
||||||
|
Data: ip,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
103
dns/service/digitalocean/service_test.go
Normal file
103
dns/service/digitalocean/service_test.go
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
package digitalocean
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"dnsupdater/dns"
|
||||||
|
|
||||||
|
"github.com/digitalocean/godo"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDigitalOceanService_New(t *testing.T) {
|
||||||
|
assert.Equal(t, Service{
|
||||||
|
api: godo.NewFromToken("token").Domains,
|
||||||
|
}, New("token"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDigitalOceanService_List(t *testing.T) {
|
||||||
|
expected := dns.RecordList{
|
||||||
|
{
|
||||||
|
Id: "236718",
|
||||||
|
Name: "sub1",
|
||||||
|
Ip: net.IPv4(161, 125, 137, 64),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "23123131",
|
||||||
|
Name: "sub2",
|
||||||
|
Ip: net.IPv4(154, 63, 46, 159),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
service := Service{
|
||||||
|
api: mock{
|
||||||
|
t: t,
|
||||||
|
records_by_type: map[string][]godo.DomainRecord{
|
||||||
|
"example.com": {
|
||||||
|
{
|
||||||
|
ID: 236718,
|
||||||
|
Type: "A",
|
||||||
|
Name: "sub1",
|
||||||
|
Data: "161.125.137.64",
|
||||||
|
Priority: 10,
|
||||||
|
TTL: 1800,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 23123131,
|
||||||
|
Type: "A",
|
||||||
|
Name: "sub2",
|
||||||
|
Data: "154.63.46.159",
|
||||||
|
Priority: 5,
|
||||||
|
TTL: 1800,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test fetch.
|
||||||
|
records, err := service.List("example.com")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expected, records)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDigitalOceanService_Update(t *testing.T) {
|
||||||
|
mockApi := mock{
|
||||||
|
t: t,
|
||||||
|
records_by_type: map[string][]godo.DomainRecord{
|
||||||
|
"example.com": {
|
||||||
|
{
|
||||||
|
ID: 1337,
|
||||||
|
Name: "www",
|
||||||
|
Data: "80.17.42.157",
|
||||||
|
Priority: 10,
|
||||||
|
TTL: 360,
|
||||||
|
Port: 22,
|
||||||
|
Weight: 100,
|
||||||
|
Flags: 0xf1,
|
||||||
|
Tag: "some_tag",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
edit_record_request: &godo.DomainRecordEditRequest{
|
||||||
|
Data: "221.135.170.186",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
service := Service{
|
||||||
|
api: &mockApi,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := service.Update("example.com", "1337", net.IPv4(221, 135, 170, 186).String())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
mockApi.edit_record_error = errors.New("Error")
|
||||||
|
err = service.Update("invalid.com", "1340", net.IPv4(72, 82, 118, 186).String())
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
err = service.Update("example.com", "1337", net.IPv4(221, 135, 170, 186).String())
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
@ -1,36 +1,35 @@
|
||||||
package manager
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"dnsupdater/provider"
|
"dnsupdater/dns/service/digitalocean"
|
||||||
"dnsupdater/provider/digitalocean"
|
"dnsupdater/dns/service/vultr"
|
||||||
"dnsupdater/provider/vultr"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var factories = map[string]provider.ProviderFactory{
|
var factories = map[string]Factory{
|
||||||
"digitalocean": digitalocean.Factory,
|
"digitalocean": digitalocean.Factory,
|
||||||
"vultr": vultr.Factory,
|
"vultr": vultr.Factory,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
services map[string]provider.Provider
|
services map[string]Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *Manager {
|
func NewManager() *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
services: make(map[string]provider.Provider),
|
services: make(map[string]Service),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) Get(name string) provider.Provider {
|
func (m Manager) Get(name string) Service {
|
||||||
if service, ok := m.services[name]; ok {
|
if service, ok := m.services[name]; ok {
|
||||||
return service
|
return service
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) RegisterFromConfig(providers map[string]map[string]interface{}) error {
|
func (m Manager) RegisterFromConfig(providers map[string]map[string]any) error {
|
||||||
for name, args := range providers {
|
for name, args := range providers {
|
||||||
if factory, ok := factories[name]; ok {
|
if factory, ok := factories[name]; ok {
|
||||||
|
|
||||||
|
|
@ -39,12 +38,12 @@ func (m Manager) RegisterFromConfig(providers map[string]map[string]interface{})
|
||||||
return fmt.Errorf("could not create provider '%s': %v", name, err)
|
return fmt.Errorf("could not create provider '%s': %v", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Register(name, provider)
|
m.Register(name, provider.(Service))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) Register(name string, provider provider.Provider) {
|
func (m Manager) Register(name string, provider Service) {
|
||||||
m.services[name] = provider
|
m.services[name] = provider
|
||||||
}
|
}
|
||||||
10
dns/service/service.go
Normal file
10
dns/service/service.go
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import "dnsupdater/dns"
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
List(domain string) (dns.RecordList, error)
|
||||||
|
Update(domain, recordID, ip string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Factory func(map[string]any) (any, error)
|
||||||
69
dns/service/vultr/service.go
Normal file
69
dns/service/vultr/service.go
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
package vultr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"dnsupdater/dns"
|
||||||
|
|
||||||
|
"github.com/vultr/govultr/v3"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
api govultr.DomainRecordService
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(token string) Service {
|
||||||
|
ctx := context.Background()
|
||||||
|
config := &oauth2.Config{}
|
||||||
|
ts := config.TokenSource(ctx, &oauth2.Token{AccessToken: token})
|
||||||
|
client := govultr.NewClient(oauth2.NewClient(ctx, ts))
|
||||||
|
|
||||||
|
return Service{
|
||||||
|
api: client.DomainRecord,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Factory(args map[string]any) (any, error) {
|
||||||
|
t, ok := args["token"]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("did not find token")
|
||||||
|
}
|
||||||
|
|
||||||
|
token, ok := t.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("token must be a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
return New(token), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Service) List(domain_name string) (dns.RecordList, error) {
|
||||||
|
fetchedRecords, _, _, err := p.api.List(context.Background(), domain_name, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
records := dns.RecordList{}
|
||||||
|
for _, record := range fetchedRecords {
|
||||||
|
|
||||||
|
if record.Type != "A" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
records.Add(dns.Record{
|
||||||
|
Id: record.ID,
|
||||||
|
Name: record.Name,
|
||||||
|
Ip: net.ParseIP(record.Data),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Service) Update(domain, recordID, ip string) error {
|
||||||
|
return p.api.Update(context.Background(), domain, recordID, &govultr.DomainRecordReq{
|
||||||
|
Data: ip,
|
||||||
|
})
|
||||||
|
}
|
||||||
96
dns/service/vultr/service_test.go
Normal file
96
dns/service/vultr/service_test.go
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
package vultr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"dnsupdater/dns"
|
||||||
|
|
||||||
|
"github.com/vultr/govultr/v3"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVultrService_List(t *testing.T) {
|
||||||
|
expected := dns.RecordList{
|
||||||
|
{
|
||||||
|
Id: "656939ee-f942-4ce2-af1d-3bd68c764e96",
|
||||||
|
Name: "sub1",
|
||||||
|
Ip: net.IPv4(201, 110, 66, 72),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "c80118f4-f04c-4ad2-8ec2-16eb15cc8aca",
|
||||||
|
Name: "sub2",
|
||||||
|
Ip: net.IPv4(242, 124, 218, 187),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
service := Service{
|
||||||
|
api: mock{
|
||||||
|
t: t,
|
||||||
|
ListReturn: map[string][]govultr.DomainRecord{
|
||||||
|
"example.com": {
|
||||||
|
{
|
||||||
|
ID: "656939ee-f942-4ce2-af1d-3bd68c764e96",
|
||||||
|
Type: "A",
|
||||||
|
Name: "sub1",
|
||||||
|
Data: "201.110.66.72",
|
||||||
|
Priority: 2,
|
||||||
|
TTL: 1800,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "c80118f4-f04c-4ad2-8ec2-16eb15cc8aca",
|
||||||
|
Type: "A",
|
||||||
|
Name: "sub2",
|
||||||
|
Data: "242.124.218.187",
|
||||||
|
Priority: 1,
|
||||||
|
TTL: 1800,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
records, err := service.List("example.com")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, expected, records)
|
||||||
|
|
||||||
|
// Fetch invalid
|
||||||
|
_, err = service.List("noexists.com")
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVultrService_Update(t *testing.T) {
|
||||||
|
mockApi := mock{
|
||||||
|
t: t,
|
||||||
|
ListReturn: map[string][]govultr.DomainRecord{
|
||||||
|
"example.com": {
|
||||||
|
{
|
||||||
|
ID: "6cabe6ba-1ea1-405d-b66d-cd56ecac45ce",
|
||||||
|
Type: "A",
|
||||||
|
Name: "www",
|
||||||
|
Data: "80.17.42.157",
|
||||||
|
Priority: 10,
|
||||||
|
TTL: 360,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
service := Service{
|
||||||
|
api: &mockApi,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := service.Update("example.com", "6cabe6ba-1ea1-405d-b66d-cd56ecac45ce", net.IPv4(221, 135, 170, 186).String())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
mockApi.updateError = errors.New("Error")
|
||||||
|
|
||||||
|
err = service.Update("invalid.com", "332b40fc-0ddf-436c-a0c7-46586b928ac2", net.IPv4(72, 82, 118, 186).String())
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
err = service.Update("example.com", "6cabe6ba-1ea1-405d-b66d-cd56ecac45ce", net.IPv4(221, 135, 170, 186).String())
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"dnsupdater/provider"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Provider struct {
|
|
||||||
service godo.DomainsService
|
|
||||||
cache map[string][]godo.DomainRecord
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(token string) Provider {
|
|
||||||
return Provider{
|
|
||||||
service: godo.NewFromToken(token).Domains,
|
|
||||||
cache: make(map[string][]godo.DomainRecord),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Factory(args map[string]any) (provider.Provider, error) {
|
|
||||||
t, ok := args["token"]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("did not find token")
|
|
||||||
}
|
|
||||||
|
|
||||||
token, ok := t.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("token must be a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
return New(token), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Provider) fetch(domain string) ([]godo.DomainRecord, error) {
|
|
||||||
domains, ok := d.cache[domain]
|
|
||||||
if !ok {
|
|
||||||
var err error
|
|
||||||
options := &godo.ListOptions{
|
|
||||||
PerPage: 50,
|
|
||||||
}
|
|
||||||
|
|
||||||
domains, _, err = d.service.RecordsByType(context.Background(), domain, "A", options)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
d.cache[domain] = domains
|
|
||||||
}
|
|
||||||
return domains, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Provider) find(domain string, record string) (*godo.DomainRecord, error) {
|
|
||||||
records, err := d.fetch(domain)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, r := range records {
|
|
||||||
if r.Name == record {
|
|
||||||
return &r, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("could not find record %s", record)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Provider) Update(domain string, record string, ip net.IP) error {
|
|
||||||
r, err := d.find(domain, record)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Data != ip.String() {
|
|
||||||
// Update
|
|
||||||
req := godo.DomainRecordEditRequest{
|
|
||||||
// Type: r.Type,
|
|
||||||
// Name: r.Name,
|
|
||||||
Data: ip.String(),
|
|
||||||
// Priority: r.Priority,
|
|
||||||
// Port: r.Port,
|
|
||||||
// TTL: r.TTL,
|
|
||||||
// Weight: r.Weight,
|
|
||||||
// Flags: r.Flags,
|
|
||||||
// Tag: r.Tag,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err := d.service.EditRecord(context.Background(), domain, r.ID, &req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,263 +0,0 @@
|
||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestProvider_New(t *testing.T) {
|
|
||||||
assert.Equal(t, Provider{
|
|
||||||
service: godo.NewFromToken("token").Domains,
|
|
||||||
cache: make(map[string][]godo.DomainRecord),
|
|
||||||
}, New("token"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProvider_fetch(t *testing.T) {
|
|
||||||
expected := []godo.DomainRecord{
|
|
||||||
{
|
|
||||||
ID: 28448429,
|
|
||||||
Type: "A",
|
|
||||||
Name: "sub1",
|
|
||||||
Data: "201.110.66.72",
|
|
||||||
Priority: 2,
|
|
||||||
TTL: 1800,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 28448430,
|
|
||||||
Type: "A",
|
|
||||||
Name: "sub2",
|
|
||||||
Data: "242.124.218.187",
|
|
||||||
Priority: 1,
|
|
||||||
TTL: 1800,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
provider := Provider{
|
|
||||||
service: mock{
|
|
||||||
t: t,
|
|
||||||
records_by_type: map[string][]godo.DomainRecord{
|
|
||||||
"example.com": expected,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
cache: make(map[string][]godo.DomainRecord),
|
|
||||||
}
|
|
||||||
|
|
||||||
records, err := provider.fetch("example.com")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, expected, records)
|
|
||||||
|
|
||||||
// Fetch invalid
|
|
||||||
_, err = provider.fetch("noexists.com")
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProvider_fetch_caches_records(t *testing.T) {
|
|
||||||
example_com_records := []godo.DomainRecord{
|
|
||||||
{
|
|
||||||
ID: 28448429,
|
|
||||||
Type: "A",
|
|
||||||
Name: "sub1",
|
|
||||||
Data: "107.218.197.189",
|
|
||||||
Priority: 2,
|
|
||||||
TTL: 1800,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 28448430,
|
|
||||||
Type: "A",
|
|
||||||
Name: "sub2",
|
|
||||||
Data: "254.221.12.160",
|
|
||||||
Priority: 1,
|
|
||||||
TTL: 1800,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
another_com_records := []godo.DomainRecord{
|
|
||||||
{
|
|
||||||
ID: 237823,
|
|
||||||
Type: "A",
|
|
||||||
Name: "box",
|
|
||||||
Data: "108.151.98.62",
|
|
||||||
Priority: 2,
|
|
||||||
TTL: 1800,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 237824,
|
|
||||||
Type: "A",
|
|
||||||
Name: "ntp",
|
|
||||||
Data: "190.255.140.208",
|
|
||||||
Priority: 10,
|
|
||||||
TTL: 300,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
mockService := mock{
|
|
||||||
t: t,
|
|
||||||
records_by_type: map[string][]godo.DomainRecord{
|
|
||||||
"example.com": example_com_records,
|
|
||||||
"another.com": another_com_records,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
provider := Provider{
|
|
||||||
service: mockService,
|
|
||||||
cache: make(map[string][]godo.DomainRecord),
|
|
||||||
}
|
|
||||||
|
|
||||||
records, err := provider.fetch("example.com")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, example_com_records, records)
|
|
||||||
|
|
||||||
records, err = provider.fetch("another.com")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, another_com_records, records)
|
|
||||||
|
|
||||||
// Check cache, should be equal to the map in mock service.
|
|
||||||
assert.Equal(t, provider.cache, mockService.records_by_type)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProvider_fetch_from_cache(t *testing.T) {
|
|
||||||
expected := []godo.DomainRecord{
|
|
||||||
{
|
|
||||||
ID: 273671823,
|
|
||||||
Type: "A",
|
|
||||||
Name: "sub1",
|
|
||||||
Data: "42.170.152.94",
|
|
||||||
Priority: 10,
|
|
||||||
TTL: 1800,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
provider := Provider{
|
|
||||||
service: mock{t: t},
|
|
||||||
cache: map[string][]godo.DomainRecord{
|
|
||||||
"example.com": expected,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
records, err := provider.fetch("example.com")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, expected, records)
|
|
||||||
|
|
||||||
_, err = provider.fetch("noexists.com")
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProvider_find(t *testing.T) {
|
|
||||||
expected := []godo.DomainRecord{
|
|
||||||
{
|
|
||||||
ID: 236718,
|
|
||||||
Type: "A",
|
|
||||||
Name: "sub1",
|
|
||||||
Data: "161.125.137.64",
|
|
||||||
Priority: 10,
|
|
||||||
TTL: 1800,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 23123131,
|
|
||||||
Type: "A",
|
|
||||||
Name: "sub2",
|
|
||||||
Data: "154.63.46.159",
|
|
||||||
Priority: 5,
|
|
||||||
TTL: 1800,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
expected_cache := []godo.DomainRecord{
|
|
||||||
{
|
|
||||||
ID: 23713762,
|
|
||||||
Type: "A",
|
|
||||||
Name: "mail",
|
|
||||||
Data: "176.151.152.10",
|
|
||||||
Priority: 10,
|
|
||||||
TTL: 3600,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
provider := Provider{
|
|
||||||
service: mock{
|
|
||||||
t: t,
|
|
||||||
records_by_type: map[string][]godo.DomainRecord{
|
|
||||||
"example.com": expected,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
cache: map[string][]godo.DomainRecord{
|
|
||||||
"cached.com": expected_cache,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test fetch.
|
|
||||||
record, err := provider.find("example.com", "sub2")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, expected[1], *record)
|
|
||||||
|
|
||||||
// Test cached record
|
|
||||||
record, err = provider.find("cached.com", "mail")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, expected_cache[0], *record)
|
|
||||||
|
|
||||||
// Test not found (domain)
|
|
||||||
_, err = provider.find("noexists.com", "www")
|
|
||||||
assert.Error(t, err)
|
|
||||||
|
|
||||||
// Test not found (subdomain)
|
|
||||||
_, err = provider.find("cached.com", "nosub")
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProvider_Update(t *testing.T) {
|
|
||||||
expected := []godo.DomainRecord{
|
|
||||||
{
|
|
||||||
ID: 1337,
|
|
||||||
Type: "A",
|
|
||||||
Name: "www",
|
|
||||||
Data: "80.17.42.157",
|
|
||||||
Priority: 10,
|
|
||||||
TTL: 360,
|
|
||||||
Port: 22,
|
|
||||||
Weight: 100,
|
|
||||||
Flags: 0xf1,
|
|
||||||
Tag: "some_tag",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
mockService := mock{
|
|
||||||
t: t,
|
|
||||||
records_by_type: map[string][]godo.DomainRecord{
|
|
||||||
"example.com": expected,
|
|
||||||
},
|
|
||||||
edit_record_request: &godo.DomainRecordEditRequest{
|
|
||||||
// Type: "A",
|
|
||||||
// Name: "www",
|
|
||||||
Data: "221.135.170.186",
|
|
||||||
// Priority: 10,
|
|
||||||
// Port: 22,
|
|
||||||
// TTL: 360,
|
|
||||||
// Weight: 100,
|
|
||||||
// Flags: 0xf1,
|
|
||||||
// Tag: "some_tag",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
provider := Provider{
|
|
||||||
service: &mockService,
|
|
||||||
cache: map[string][]godo.DomainRecord{},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := provider.Update("example.com", "www", net.IPv4(221, 135, 170, 186))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
err = provider.Update("invalid.com", "www", net.IPv4(72, 82, 118, 186))
|
|
||||||
assert.Error(t, err)
|
|
||||||
|
|
||||||
mockService.edit_record_error = errors.New("Error")
|
|
||||||
|
|
||||||
err = provider.Update("example.com", "www", net.IPv4(221, 135, 170, 186))
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Provider interface {
|
|
||||||
Update(domain string, record string, ip net.IP) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProviderFactory func(map[string]any) (Provider, error)
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
package vultr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"dnsupdater/provider"
|
|
||||||
|
|
||||||
"github.com/vultr/govultr/v3"
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Provider struct {
|
|
||||||
service govultr.DomainRecordService
|
|
||||||
cache map[string][]govultr.DomainRecord
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(token string) Provider {
|
|
||||||
ctx := context.Background()
|
|
||||||
config := &oauth2.Config{}
|
|
||||||
ts := config.TokenSource(ctx, &oauth2.Token{AccessToken: token})
|
|
||||||
client := govultr.NewClient(oauth2.NewClient(ctx, ts))
|
|
||||||
|
|
||||||
return Provider{
|
|
||||||
service: client.DomainRecord,
|
|
||||||
cache: make(map[string][]govultr.DomainRecord),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Factory(args map[string]any) (provider.Provider, error) {
|
|
||||||
t, ok := args["token"]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("did not find token")
|
|
||||||
}
|
|
||||||
|
|
||||||
token, ok := t.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("token must be a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
return New(token), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Provider) fetch(domain string) ([]govultr.DomainRecord, error) {
|
|
||||||
records, ok := d.cache[domain]
|
|
||||||
if !ok {
|
|
||||||
fetchedRecords, _, _, err := d.service.List(context.Background(), domain, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rec := range fetchedRecords {
|
|
||||||
if rec.Type != "A" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
records = append(records, rec)
|
|
||||||
}
|
|
||||||
d.cache[domain] = records
|
|
||||||
}
|
|
||||||
return records, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Provider) find(domain, name string) (govultr.DomainRecord, error) {
|
|
||||||
records, err := d.fetch(domain)
|
|
||||||
if err != nil {
|
|
||||||
return govultr.DomainRecord{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rec := range records {
|
|
||||||
if rec.Name == name {
|
|
||||||
return rec, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return govultr.DomainRecord{}, errors.New("not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Provider) Update(domain string, name string, ip net.IP) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
record, err := d.find(domain, name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
recordIP := net.ParseIP(record.Data)
|
|
||||||
if recordIP == nil || !recordIP.Equal(ip) {
|
|
||||||
updateData := govultr.DomainRecordReq{
|
|
||||||
Data: ip.String(),
|
|
||||||
}
|
|
||||||
return d.service.Update(ctx, domain, record.ID, &updateData)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,241 +0,0 @@
|
||||||
package vultr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/vultr/govultr/v3"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestProvider_fetch(t *testing.T) {
|
|
||||||
expected := []govultr.DomainRecord{
|
|
||||||
{
|
|
||||||
ID: "656939ee-f942-4ce2-af1d-3bd68c764e96",
|
|
||||||
Type: "A",
|
|
||||||
Name: "sub1",
|
|
||||||
Data: "201.110.66.72",
|
|
||||||
Priority: 2,
|
|
||||||
TTL: 1800,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: "c80118f4-f04c-4ad2-8ec2-16eb15cc8aca",
|
|
||||||
Type: "A",
|
|
||||||
Name: "sub2",
|
|
||||||
Data: "242.124.218.187",
|
|
||||||
Priority: 1,
|
|
||||||
TTL: 1800,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
provider := Provider{
|
|
||||||
service: mock{
|
|
||||||
t: t,
|
|
||||||
ListReturn: map[string][]govultr.DomainRecord{
|
|
||||||
"example.com": expected,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
cache: make(map[string][]govultr.DomainRecord),
|
|
||||||
}
|
|
||||||
|
|
||||||
records, err := provider.fetch("example.com")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, expected, records)
|
|
||||||
|
|
||||||
// Fetch invalid
|
|
||||||
_, err = provider.fetch("noexists.com")
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProvider_fetch_caches_records(t *testing.T) {
|
|
||||||
example_com_records := []govultr.DomainRecord{
|
|
||||||
{
|
|
||||||
ID: "884edc0a-295c-418a-97ac-b7b67d85efc1",
|
|
||||||
Type: "A",
|
|
||||||
Name: "sub1",
|
|
||||||
Data: "107.218.197.189",
|
|
||||||
Priority: 2,
|
|
||||||
TTL: 1800,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: "43608e90-8917-45f9-8300-1a40b13886b7",
|
|
||||||
Type: "A",
|
|
||||||
Name: "sub2",
|
|
||||||
Data: "254.221.12.160",
|
|
||||||
Priority: 1,
|
|
||||||
TTL: 1800,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
another_com_records := []govultr.DomainRecord{
|
|
||||||
{
|
|
||||||
ID: "43608e90-8917-45f9-8300-1a40b13886b7",
|
|
||||||
Type: "A",
|
|
||||||
Name: "box",
|
|
||||||
Data: "108.151.98.62",
|
|
||||||
Priority: 2,
|
|
||||||
TTL: 1800,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: "43608e90-8917-45f9-8300-1a40b13886b7",
|
|
||||||
Type: "A",
|
|
||||||
Name: "ntp",
|
|
||||||
Data: "190.255.140.208",
|
|
||||||
Priority: 10,
|
|
||||||
TTL: 300,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
mockService := mock{
|
|
||||||
t: t,
|
|
||||||
ListReturn: map[string][]govultr.DomainRecord{
|
|
||||||
"example.com": example_com_records,
|
|
||||||
"another.com": another_com_records,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
provider := Provider{
|
|
||||||
service: mockService,
|
|
||||||
cache: make(map[string][]govultr.DomainRecord),
|
|
||||||
}
|
|
||||||
|
|
||||||
records, err := provider.fetch("example.com")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, example_com_records, records)
|
|
||||||
|
|
||||||
records, err = provider.fetch("another.com")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, another_com_records, records)
|
|
||||||
|
|
||||||
// Check cache, should be equal to the map in mock service.
|
|
||||||
assert.Equal(t, provider.cache, mockService.ListReturn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProvider_fetch_from_cache(t *testing.T) {
|
|
||||||
expected := []govultr.DomainRecord{
|
|
||||||
{
|
|
||||||
ID: "43608e90-8917-45f9-8300-1a40b13886b7",
|
|
||||||
Type: "A",
|
|
||||||
Name: "sub1",
|
|
||||||
Data: "42.170.152.94",
|
|
||||||
Priority: 10,
|
|
||||||
TTL: 1800,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
provider := Provider{
|
|
||||||
service: mock{t: t},
|
|
||||||
cache: map[string][]govultr.DomainRecord{
|
|
||||||
"example.com": expected,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
records, err := provider.fetch("example.com")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, expected, records)
|
|
||||||
|
|
||||||
_, err = provider.fetch("noexists.com")
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProvider_find(t *testing.T) {
|
|
||||||
expected := []govultr.DomainRecord{
|
|
||||||
{
|
|
||||||
ID: "7d53d1b4-8264-40f7-b379-536b5caff54c",
|
|
||||||
Type: "A",
|
|
||||||
Name: "sub1",
|
|
||||||
Data: "161.125.137.64",
|
|
||||||
Priority: 10,
|
|
||||||
TTL: 1800,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: "309f6dda-4aef-453d-ae05-71f8274cdd76",
|
|
||||||
Type: "A",
|
|
||||||
Name: "sub2",
|
|
||||||
Data: "154.63.46.159",
|
|
||||||
Priority: 5,
|
|
||||||
TTL: 1800,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
expected_cache := []govultr.DomainRecord{
|
|
||||||
{
|
|
||||||
ID: "2216c435-0405-49aa-96bc-1d177146ee4e",
|
|
||||||
Type: "A",
|
|
||||||
Name: "mail",
|
|
||||||
Data: "176.151.152.10",
|
|
||||||
Priority: 10,
|
|
||||||
TTL: 3600,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
provider := Provider{
|
|
||||||
service: mock{
|
|
||||||
t: t,
|
|
||||||
ListReturn: map[string][]govultr.DomainRecord{
|
|
||||||
"example.com": expected,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
cache: map[string][]govultr.DomainRecord{
|
|
||||||
"cached.com": expected_cache,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test fetch.
|
|
||||||
record, err := provider.find("example.com", "sub2")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, expected[1], record)
|
|
||||||
|
|
||||||
// Test cached record
|
|
||||||
record, err = provider.find("cached.com", "mail")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, expected_cache[0], record)
|
|
||||||
|
|
||||||
// Test not found (domain)
|
|
||||||
_, err = provider.find("noexists.com", "www")
|
|
||||||
assert.Error(t, err)
|
|
||||||
|
|
||||||
// Test not found (subdomain)
|
|
||||||
_, err = provider.find("cached.com", "nosub")
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProvider_Update(t *testing.T) {
|
|
||||||
expected := []govultr.DomainRecord{
|
|
||||||
{
|
|
||||||
ID: "6cabe6ba-1ea1-405d-b66d-cd56ecac45ce",
|
|
||||||
Type: "A",
|
|
||||||
Name: "www",
|
|
||||||
Data: "80.17.42.157",
|
|
||||||
Priority: 10,
|
|
||||||
TTL: 360,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
mockService := mock{
|
|
||||||
t: t,
|
|
||||||
ListReturn: map[string][]govultr.DomainRecord{
|
|
||||||
"example.com": expected,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
provider := Provider{
|
|
||||||
service: &mockService,
|
|
||||||
cache: map[string][]govultr.DomainRecord{},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := provider.Update("example.com", "www", net.IPv4(221, 135, 170, 186))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
err = provider.Update("invalid.com", "www", net.IPv4(72, 82, 118, 186))
|
|
||||||
assert.Error(t, err)
|
|
||||||
|
|
||||||
mockService.updateError = errors.New("Error")
|
|
||||||
|
|
||||||
err = provider.Update("example.com", "www", net.IPv4(221, 135, 170, 186))
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue