add token based providers
This commit is contained in:
parent
21b55d8a0a
commit
63b9c1a8d8
9
main.go
9
main.go
|
@ -12,13 +12,6 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
ErrSbsLen = errors.New("subdomains len is 0")
|
|
||||||
ErrInvalidProvider = errors.New("invalid provider name")
|
|
||||||
ErrWatchersConfigLen = errors.New("watcher configuration needs at least one [watcherd] and [providerd] configuration")
|
|
||||||
ErrWatcherCreationLen = errors.New("no valid watchers were created. Checkout [watchers] configuration and its [providers] configuration")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Start create a new instance of ddns-client
|
// Start create a new instance of ddns-client
|
||||||
func Start(logger logrus.FieldLogger, config utils.ClientConfig) error {
|
func Start(logger logrus.FieldLogger, config utils.ClientConfig) error {
|
||||||
log := logger.WithFields(logrus.Fields{
|
log := logger.WithFields(logrus.Fields{
|
||||||
|
@ -38,7 +31,7 @@ func Start(logger logrus.FieldLogger, config utils.ClientConfig) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ws) == 0 {
|
if len(ws) == 0 {
|
||||||
return ErrWatcherCreationLen
|
return errors.New("no valid watchers were created. Checkout [watchers] configuration and its [providers] configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
sigc := make(chan os.Signal, 1)
|
sigc := make(chan os.Signal, 1)
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -10,9 +11,10 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provider is the default interface for all providers
|
|
||||||
type Provider interface {
|
type Provider interface {
|
||||||
UpdateSubdomains(ip string) error
|
UpdateSubdomains(ip string) error
|
||||||
|
updateSubdomain(subdomain, ip string) error
|
||||||
|
retrieveSubdomainIP(addr string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type provider struct {
|
type provider struct {
|
||||||
|
@ -22,6 +24,7 @@ type provider struct {
|
||||||
url string
|
url string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewProvider creates a new instance of the `Provider` interface
|
||||||
func NewProvider(logger logrus.FieldLogger, config utils.Config, url, name string) (Provider, error) {
|
func NewProvider(logger logrus.FieldLogger, config utils.Config, url, name string) (Provider, error) {
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
return nil, utils.ErrNilLogger
|
return nil, utils.ErrNilLogger
|
||||||
|
@ -45,9 +48,11 @@ func NewProvider(logger logrus.FieldLogger, config utils.Config, url, name strin
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateSubdomains will watch for every defined subdomains if they need an update.
|
||||||
|
// If so, it'll trigger an update
|
||||||
func (p *provider) UpdateSubdomains(srvIP string) error {
|
func (p *provider) UpdateSubdomains(srvIP string) error {
|
||||||
for _, sb := range p.config.Subdomains {
|
for _, sb := range p.config.Subdomains {
|
||||||
ip, err := utils.RetrieveSubdomainIP(sb)
|
ip, err := p.retrieveSubdomainIP(sb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -63,14 +68,11 @@ func (p *provider) UpdateSubdomains(srvIP string) error {
|
||||||
"subdomain": sb,
|
"subdomain": sb,
|
||||||
}).Infoln("IP addresses doesn't match. Updating subdomain's ip...")
|
}).Infoln("IP addresses doesn't match. Updating subdomain's ip...")
|
||||||
if err := p.updateSubdomain(sb, srvIP); err != nil {
|
if err := p.updateSubdomain(sb, srvIP); err != nil {
|
||||||
if err != utils.ErrReadBody && err != utils.ErrWrongStatusCode {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.logger.WithError(err).WithFields(logrus.Fields{
|
p.logger.WithError(err).WithFields(logrus.Fields{
|
||||||
"component": "UpdateSubdomains",
|
"component": "UpdateSubdomains",
|
||||||
"subdomain": sb,
|
"subdomain": sb,
|
||||||
"new-ip": srvIP,
|
"new-ip": srvIP,
|
||||||
}).Warnln("failed to update subdomain ip")
|
}).Errorln("failed to update subdomain ip")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,8 +80,13 @@ func (p *provider) UpdateSubdomains(srvIP string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *provider) updateSubdomain(subdomain, ip string) error {
|
func (p *provider) updateSubdomain(subdomain, ip string) error {
|
||||||
|
tokenBased := p.config.Token != "" && (p.config.Username == "" && p.config.Password == "")
|
||||||
|
|
||||||
newURL := strings.ReplaceAll(p.url, "SUBDOMAIN", subdomain)
|
newURL := strings.ReplaceAll(p.url, "SUBDOMAIN", subdomain)
|
||||||
newURL = strings.ReplaceAll(newURL, "NEWIP", ip)
|
newURL = strings.ReplaceAll(newURL, "NEWIP", ip)
|
||||||
|
if tokenBased {
|
||||||
|
newURL = strings.ReplaceAll(newURL, "TOKEN", p.config.Token)
|
||||||
|
}
|
||||||
logger := p.logger.WithFields(logrus.Fields{
|
logger := p.logger.WithFields(logrus.Fields{
|
||||||
"component": "UpdateIP",
|
"component": "UpdateIP",
|
||||||
"updated-url": newURL,
|
"updated-url": newURL,
|
||||||
|
@ -91,23 +98,25 @@ func (p *provider) updateSubdomain(subdomain, ip string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return utils.ErrCreateNewRequest
|
return utils.ErrCreateNewRequest
|
||||||
}
|
}
|
||||||
req.SetBasicAuth(p.config.Username, p.config.Password)
|
if !tokenBased {
|
||||||
|
req.SetBasicAuth(p.config.Username, p.config.Password)
|
||||||
|
}
|
||||||
|
|
||||||
logger.Debugln("calling DDNS provider for subdomain update")
|
logger.Debugln("calling DDNS provider for subdomain update")
|
||||||
c := new(http.Client)
|
c := new(http.Client)
|
||||||
resp, err := c.Do(req)
|
resp, err := c.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return utils.ErrUpdateRequest
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.ContentLength != 0 {
|
if resp.ContentLength != 0 {
|
||||||
b, err := ioutil.ReadAll(resp.Body)
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return utils.ErrReadBody
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(string(b), "good "+ip) && !strings.Contains(string(b), "nochg "+ip) {
|
if err := p.checkResponse(b, tokenBased, ip); err != nil {
|
||||||
return errors.New("failed to update subdomain ip. Error: " + string(b))
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,3 +126,44 @@ func (p *provider) updateSubdomain(subdomain, ip string) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *provider) retrieveSubdomainIP(addr string) (string, error) {
|
||||||
|
ips, err := net.LookupIP(addr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ips) != 1 {
|
||||||
|
return "", utils.ErrIpLenght
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := ips[0].String()
|
||||||
|
if strings.Contains(ip, ":") {
|
||||||
|
ip, _, err = net.SplitHostPort(ip)
|
||||||
|
if err != nil {
|
||||||
|
return "", utils.ErrSplitAddr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *provider) checkResponse(body []byte, tokenBased bool, ip string) error {
|
||||||
|
var invalidResponse error
|
||||||
|
|
||||||
|
if tokenBased {
|
||||||
|
if !strings.Contains(string(body), "OK") {
|
||||||
|
if strings.Contains(string(body), "KO") {
|
||||||
|
invalidResponse = fmt.Errorf("invalid body response.\n Body response: %v", string(body))
|
||||||
|
} else {
|
||||||
|
invalidResponse = fmt.Errorf("unknown body response. Please fill a issue if you think this is an error.\n Body response: %v", string(body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !strings.Contains(string(body), "good "+ip) && !strings.Contains(string(body), "nochg "+ip) {
|
||||||
|
invalidResponse = fmt.Errorf("invalid body response.\n Body response: %v", string(body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return invalidResponse
|
||||||
|
}
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
package utils
|
|
||||||
|
|
||||||
type ClientConfig struct {
|
|
||||||
Logger Logger `mapstructure:"logger"`
|
|
||||||
Watchers []Watcher `mapstructure:"watchers"`
|
|
||||||
UpdateTime int `mapstructure:"update-time,omitempty"`
|
|
||||||
PendingDnsPropagation int `mapstructure:"pending-dns-propagation,omitempty"`
|
|
||||||
WebIP string `mapstructure:"web-ip,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Logger struct {
|
|
||||||
Level string `mapstructure:"level"`
|
|
||||||
DisableTimestamp bool `mapstructure:"disable-timestamp,omitempty"`
|
|
||||||
DisableColor bool `mapstructure:"disable-color,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Watcher struct {
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
URL string `yaml:"url,omitempty"`
|
|
||||||
Config []Config `yaml:"config"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Username string `yaml:"username"`
|
|
||||||
Password string `yaml:"password"`
|
|
||||||
Subdomains []string `yaml:"subdomains"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var DefaultURLs = map[string]string{
|
|
||||||
"ovh": "http://www.ovh.com/nic/update?system=dyndns&hostname=SUBDOMAIN&myip=NEWIP",
|
|
||||||
"google": "https://domains.google.com/nic/update?hostname=SUBDOMAIN&myip=NEWIP",
|
|
||||||
"webIP": "http://dynamicdns.park-your-domain.com/getip",
|
|
||||||
}
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// * Errors
|
// ** ERRORS ** //
|
||||||
var (
|
var (
|
||||||
// ErrNilLogger is thrown when the parameter logger is nil
|
// ErrNilLogger is thrown when the parameter logger is nil
|
||||||
ErrNilLogger = errors.New("logger is mandatory")
|
ErrNilLogger = errors.New("logger is mandatory")
|
||||||
|
@ -18,14 +18,10 @@ var (
|
||||||
ErrSplitAddr = errors.New("can't split subdomain remote IP address")
|
ErrSplitAddr = errors.New("can't split subdomain remote IP address")
|
||||||
// ErrCreateNewRequest is thrown when http request creation failed
|
// ErrCreateNewRequest is thrown when http request creation failed
|
||||||
ErrCreateNewRequest = errors.New("can't create http request")
|
ErrCreateNewRequest = errors.New("can't create http request")
|
||||||
// ErrUpdateRequest is thrown when the update request failed
|
|
||||||
ErrUpdateRequest = errors.New("failed to set new IP address")
|
|
||||||
// ErrInvalidURL is thrown when user does not provide a URL and it does not exist in default urls
|
// ErrInvalidURL is thrown when user does not provide a URL and it does not exist in default urls
|
||||||
ErrInvalidURL = errors.New("no url was provided")
|
ErrInvalidURL = errors.New("no url was provided")
|
||||||
// ErrInvalidName is thrown when provider name was not provided
|
// ErrInvalidName is thrown when provider name was not provided
|
||||||
ErrInvalidName = errors.New("no provider name was provided")
|
ErrInvalidName = errors.New("no provider name was provided")
|
||||||
// ErrReadBody is thrown when body response can't be parsed
|
|
||||||
ErrReadBody = errors.New("failed to read response body")
|
|
||||||
// ErrNilWatcher is thrown when no watcher config was provided
|
// ErrNilWatcher is thrown when no watcher config was provided
|
||||||
ErrNilWatcher = errors.New("watcher is mandatory")
|
ErrNilWatcher = errors.New("watcher is mandatory")
|
||||||
// ErrIpLength is thrown when subdomain no or multiples remote IP address
|
// ErrIpLength is thrown when subdomain no or multiples remote IP address
|
||||||
|
@ -33,3 +29,37 @@ var (
|
||||||
// ErrNilConfig is thrown when an empty config is provided
|
// ErrNilConfig is thrown when an empty config is provided
|
||||||
ErrNilConfig = errors.New("config is mandatory")
|
ErrNilConfig = errors.New("config is mandatory")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ** CONFIGURATION ** //
|
||||||
|
type ClientConfig struct {
|
||||||
|
Logger Logger `mapstructure:"logger"`
|
||||||
|
Watchers []Watcher `mapstructure:"watchers"`
|
||||||
|
UpdateTime int `mapstructure:"update-time,omitempty"`
|
||||||
|
WebIP string `mapstructure:"web-ip,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
Level string `mapstructure:"level"`
|
||||||
|
DisableTimestamp bool `mapstructure:"disable-timestamp,omitempty"`
|
||||||
|
DisableColor bool `mapstructure:"disable-color,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Watcher struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
URL string `yaml:"url,omitempty"`
|
||||||
|
Config []Config `yaml:"config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Username string `yaml:"username,omitempty"`
|
||||||
|
Password string `yaml:"password,omitempty"`
|
||||||
|
Token string `yaml:"password,omitempty"`
|
||||||
|
Subdomains []string `yaml:"subdomains"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var DefaultURLs = map[string]string{
|
||||||
|
"ovh": "http://www.ovh.com/nic/update?system=dyndns&hostname=SUBDOMAIN&myip=NEWIP",
|
||||||
|
"google": "https://domains.google.com/nic/update?hostname=SUBDOMAIN&myip=NEWIP",
|
||||||
|
"duckdns": "https://duckdns.org/update/SUBDOMAIN/TOKEN[/NEWIP",
|
||||||
|
"webIP": "http://dynamicdns.park-your-domain.com/getip",
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
@ -55,43 +50,3 @@ func SetupLogger(logger *logrus.Logger) {
|
||||||
DisableTimestamp: timestamp,
|
DisableTimestamp: timestamp,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// RetrieveServerIP will use the defined web-ip service to get the server public address
|
|
||||||
func RetrieveServerIP(webIP string) (string, error) {
|
|
||||||
resp, err := http.Get(webIP)
|
|
||||||
if err != nil {
|
|
||||||
return "", ErrGetServerIP
|
|
||||||
}
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
return "", ErrWrongStatusCode
|
|
||||||
}
|
|
||||||
|
|
||||||
d, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return "", ErrParseHTTPBody
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(d), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RetrieveSubdomainIP will retrieve the subdomain IP
|
|
||||||
func RetrieveSubdomainIP(addr string) (string, error) {
|
|
||||||
ips, err := net.LookupIP(addr)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ips) != 1 {
|
|
||||||
return "", ErrIpLenght
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := ips[0].String()
|
|
||||||
if strings.Contains(ip, ":") {
|
|
||||||
ip, _, err = net.SplitHostPort(ip)
|
|
||||||
if err != nil {
|
|
||||||
return "", ErrSplitAddr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ip, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package watcher
|
package watcher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/datahearth/ddnsclient/pkg/provider"
|
"github.com/datahearth/ddnsclient/pkg/provider"
|
||||||
|
@ -10,6 +12,8 @@ import (
|
||||||
|
|
||||||
type Watcher interface {
|
type Watcher interface {
|
||||||
Run(*time.Ticker, chan struct{}, chan error)
|
Run(*time.Ticker, chan struct{}, chan error)
|
||||||
|
runDDNSCheck() error
|
||||||
|
retrieveServerIP() (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type watcher struct {
|
type watcher struct {
|
||||||
|
@ -20,7 +24,7 @@ type watcher struct {
|
||||||
providerName string
|
providerName string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWatcher creates a watcher a given provider config
|
// NewWatcher creates a new instance of the `Watcher` interface
|
||||||
func NewWatcher(logger logrus.FieldLogger, w *utils.Watcher, webIP string) (Watcher, error) {
|
func NewWatcher(logger logrus.FieldLogger, w *utils.Watcher, webIP string) (Watcher, error) {
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
return nil, utils.ErrNilLogger
|
return nil, utils.ErrNilLogger
|
||||||
|
@ -51,6 +55,7 @@ func NewWatcher(logger logrus.FieldLogger, w *utils.Watcher, webIP string) (Watc
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run will trigger a subdomain update every X seconds (defined in config `update-time`)
|
||||||
func (w *watcher) Run(t *time.Ticker, chClose chan struct{}, chErr chan error) {
|
func (w *watcher) Run(t *time.Ticker, chClose chan struct{}, chErr chan error) {
|
||||||
logger := w.logger.WithField("component", "Run")
|
logger := w.logger.WithField("component", "Run")
|
||||||
|
|
||||||
|
@ -81,7 +86,7 @@ func (w *watcher) runDDNSCheck() error {
|
||||||
logger.Infof("Starting [%s] DDNS check...\n", w.providerName)
|
logger.Infof("Starting [%s] DDNS check...\n", w.providerName)
|
||||||
|
|
||||||
logger.Debugln("Checking server IP...")
|
logger.Debugln("Checking server IP...")
|
||||||
srvIP, err := utils.RetrieveServerIP(w.webIP)
|
srvIP, err := w.retrieveServerIP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -95,3 +100,20 @@ func (w *watcher) runDDNSCheck() error {
|
||||||
logger.Infof("[%s] DDNS check finished\n", w.providerName)
|
logger.Infof("[%s] DDNS check finished\n", w.providerName)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *watcher) retrieveServerIP() (string, error) {
|
||||||
|
resp, err := http.Get(w.webIP)
|
||||||
|
if err != nil {
|
||||||
|
return "", utils.ErrGetServerIP
|
||||||
|
}
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return "", utils.ErrWrongStatusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", utils.ErrParseHTTPBody
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(d), nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue