From 0c347312bd0808b376c73eef2a8a8c53a96fa3c9 Mon Sep 17 00:00:00 2001 From: Henrik Hautakoski Date: Sun, 12 Oct 2025 23:52:30 +0200 Subject: [PATCH] line ending fix --- .gitignore | 2 +- .gitlab-ci.yml | 44 +++--- Makefile | 20 +-- app/app.go | 122 ++++++++--------- app/config.go | 94 ++++++------- cmd/dnsupdater/main.go | 136 +++++++++---------- config.example.yml | 36 ++--- go.mod | 7 +- go.sum | 16 ++- http/get.go | 54 ++++---- ip/cache.go | 86 ++++++------ ip/cache_test.go | 152 ++++++++++----------- ip/interface.go | 92 ++++++------- ip/interface_test.go | 138 +++++++++---------- ip/internal/ip.go | 30 ++--- ip/internal/ip_test.go | 70 +++++----- ip/resolver/http/service.go | 92 ++++++------- ip/resolver/http/service_test.go | 170 +++++++++++------------ ip/resolver/jsonip.go | 40 +++--- ip/resolver/manager.go | 106 +++++++-------- ip/resolver/mock/service.go | 38 +++--- ip/resolver/resolver.go | 30 ++--- provider/digitalocean/mock_test.go | 210 ++++++++++++++--------------- provider/digitalocean/provider.go | 196 +++++++++++++-------------- provider/interface.go | 22 +-- provider/manager/manager.go | 96 ++++++------- 26 files changed, 1053 insertions(+), 1046 deletions(-) diff --git a/.gitignore b/.gitignore index 5598e8b..a2acf60 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -config.yml +config.yml build/ \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6c878c3..c5afa2b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,22 +1,22 @@ - -image: golang:1.19 - -stages: - - test - - build - -unit-test: - stage: test - script: - - go test -v ./... - -compile: - stage: build - script: - - mkdir -p build - - GOOS=linux GOARCH=amd64 go build -o build/dnsupdater-linux-amd64 cmd/dnsupdater/main.go - - GOOS=linux GOARCH=mips GOMIPS=softfloat go build -o build/dnsupdater-linux-mips cmd/dnsupdater/main.go - - GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -o build/dnsupdater-linux-mipsle cmd/dnsupdater/main.go - artifacts: - paths: - - build + +image: golang:1.19 + +stages: + - test + - build + +unit-test: + stage: test + script: + - go test -v ./... + +compile: + stage: build + script: + - mkdir -p build + - GOOS=linux GOARCH=amd64 go build -o build/dnsupdater-linux-amd64 cmd/dnsupdater/main.go + - GOOS=linux GOARCH=mips GOMIPS=softfloat go build -o build/dnsupdater-linux-mips cmd/dnsupdater/main.go + - GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -o build/dnsupdater-linux-mipsle cmd/dnsupdater/main.go + artifacts: + paths: + - build diff --git a/Makefile b/Makefile index faf5a10..85ecd14 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ -GO=go -GOLDFLAGS=-v -s -w -GOBUILDFLAGS=-v -p $(shell nproc) -ldflags="$(GOLDFLAGS)" - -.PHONY: build test - -build : - $(GO) build $(GOBUILDFLAGS) -o build/dnsupdater cmd/dnsupdater/main.go - -test : +GO=go +GOLDFLAGS=-v -s -w +GOBUILDFLAGS=-v -p $(shell nproc) -ldflags="$(GOLDFLAGS)" + +.PHONY: build test + +build : + $(GO) build $(GOBUILDFLAGS) -o build/dnsupdater cmd/dnsupdater/main.go + +test : $(GO) test -v ./... \ No newline at end of file diff --git a/app/app.go b/app/app.go index a136a4a..1fc5b19 100644 --- a/app/app.go +++ b/app/app.go @@ -1,61 +1,61 @@ -package app - -import ( - "context" - "fmt" - "net" - "time" - - "dnsupdater/provider/manager" - - "dnsupdater/ip" - "dnsupdater/ip/resolver" -) - -// Constant name for the virtual WAN interface -const WAN_IFACE = "wan" - -type App struct { - cache *ip.Cache - - cacheDefaultCallback ip.CacheDefaultCallback - - // Updater manager - ProviderManager *manager.Manager -} - -func makeCacheCallback(service resolver.Service) ip.CacheDefaultCallback { - return func(name string) (net.IP, error) { - if name == WAN_IFACE { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - defer cancel() - return service.Lookup(ctx) - } - return ip.GetInterfaceIP(name) - } -} - -func NewApp(config *Config) (*App, error) { - providerMgr := manager.New() - // providerMgr.Register("digitalocean", digitalocean.New(config.Services.DigitalOcean.Token)) - err := providerMgr.RegisterFromConfig(config.Providers) - if err != nil { - return nil, err - } - - service := resolver.Get(config.Services.IPLookup) - - if service == nil { - return nil, fmt.Errorf("Failed to load lookup service: %s", config.Services.IPLookup) - } - - return &App{ - ProviderManager: providerMgr, - cache: ip.NewCache(), - cacheDefaultCallback: makeCacheCallback(service), - }, nil -} - -func (a App) GetIP(iface_name string) (net.IP, error) { - return a.cache.GetWithDefault(iface_name, a.cacheDefaultCallback) -} +package app + +import ( + "context" + "fmt" + "net" + "time" + + "dnsupdater/provider/manager" + + "dnsupdater/ip" + "dnsupdater/ip/resolver" +) + +// Constant name for the virtual WAN interface +const WAN_IFACE = "wan" + +type App struct { + cache *ip.Cache + + cacheDefaultCallback ip.CacheDefaultCallback + + // Updater manager + ProviderManager *manager.Manager +} + +func makeCacheCallback(service resolver.Service) ip.CacheDefaultCallback { + return func(name string) (net.IP, error) { + if name == WAN_IFACE { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + return service.Lookup(ctx) + } + return ip.GetInterfaceIP(name) + } +} + +func NewApp(config *Config) (*App, error) { + providerMgr := manager.New() + // providerMgr.Register("digitalocean", digitalocean.New(config.Services.DigitalOcean.Token)) + err := providerMgr.RegisterFromConfig(config.Providers) + if err != nil { + return nil, err + } + + service := resolver.Get(config.Services.IPLookup) + + if service == nil { + return nil, fmt.Errorf("Failed to load lookup service: %s", config.Services.IPLookup) + } + + return &App{ + ProviderManager: providerMgr, + cache: ip.NewCache(), + cacheDefaultCallback: makeCacheCallback(service), + }, nil +} + +func (a App) GetIP(iface_name string) (net.IP, error) { + return a.cache.GetWithDefault(iface_name, a.cacheDefaultCallback) +} diff --git a/app/config.go b/app/config.go index 7346c95..4515be4 100644 --- a/app/config.go +++ b/app/config.go @@ -1,47 +1,47 @@ -package app - -import ( - "os" - - "gopkg.in/yaml.v3" -) - -type ( - DomainRecords map[string]string - Domain map[string]DomainRecords -) - -type DigitalOceanService struct { - Token string `yaml:"token"` - Domains map[string]DomainRecords `yaml:"domains"` -} - -type Providers struct { - Token string `yaml:"token"` - Domains map[string]DomainRecords `yaml:"domains"` -} - -type Services struct { - IPLookup string `yaml:"IPLookup"` - // DigitalOcean DigitalOceanService `yaml:"digitalocean"` -} - -type Config struct { - Services Services `yaml:"services"` - Providers map[string]map[string]interface{} - Updates map[string]Domain -} - -func LoadConfig(filename string) (*Config, error) { - cfg := Config{ - Services: Services{ - IPLookup: "ipecho", - }, - } - - data, err := os.ReadFile(filename) - if err == nil { - err = yaml.Unmarshal(data, &cfg) - } - return &cfg, err -} +package app + +import ( + "os" + + "gopkg.in/yaml.v3" +) + +type ( + DomainRecords map[string]string + Domain map[string]DomainRecords +) + +type DigitalOceanService struct { + Token string `yaml:"token"` + Domains map[string]DomainRecords `yaml:"domains"` +} + +type Providers struct { + Token string `yaml:"token"` + Domains map[string]DomainRecords `yaml:"domains"` +} + +type Services struct { + IPLookup string `yaml:"IPLookup"` + // DigitalOcean DigitalOceanService `yaml:"digitalocean"` +} + +type Config struct { + Services Services `yaml:"services"` + Providers map[string]map[string]interface{} + Updates map[string]Domain +} + +func LoadConfig(filename string) (*Config, error) { + cfg := Config{ + Services: Services{ + IPLookup: "ipecho", + }, + } + + data, err := os.ReadFile(filename) + if err == nil { + err = yaml.Unmarshal(data, &cfg) + } + return &cfg, err +} diff --git a/cmd/dnsupdater/main.go b/cmd/dnsupdater/main.go index 9490d83..e84b4bd 100644 --- a/cmd/dnsupdater/main.go +++ b/cmd/dnsupdater/main.go @@ -1,68 +1,68 @@ -package main - -import ( - "flag" - "os" - "time" - - "dnsupdater/app" - - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" -) - -func main() { - configFile := flag.String("config", "./config.yml", "configuration file") - - flag.Parse() - - log.Logger = log.Output(zerolog.ConsoleWriter{ - Out: os.Stderr, - TimeFormat: time.RFC3339, - }) - - config, err := app.LoadConfig(*configFile) - if err != nil { - log.Fatal().Err(err).Str("file", *configFile).Msg("Failed to load config") - } - - app, err := app.NewApp(config) - if err != nil { - log.Fatal().Err(err).Msg("Failed to initialize application") - } - - for service_name, domains := range config.Updates { - - log.Info().Str("service", service_name).Msg("Begin update for service") - - // Get service - service := app.ProviderManager.Get(service_name) - - for domain, records := range domains { - for name, data := range records { - - logger := log.With(). - Str("service", service_name). - Str("domain", domain). - Str("record", name). - Str("interface", data). - Logger() - - ip, err := app.GetIP(data) - if err != nil { - logger.Error().Err(err).Msg("Failed to fetch ip") - continue - } - - logger = logger.With().IPAddr("ip", ip).Logger() - - err = service.Update(domain, name, ip) - if err != nil { - logger.Error().Err(err).Msg("Failed to update record") - } else { - logger.Info().Msg("Record updated") - } - } - } - } -} +package main + +import ( + "flag" + "os" + "time" + + "dnsupdater/app" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func main() { + configFile := flag.String("config", "./config.yml", "configuration file") + + flag.Parse() + + log.Logger = log.Output(zerolog.ConsoleWriter{ + Out: os.Stderr, + TimeFormat: time.RFC3339, + }) + + config, err := app.LoadConfig(*configFile) + if err != nil { + log.Fatal().Err(err).Str("file", *configFile).Msg("Failed to load config") + } + + app, err := app.NewApp(config) + if err != nil { + log.Fatal().Err(err).Msg("Failed to initialize application") + } + + for service_name, domains := range config.Updates { + + log.Info().Str("service", service_name).Msg("Begin update for service") + + // Get service + service := app.ProviderManager.Get(service_name) + + for domain, records := range domains { + for name, data := range records { + + logger := log.With(). + Str("service", service_name). + Str("domain", domain). + Str("record", name). + Str("interface", data). + Logger() + + ip, err := app.GetIP(data) + if err != nil { + logger.Error().Err(err).Msg("Failed to fetch ip") + continue + } + + logger = logger.With().IPAddr("ip", ip).Logger() + + err = service.Update(domain, name, ip) + if err != nil { + logger.Error().Err(err).Msg("Failed to update record") + } else { + logger.Info().Msg("Record updated") + } + } + } + } +} diff --git a/config.example.yml b/config.example.yml index 093ff0b..cb6243d 100644 --- a/config.example.yml +++ b/config.example.yml @@ -1,18 +1,18 @@ - -services: - IPLookup: ipecho - -providers: - digitalocean: - token: xxxx - -updates: - digitalocean: - domain1.com: - www: wan - box: 10.140.14.2 - domain2.com: - www: wan - mail: wan - static: 84.24.254.21 - + +services: + IPLookup: ipecho + +providers: + digitalocean: + token: xxxx + +updates: + digitalocean: + domain1.com: + www: wan + box: 10.140.14.2 + domain2.com: + www: wan + mail: wan + static: 84.24.254.21 + diff --git a/go.mod b/go.mod index 7710100..389fb00 100644 --- a/go.mod +++ b/go.mod @@ -12,13 +12,14 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/net v0.9.0 // indirect golang.org/x/oauth2 v0.7.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/sys v0.20.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.30.0 // indirect diff --git a/go.sum b/go.sum index f4f201a..e11432e 100644 --- a/go.sum +++ b/go.sum @@ -11,13 +11,17 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -40,8 +44,10 @@ golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= diff --git a/http/get.go b/http/get.go index ef16922..3da6058 100644 --- a/http/get.go +++ b/http/get.go @@ -1,27 +1,27 @@ -package http - -import ( - "context" - "fmt" - "net/http" -) - -// Perform a HTTP Get request. -func Get(ctx context.Context, url string, headers http.Header) (*http.Response, error) { - req, err := http.NewRequestWithContext(ctx, "GET", url, nil) - if err != nil { - return nil, err - } - - req.Header = headers - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - - if resp.StatusCode >= 400 { - return nil, fmt.Errorf("HTTP Response: %s", resp.Status) - } - return resp, nil -} +package http + +import ( + "context" + "fmt" + "net/http" +) + +// Perform a HTTP Get request. +func Get(ctx context.Context, url string, headers http.Header) (*http.Response, error) { + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, err + } + + req.Header = headers + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + + if resp.StatusCode >= 400 { + return nil, fmt.Errorf("HTTP Response: %s", resp.Status) + } + return resp, nil +} diff --git a/ip/cache.go b/ip/cache.go index 16e1ea8..fc5b5f6 100644 --- a/ip/cache.go +++ b/ip/cache.go @@ -1,43 +1,43 @@ -package ip - -import ( - "errors" - "net" -) - -type CacheDefaultCallback func(name string) (net.IP, error) - -type Cache struct { - items map[string]net.IP -} - -func NewCache() *Cache { - return &Cache{ - items: make(map[string]net.IP), - } -} - -func (c Cache) Get(name string) (net.IP, error) { - // Return cached entry. - if cached, ok := c.items[name]; ok { - return cached, nil - } - return nil, errors.New("key did not exist") -} - -func (c Cache) GetWithDefault(name string, callback CacheDefaultCallback) (net.IP, error) { - // Return cached entry. - if cached, ok := c.items[name]; ok { - return cached, nil - } - - ip, err := callback(name) - if err == nil { - c.Set(name, ip) - } - return ip, err -} - -func (c *Cache) Set(name string, ip net.IP) { - c.items[name] = ip -} +package ip + +import ( + "errors" + "net" +) + +type CacheDefaultCallback func(name string) (net.IP, error) + +type Cache struct { + items map[string]net.IP +} + +func NewCache() *Cache { + return &Cache{ + items: make(map[string]net.IP), + } +} + +func (c Cache) Get(name string) (net.IP, error) { + // Return cached entry. + if cached, ok := c.items[name]; ok { + return cached, nil + } + return nil, errors.New("key did not exist") +} + +func (c Cache) GetWithDefault(name string, callback CacheDefaultCallback) (net.IP, error) { + // Return cached entry. + if cached, ok := c.items[name]; ok { + return cached, nil + } + + ip, err := callback(name) + if err == nil { + c.Set(name, ip) + } + return ip, err +} + +func (c *Cache) Set(name string, ip net.IP) { + c.items[name] = ip +} diff --git a/ip/cache_test.go b/ip/cache_test.go index 4bececa..a9bd157 100644 --- a/ip/cache_test.go +++ b/ip/cache_test.go @@ -1,76 +1,76 @@ -package ip - -import ( - "errors" - "net" - "reflect" - "testing" - - "github.com/stretchr/testify/assert" -) - -func defaultCallback(t *testing.T, expected_name string, ip net.IP, err error) CacheDefaultCallback { - return func(name string) (net.IP, error) { - assert.Equal(t, expected_name, name) - return ip, err - } -} - -func dontCallDefaultCallback(t *testing.T) CacheDefaultCallback { - return func(name string) (net.IP, error) { - t.Error("Should not have been called") - return nil, nil - } -} - -func TestCache_Get(t *testing.T) { - tests := []struct { - name string - c *Cache - iface string - want net.IP - wantErr bool - }{ - {"Exists in cache", &Cache{items: map[string]net.IP{"eth0": net.IPv4(10, 4, 0, 1)}}, "eth0", net.IPv4(10, 4, 0, 1), false}, - {"Did not exist in cache", &Cache{items: map[string]net.IP{}}, "eth0", nil, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := tt.c.Get(tt.iface) - if (err != nil) != tt.wantErr { - t.Errorf("Cache.Get() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Cache.Get() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestCache_GetWithDefault(t *testing.T) { - tests := []struct { - name string - c *Cache - def CacheDefaultCallback - iface string - want net.IP - wantErr bool - }{ - {"Exists in cache", &Cache{items: map[string]net.IP{"eth0": net.IPv4(10, 4, 0, 1)}}, dontCallDefaultCallback(t), "eth0", net.IPv4(10, 4, 0, 1), false}, - {"Did not exists in cache", NewCache(), defaultCallback(t, "eth1", net.IPv4(192, 172, 44, 25), nil), "eth1", net.IPv4(192, 172, 44, 25), false}, - {"Callback returns error", NewCache(), defaultCallback(t, "eth1", nil, errors.New("some error")), "eth1", nil, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := tt.c.GetWithDefault(tt.iface, tt.def) - if (err != nil) != tt.wantErr { - t.Errorf("Cache.Get() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Cache.Get() = %v, want %v", got, tt.want) - } - }) - } -} +package ip + +import ( + "errors" + "net" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" +) + +func defaultCallback(t *testing.T, expected_name string, ip net.IP, err error) CacheDefaultCallback { + return func(name string) (net.IP, error) { + assert.Equal(t, expected_name, name) + return ip, err + } +} + +func dontCallDefaultCallback(t *testing.T) CacheDefaultCallback { + return func(name string) (net.IP, error) { + t.Error("Should not have been called") + return nil, nil + } +} + +func TestCache_Get(t *testing.T) { + tests := []struct { + name string + c *Cache + iface string + want net.IP + wantErr bool + }{ + {"Exists in cache", &Cache{items: map[string]net.IP{"eth0": net.IPv4(10, 4, 0, 1)}}, "eth0", net.IPv4(10, 4, 0, 1), false}, + {"Did not exist in cache", &Cache{items: map[string]net.IP{}}, "eth0", nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.c.Get(tt.iface) + if (err != nil) != tt.wantErr { + t.Errorf("Cache.Get() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Cache.Get() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCache_GetWithDefault(t *testing.T) { + tests := []struct { + name string + c *Cache + def CacheDefaultCallback + iface string + want net.IP + wantErr bool + }{ + {"Exists in cache", &Cache{items: map[string]net.IP{"eth0": net.IPv4(10, 4, 0, 1)}}, dontCallDefaultCallback(t), "eth0", net.IPv4(10, 4, 0, 1), false}, + {"Did not exists in cache", NewCache(), defaultCallback(t, "eth1", net.IPv4(192, 172, 44, 25), nil), "eth1", net.IPv4(192, 172, 44, 25), false}, + {"Callback returns error", NewCache(), defaultCallback(t, "eth1", nil, errors.New("some error")), "eth1", nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.c.GetWithDefault(tt.iface, tt.def) + if (err != nil) != tt.wantErr { + t.Errorf("Cache.Get() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Cache.Get() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/ip/interface.go b/ip/interface.go index dd32b17..d12332a 100644 --- a/ip/interface.go +++ b/ip/interface.go @@ -1,46 +1,46 @@ -package ip - -import ( - "errors" - "net" -) - -func GetInterfaceIP(iface_name string) (net.IP, error) { - ip := net.IP{} - iface, err := net.InterfaceByName(iface_name) - if err != nil { - return ip, err - } - - addrs, err := iface.Addrs() - if err != nil { - return ip, err - } - - return GetPublicIp(addrs) -} - -func GetPublicIp(list []net.Addr) (net.IP, error) { - for _, addr := range list { - ip, err := AddrToIP(addr) - if err == nil && !ip.IsPrivate() { - return ip, nil - } - } - - return nil, errors.New("no public ip found on interface") -} - -func AddrToIP(addr net.Addr) (net.IP, error) { - switch v := addr.(type) { - case *net.IPNet: - return v.IP, nil - case *net.IPAddr: - return v.IP, nil - case *net.UDPAddr: - return v.IP, nil - case *net.TCPAddr: - return v.IP, nil - } - return nil, errors.New("could not find ip") -} +package ip + +import ( + "errors" + "net" +) + +func GetInterfaceIP(iface_name string) (net.IP, error) { + ip := net.IP{} + iface, err := net.InterfaceByName(iface_name) + if err != nil { + return ip, err + } + + addrs, err := iface.Addrs() + if err != nil { + return ip, err + } + + return GetPublicIp(addrs) +} + +func GetPublicIp(list []net.Addr) (net.IP, error) { + for _, addr := range list { + ip, err := AddrToIP(addr) + if err == nil && !ip.IsPrivate() { + return ip, nil + } + } + + return nil, errors.New("no public ip found on interface") +} + +func AddrToIP(addr net.Addr) (net.IP, error) { + switch v := addr.(type) { + case *net.IPNet: + return v.IP, nil + case *net.IPAddr: + return v.IP, nil + case *net.UDPAddr: + return v.IP, nil + case *net.TCPAddr: + return v.IP, nil + } + return nil, errors.New("could not find ip") +} diff --git a/ip/interface_test.go b/ip/interface_test.go index 436eb96..771656b 100644 --- a/ip/interface_test.go +++ b/ip/interface_test.go @@ -1,69 +1,69 @@ -package ip - -import ( - "net" - "reflect" - "testing" -) - -func TestGetPublicIp(t *testing.T) { - tests := []struct { - name string - list []string - want string - wantErr bool - }{ - {"empty", []string{}, "", true}, - {"find", []string{"99.140.96.132"}, "99.140.96.132", false}, - {"findfirst", []string{"23.114.115.197", "251.78.128.148"}, "23.114.115.197", false}, - {"dontfindprivate", []string{"192.168.0.22", "88.12.32.44"}, "88.12.32.44", false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - list := []net.Addr{} - for _, item := range tt.list { - list = append(list, &net.IPAddr{IP: net.ParseIP(item)}) - } - - want := net.ParseIP(tt.want) - - got, err := GetPublicIp(list) - if (err != nil) != tt.wantErr { - t.Errorf("GetPublicIp() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, want) { - t.Errorf("GetPublicIp() = %v, want %v", got, want) - } - }) - } -} - -func TestAddrToIP(t *testing.T) { - tests := []struct { - name string - addr net.Addr - want net.IP - wantErr bool - }{ - {"IPNet", &net.IPNet{IP: net.IPv4(177, 171, 44, 1)}, net.IPv4(177, 171, 44, 1), false}, - {"IPAddr", &net.IPAddr{IP: net.IPv4(240, 23, 119, 171)}, net.IPv4(240, 23, 119, 171), false}, - {"TCPAddr", &net.TCPAddr{IP: net.IPv4(139, 231, 35, 221)}, net.IPv4(139, 231, 35, 221), false}, - {"UDPAddr", &net.UDPAddr{IP: net.IPv4(167, 147, 140, 119)}, net.IPv4(167, 147, 140, 119), false}, - {"UnixAddr", &net.UnixAddr{}, nil, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := AddrToIP(tt.addr) - - if (err != nil) != tt.wantErr { - t.Errorf("AddrToIP() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("AddrToIP() = %v, want %v", got, tt.want) - } - }) - } -} +package ip + +import ( + "net" + "reflect" + "testing" +) + +func TestGetPublicIp(t *testing.T) { + tests := []struct { + name string + list []string + want string + wantErr bool + }{ + {"empty", []string{}, "", true}, + {"find", []string{"99.140.96.132"}, "99.140.96.132", false}, + {"findfirst", []string{"23.114.115.197", "251.78.128.148"}, "23.114.115.197", false}, + {"dontfindprivate", []string{"192.168.0.22", "88.12.32.44"}, "88.12.32.44", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + list := []net.Addr{} + for _, item := range tt.list { + list = append(list, &net.IPAddr{IP: net.ParseIP(item)}) + } + + want := net.ParseIP(tt.want) + + got, err := GetPublicIp(list) + if (err != nil) != tt.wantErr { + t.Errorf("GetPublicIp() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, want) { + t.Errorf("GetPublicIp() = %v, want %v", got, want) + } + }) + } +} + +func TestAddrToIP(t *testing.T) { + tests := []struct { + name string + addr net.Addr + want net.IP + wantErr bool + }{ + {"IPNet", &net.IPNet{IP: net.IPv4(177, 171, 44, 1)}, net.IPv4(177, 171, 44, 1), false}, + {"IPAddr", &net.IPAddr{IP: net.IPv4(240, 23, 119, 171)}, net.IPv4(240, 23, 119, 171), false}, + {"TCPAddr", &net.TCPAddr{IP: net.IPv4(139, 231, 35, 221)}, net.IPv4(139, 231, 35, 221), false}, + {"UDPAddr", &net.UDPAddr{IP: net.IPv4(167, 147, 140, 119)}, net.IPv4(167, 147, 140, 119), false}, + {"UnixAddr", &net.UnixAddr{}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := AddrToIP(tt.addr) + + if (err != nil) != tt.wantErr { + t.Errorf("AddrToIP() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("AddrToIP() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/ip/internal/ip.go b/ip/internal/ip.go index 45ab9be..1d7f0c5 100644 --- a/ip/internal/ip.go +++ b/ip/internal/ip.go @@ -1,15 +1,15 @@ -package internal - -import "net" - -func ParseIP(s string) (net.IP, error) { - var err error = nil - ip := net.ParseIP(s) - if ip == nil { - err = &net.ParseError{ - Type: "IP address", - Text: s, - } - } - return ip, err -} +package internal + +import "net" + +func ParseIP(s string) (net.IP, error) { + var err error = nil + ip := net.ParseIP(s) + if ip == nil { + err = &net.ParseError{ + Type: "IP address", + Text: s, + } + } + return ip, err +} diff --git a/ip/internal/ip_test.go b/ip/internal/ip_test.go index 29d0c2e..a65cb72 100644 --- a/ip/internal/ip_test.go +++ b/ip/internal/ip_test.go @@ -1,35 +1,35 @@ -package internal - -import ( - "net" - "reflect" - "testing" -) - -func TestParseIP(t *testing.T) { - tests := []struct { - name string - input string - want net.IP - wantErr bool - }{ - {"localhost", "127.0.0.1", net.IPv4(127, 0, 0, 1), false}, - {"Private#1", "10.4.0.11", net.IPv4(10, 4, 0, 11), false}, - {"Private#2", "192.168.1.12", net.IPv4(192, 168, 1, 12), false}, - {"Public#1", "82.249.10.254", net.IPv4(82, 249, 10, 254), false}, - {"Public#2", "57.167.50.222", net.IPv4(57, 167, 50, 222), false}, - {"Invalid", "xx", nil, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := ParseIP(tt.input) - if (err != nil) != tt.wantErr { - t.Errorf("ParseIP() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ParseIP() = %v, want %v", got, tt.want) - } - }) - } -} +package internal + +import ( + "net" + "reflect" + "testing" +) + +func TestParseIP(t *testing.T) { + tests := []struct { + name string + input string + want net.IP + wantErr bool + }{ + {"localhost", "127.0.0.1", net.IPv4(127, 0, 0, 1), false}, + {"Private#1", "10.4.0.11", net.IPv4(10, 4, 0, 11), false}, + {"Private#2", "192.168.1.12", net.IPv4(192, 168, 1, 12), false}, + {"Public#1", "82.249.10.254", net.IPv4(82, 249, 10, 254), false}, + {"Public#2", "57.167.50.222", net.IPv4(57, 167, 50, 222), false}, + {"Invalid", "xx", nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseIP(tt.input) + if (err != nil) != tt.wantErr { + t.Errorf("ParseIP() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ParseIP() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/ip/resolver/http/service.go b/ip/resolver/http/service.go index 2ee5193..d8771c5 100644 --- a/ip/resolver/http/service.go +++ b/ip/resolver/http/service.go @@ -1,46 +1,46 @@ -package http - -import ( - "context" - "io" - "net" - "net/http" - "strings" - - httputils "dnsupdater/http" - "dnsupdater/ip/internal" -) - -type Decoder func(io.Reader) ([]byte, error) - -type Service struct { - ServiceName string - Url string - Headers http.Header - Decoder Decoder -} - -func (s Service) Name() string { - return s.ServiceName -} - -func (s Service) Lookup(ctx context.Context) (net.IP, error) { - resp, err := httputils.Get(ctx, s.Url, s.Headers) - if err != nil { - return nil, err - } - - if s.Decoder == nil { - s.Decoder = io.ReadAll - } - - body, err := s.Decoder(resp.Body) - if err != nil { - return nil, err - } - - // Trim spaces and stuff. - ip_str := strings.TrimSpace(string(body)) - - return internal.ParseIP(ip_str) -} +package http + +import ( + "context" + "io" + "net" + "net/http" + "strings" + + httputils "dnsupdater/http" + "dnsupdater/ip/internal" +) + +type Decoder func(io.Reader) ([]byte, error) + +type Service struct { + ServiceName string + Url string + Headers http.Header + Decoder Decoder +} + +func (s Service) Name() string { + return s.ServiceName +} + +func (s Service) Lookup(ctx context.Context) (net.IP, error) { + resp, err := httputils.Get(ctx, s.Url, s.Headers) + if err != nil { + return nil, err + } + + if s.Decoder == nil { + s.Decoder = io.ReadAll + } + + body, err := s.Decoder(resp.Body) + if err != nil { + return nil, err + } + + // Trim spaces and stuff. + ip_str := strings.TrimSpace(string(body)) + + return internal.ParseIP(ip_str) +} diff --git a/ip/resolver/http/service_test.go b/ip/resolver/http/service_test.go index 3fd98c7..d09ce61 100644 --- a/ip/resolver/http/service_test.go +++ b/ip/resolver/http/service_test.go @@ -1,85 +1,85 @@ -package http - -import ( - "context" - "net" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestService_Name(t *testing.T) { - s := Service{ServiceName: "my_service"} - - assert.Equal(t, "my_service", s.Name()) -} - -func TestService_Lookup(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, err := w.Write([]byte("255.240.85.2")) - assert.NoError(t, err) - })) - defer server.Close() - - s := Service{Url: server.URL} - - ip, err := s.Lookup(context.Background()) - assert.NoError(t, err) - - assert.Equal(t, net.IPv4(255, 240, 85, 2), ip) -} - -func TestService_Lookup_WithHeaders(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, "application/json", r.Header.Get("Content-Type")) - - _, err := w.Write([]byte("125.74.233.13")) - assert.NoError(t, err) - })) - defer server.Close() - - s := Service{ - Url: server.URL, - Headers: http.Header{ - "Content-Type": []string{"application/json"}, - }, - } - - ip, err := s.Lookup(context.Background()) - assert.NoError(t, err) - - assert.Equal(t, net.IPv4(125, 74, 233, 13), ip) -} - -func TestService_Lookup_HTTPError(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(404) - })) - defer server.Close() - - s := Service{ - Url: server.URL, - } - - ip, err := s.Lookup(context.Background()) - assert.EqualError(t, err, "HTTP Response: 404 Not Found") - assert.Nil(t, ip) -} - -func TestService_Lookup_ParseError(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, err := w.Write([]byte("random_string")) - assert.NoError(t, err) - })) - defer server.Close() - - s := Service{ - Url: server.URL, - } - - ip, err := s.Lookup(context.Background()) - assert.EqualError(t, err, "invalid IP address: random_string") - assert.Nil(t, ip) -} +package http + +import ( + "context" + "net" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestService_Name(t *testing.T) { + s := Service{ServiceName: "my_service"} + + assert.Equal(t, "my_service", s.Name()) +} + +func TestService_Lookup(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte("255.240.85.2")) + assert.NoError(t, err) + })) + defer server.Close() + + s := Service{Url: server.URL} + + ip, err := s.Lookup(context.Background()) + assert.NoError(t, err) + + assert.Equal(t, net.IPv4(255, 240, 85, 2), ip) +} + +func TestService_Lookup_WithHeaders(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + + _, err := w.Write([]byte("125.74.233.13")) + assert.NoError(t, err) + })) + defer server.Close() + + s := Service{ + Url: server.URL, + Headers: http.Header{ + "Content-Type": []string{"application/json"}, + }, + } + + ip, err := s.Lookup(context.Background()) + assert.NoError(t, err) + + assert.Equal(t, net.IPv4(125, 74, 233, 13), ip) +} + +func TestService_Lookup_HTTPError(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(404) + })) + defer server.Close() + + s := Service{ + Url: server.URL, + } + + ip, err := s.Lookup(context.Background()) + assert.EqualError(t, err, "HTTP Response: 404 Not Found") + assert.Nil(t, ip) +} + +func TestService_Lookup_ParseError(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte("random_string")) + assert.NoError(t, err) + })) + defer server.Close() + + s := Service{ + Url: server.URL, + } + + ip, err := s.Lookup(context.Background()) + assert.EqualError(t, err, "invalid IP address: random_string") + assert.Nil(t, ip) +} diff --git a/ip/resolver/jsonip.go b/ip/resolver/jsonip.go index b3d80ae..4e1f87e 100644 --- a/ip/resolver/jsonip.go +++ b/ip/resolver/jsonip.go @@ -1,20 +1,20 @@ -package resolver - -import ( - "encoding/json" - "io" -) - -func JsonipDecoder(r io.Reader) ([]byte, error) { - var v struct { - Ip string `json:"ip"` - Location string `json:"geo-ip"` - Help string `json:"API Help"` - } - var val []byte - err := json.NewDecoder(r).Decode(&v) - if err == nil { - val = []byte(v.Ip) - } - return val, err -} +package resolver + +import ( + "encoding/json" + "io" +) + +func JsonipDecoder(r io.Reader) ([]byte, error) { + var v struct { + Ip string `json:"ip"` + Location string `json:"geo-ip"` + Help string `json:"API Help"` + } + var val []byte + err := json.NewDecoder(r).Decode(&v) + if err == nil { + val = []byte(v.Ip) + } + return val, err +} diff --git a/ip/resolver/manager.go b/ip/resolver/manager.go index 3e32136..136bc8f 100644 --- a/ip/resolver/manager.go +++ b/ip/resolver/manager.go @@ -1,53 +1,53 @@ -package resolver - -import ( - "net/http" - - httpres "dnsupdater/ip/resolver/http" -) - -var services []Service - -func Provide(service Service) { - services = append(services, service) -} - -func Get(name string) Service { - for _, service := range services { - if service.Name() == name { - return service - } - } - return nil -} - -func init() { - Provide(&httpres.Service{ - ServiceName: "jsonip", - Url: "https://jsonip.com", - Decoder: JsonipDecoder, - }) - - Provide(&httpres.Service{ - ServiceName: "ifconfig.me", - Url: "https://ifconfig.me/ip", - }) - - Provide(&httpres.Service{ - ServiceName: "ip.me", - Url: "https://ip.me", - Headers: http.Header{ - "User-Agent": []string{"curl"}, - }, - }) - - Provide(&httpres.Service{ - ServiceName: "ipecho", - Url: "http://ipecho.net/plain", - }) - - Provide(&httpres.Service{ - ServiceName: "icanhazip", - Url: "https://icanhazip.com", - }) -} +package resolver + +import ( + "net/http" + + httpres "dnsupdater/ip/resolver/http" +) + +var services []Service + +func Provide(service Service) { + services = append(services, service) +} + +func Get(name string) Service { + for _, service := range services { + if service.Name() == name { + return service + } + } + return nil +} + +func init() { + Provide(&httpres.Service{ + ServiceName: "jsonip", + Url: "https://jsonip.com", + Decoder: JsonipDecoder, + }) + + Provide(&httpres.Service{ + ServiceName: "ifconfig.me", + Url: "https://ifconfig.me/ip", + }) + + Provide(&httpres.Service{ + ServiceName: "ip.me", + Url: "https://ip.me", + Headers: http.Header{ + "User-Agent": []string{"curl"}, + }, + }) + + Provide(&httpres.Service{ + ServiceName: "ipecho", + Url: "http://ipecho.net/plain", + }) + + Provide(&httpres.Service{ + ServiceName: "icanhazip", + Url: "https://icanhazip.com", + }) +} diff --git a/ip/resolver/mock/service.go b/ip/resolver/mock/service.go index fe72248..464336e 100644 --- a/ip/resolver/mock/service.go +++ b/ip/resolver/mock/service.go @@ -1,19 +1,19 @@ -package mock - -import ( - "context" - "net" -) - -type Service struct { - IP net.IP - Error error -} - -func (s Service) Name() string { - return "mock" -} - -func (s Service) Lookup(ctx context.Context) (net.IP, error) { - return s.IP, s.Error -} +package mock + +import ( + "context" + "net" +) + +type Service struct { + IP net.IP + Error error +} + +func (s Service) Name() string { + return "mock" +} + +func (s Service) Lookup(ctx context.Context) (net.IP, error) { + return s.IP, s.Error +} diff --git a/ip/resolver/resolver.go b/ip/resolver/resolver.go index 094418f..c01d167 100644 --- a/ip/resolver/resolver.go +++ b/ip/resolver/resolver.go @@ -1,15 +1,15 @@ -package resolver - -import ( - "context" - "net" -) - -// Interface that IP Lookup Services must implement. -type Service interface { - // Get the name of the serivce - Name() string - - // Lookup the public ip. - Lookup(ctx context.Context) (net.IP, error) -} +package resolver + +import ( + "context" + "net" +) + +// Interface that IP Lookup Services must implement. +type Service interface { + // Get the name of the serivce + Name() string + + // Lookup the public ip. + Lookup(ctx context.Context) (net.IP, error) +} diff --git a/provider/digitalocean/mock_test.go b/provider/digitalocean/mock_test.go index 1f9ab0b..561ff0e 100644 --- a/provider/digitalocean/mock_test.go +++ b/provider/digitalocean/mock_test.go @@ -1,105 +1,105 @@ -package digitalocean - -import ( - "context" - "errors" - "testing" - - "github.com/digitalocean/godo" - "github.com/stretchr/testify/assert" -) - -type mock struct { - t *testing.T - - records_by_type map[string][]godo.DomainRecord - - edit_record_request *godo.DomainRecordEditRequest - edit_record_error error -} - -func (m mock) List(context.Context, *godo.ListOptions) ([]godo.Domain, *godo.Response, error) { - m.t.Error("List called when it should not have been") - return nil, nil, nil -} - -func (m mock) Get(context.Context, string) (*godo.Domain, *godo.Response, error) { - m.t.Error("Get called when it should not have been") - return nil, nil, nil -} - -func (m mock) Create(context.Context, *godo.DomainCreateRequest) (*godo.Domain, *godo.Response, error) { - m.t.Error("Create called when it should not have been") - return nil, nil, nil -} - -func (m mock) Delete(context.Context, string) (*godo.Response, error) { - m.t.Error("Delete called when it should not have been") - return nil, nil -} - -func (m mock) Records(context.Context, string, *godo.ListOptions) ([]godo.DomainRecord, *godo.Response, error) { - m.t.Error("Records called when it should not have been") - return nil, nil, nil -} - -func (m mock) RecordsByType(_ context.Context, name string, t string, opt *godo.ListOptions) ([]godo.DomainRecord, *godo.Response, error) { - var err error - - // Only care about "A" records - assert.Equal(m.t, "A", t) - - r, ok := m.records_by_type[name] - if !ok { - err = errors.New("Record not found") - } - return r, nil, err -} - -func (m mock) RecordsByName(context.Context, string, string, *godo.ListOptions) ([]godo.DomainRecord, *godo.Response, error) { - m.t.Error("RecordsByName called when it should not have been") - return nil, nil, nil -} - -func (m mock) RecordsByTypeAndName(context.Context, string, string, string, *godo.ListOptions) ([]godo.DomainRecord, *godo.Response, error) { - m.t.Error("RecordsByTypeAndName called when it should not have been") - return nil, nil, nil -} - -func (m mock) Record(context.Context, string, int) (*godo.DomainRecord, *godo.Response, error) { - m.t.Error("Record called when it should not have been") - return nil, nil, nil -} - -func (m mock) DeleteRecord(context.Context, string, int) (*godo.Response, error) { - m.t.Error("DeleteRecord called when it should not have been") - return nil, nil -} - -func (m mock) EditRecord(_ context.Context, domain string, id int, req *godo.DomainRecordEditRequest) (*godo.DomainRecord, *godo.Response, error) { - if m.edit_record_request == nil { - m.t.Error("EditRecord called with empty request") - } - - assert.Equal(m.t, m.edit_record_request, req) - - record := godo.DomainRecord{ - ID: id, - Type: req.Type, - Name: req.Name, - Data: req.Data, - Priority: req.Priority, - Port: req.Port, - TTL: req.TTL, - Weight: req.Weight, - Flags: req.Flags, - Tag: req.Tag, - } - - return &record, nil, m.edit_record_error -} - -func (m mock) CreateRecord(context.Context, string, *godo.DomainRecordEditRequest) (*godo.DomainRecord, *godo.Response, error) { - m.t.Error("CreateRecord called when it should not have been") - return nil, nil, nil -} +package digitalocean + +import ( + "context" + "errors" + "testing" + + "github.com/digitalocean/godo" + "github.com/stretchr/testify/assert" +) + +type mock struct { + t *testing.T + + records_by_type map[string][]godo.DomainRecord + + edit_record_request *godo.DomainRecordEditRequest + edit_record_error error +} + +func (m mock) List(context.Context, *godo.ListOptions) ([]godo.Domain, *godo.Response, error) { + m.t.Error("List called when it should not have been") + return nil, nil, nil +} + +func (m mock) Get(context.Context, string) (*godo.Domain, *godo.Response, error) { + m.t.Error("Get called when it should not have been") + return nil, nil, nil +} + +func (m mock) Create(context.Context, *godo.DomainCreateRequest) (*godo.Domain, *godo.Response, error) { + m.t.Error("Create called when it should not have been") + return nil, nil, nil +} + +func (m mock) Delete(context.Context, string) (*godo.Response, error) { + m.t.Error("Delete called when it should not have been") + return nil, nil +} + +func (m mock) Records(context.Context, string, *godo.ListOptions) ([]godo.DomainRecord, *godo.Response, error) { + m.t.Error("Records called when it should not have been") + return nil, nil, nil +} + +func (m mock) RecordsByType(_ context.Context, name string, t string, opt *godo.ListOptions) ([]godo.DomainRecord, *godo.Response, error) { + var err error + + // Only care about "A" records + assert.Equal(m.t, "A", t) + + r, ok := m.records_by_type[name] + if !ok { + err = errors.New("Record not found") + } + return r, nil, err +} + +func (m mock) RecordsByName(context.Context, string, string, *godo.ListOptions) ([]godo.DomainRecord, *godo.Response, error) { + m.t.Error("RecordsByName called when it should not have been") + return nil, nil, nil +} + +func (m mock) RecordsByTypeAndName(context.Context, string, string, string, *godo.ListOptions) ([]godo.DomainRecord, *godo.Response, error) { + m.t.Error("RecordsByTypeAndName called when it should not have been") + return nil, nil, nil +} + +func (m mock) Record(context.Context, string, int) (*godo.DomainRecord, *godo.Response, error) { + m.t.Error("Record called when it should not have been") + return nil, nil, nil +} + +func (m mock) DeleteRecord(context.Context, string, int) (*godo.Response, error) { + m.t.Error("DeleteRecord called when it should not have been") + return nil, nil +} + +func (m mock) EditRecord(_ context.Context, domain string, id int, req *godo.DomainRecordEditRequest) (*godo.DomainRecord, *godo.Response, error) { + if m.edit_record_request == nil { + m.t.Error("EditRecord called with empty request") + } + + assert.Equal(m.t, m.edit_record_request, req) + + record := godo.DomainRecord{ + ID: id, + Type: req.Type, + Name: req.Name, + Data: req.Data, + Priority: req.Priority, + Port: req.Port, + TTL: req.TTL, + Weight: req.Weight, + Flags: req.Flags, + Tag: req.Tag, + } + + return &record, nil, m.edit_record_error +} + +func (m mock) CreateRecord(context.Context, string, *godo.DomainRecordEditRequest) (*godo.DomainRecord, *godo.Response, error) { + m.t.Error("CreateRecord called when it should not have been") + return nil, nil, nil +} diff --git a/provider/digitalocean/provider.go b/provider/digitalocean/provider.go index cb4acf2..f31e2c4 100644 --- a/provider/digitalocean/provider.go +++ b/provider/digitalocean/provider.go @@ -1,98 +1,98 @@ -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]interface{}) (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 -} +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]interface{}) (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 +} diff --git a/provider/interface.go b/provider/interface.go index 1d7927a..79331b5 100644 --- a/provider/interface.go +++ b/provider/interface.go @@ -1,11 +1,11 @@ -package provider - -import ( - "net" -) - -type Provider interface { - Update(domain string, record string, ip net.IP) error -} - -type ProviderFactory func(map[string]interface{}) (Provider, error) +package provider + +import ( + "net" +) + +type Provider interface { + Update(domain string, record string, ip net.IP) error +} + +type ProviderFactory func(map[string]interface{}) (Provider, error) diff --git a/provider/manager/manager.go b/provider/manager/manager.go index 9f54f79..48778da 100644 --- a/provider/manager/manager.go +++ b/provider/manager/manager.go @@ -1,48 +1,48 @@ -package manager - -import ( - "fmt" - - "dnsupdater/provider" - "dnsupdater/provider/digitalocean" -) - -var factories = map[string]provider.ProviderFactory{ - "digitalocean": digitalocean.Factory, -} - -type Manager struct { - services map[string]provider.Provider -} - -func New() *Manager { - return &Manager{ - services: make(map[string]provider.Provider), - } -} - -func (m Manager) Get(name string) provider.Provider { - if service, ok := m.services[name]; ok { - return service - } - return nil -} - -func (m Manager) RegisterFromConfig(providers map[string]map[string]interface{}) error { - for name, args := range providers { - if factory, ok := factories[name]; ok { - - provider, err := factory(args) - if err != nil { - return fmt.Errorf("could not create provider '%s': %v", name, err) - } - - m.Register(name, provider) - } - } - return nil -} - -func (m Manager) Register(name string, provider provider.Provider) { - m.services[name] = provider -} +package manager + +import ( + "fmt" + + "dnsupdater/provider" + "dnsupdater/provider/digitalocean" +) + +var factories = map[string]provider.ProviderFactory{ + "digitalocean": digitalocean.Factory, +} + +type Manager struct { + services map[string]provider.Provider +} + +func New() *Manager { + return &Manager{ + services: make(map[string]provider.Provider), + } +} + +func (m Manager) Get(name string) provider.Provider { + if service, ok := m.services[name]; ok { + return service + } + return nil +} + +func (m Manager) RegisterFromConfig(providers map[string]map[string]interface{}) error { + for name, args := range providers { + if factory, ok := factories[name]; ok { + + provider, err := factory(args) + if err != nil { + return fmt.Errorf("could not create provider '%s': %v", name, err) + } + + m.Register(name, provider) + } + } + return nil +} + +func (m Manager) Register(name string, provider provider.Provider) { + m.services[name] = provider +}