diff --git a/cmd/main.go b/cmd/main.go index c25dfc0..488926d 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -22,12 +22,13 @@ var ( }, } logger = logrus.StandardLogger() - config ddnsclient.ClientConfig + config utils.ClientConfig ) func init() { viper.BindEnv("CONFIG_PATH") viper.SetConfigType("yaml") + if conf := viper.GetString("CONFIG_PATH"); conf == "" { viper.SetConfigFile("ddnsclient.yaml") } else { diff --git a/config.go b/config.go deleted file mode 100644 index a4cfbc8..0000000 --- a/config.go +++ /dev/null @@ -1,31 +0,0 @@ -package ddnsclient - -import ( - "github.com/datahearth/ddnsclient/pkg/providers/google" - "github.com/datahearth/ddnsclient/pkg/providers/ovh" -) - -type ClientConfig struct { - Logger Logger `mapstructure:"logger"` - Providers Providers `mapstructure:"providers"` - Watchers WatcherConfig `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 Providers struct { - Ovh ovh.OvhConfig `mapstructure:"ovh,omitempty"` - Google google.GoogleConfig `mapstructure:"google,omitempty"` -} - -type WatcherConfig struct { - Ovh []string `yaml:"ovh,omitempty"` - Google []string `yaml:"google,omitempty"` -} diff --git a/main.go b/main.go index d26036f..7313c6a 100644 --- a/main.go +++ b/main.go @@ -4,14 +4,10 @@ import ( "errors" "os" "os/signal" - "reflect" - "strings" "syscall" "time" - "github.com/datahearth/ddnsclient/pkg/providers" - "github.com/datahearth/ddnsclient/pkg/providers/google" - "github.com/datahearth/ddnsclient/pkg/providers/ovh" + "github.com/datahearth/ddnsclient/pkg/utils" "github.com/datahearth/ddnsclient/pkg/watcher" "github.com/sirupsen/logrus" ) @@ -24,25 +20,15 @@ var ( ) // Start create a new instance of ddns-client -func Start(logger logrus.FieldLogger, config ClientConfig) error { +func Start(logger logrus.FieldLogger, config utils.ClientConfig) error { log := logger.WithFields(logrus.Fields{ "pkg": "ddnsclient", "component": "root", }) - fields := reflect.ValueOf(config.Watchers) - ws := []watcher.Watcher{} - - // * check providers and watchers config - // todo: invalid condition but while still exit in the next step. To be corrected - if fields.NumField() == 0 || reflect.ValueOf(config.Providers).NumField() == 0 { - return ErrWatchersConfigLen - } - - for i := 0; i < fields.NumField(); i++ { - providerName := strings.ToLower(fields.Type().Field(i).Name) - - w, err := CreateWatcher(providerName, config.WebIP, logger, config.Watchers, config.Providers, config.PendingDnsPropagation) + ws := make([]watcher.Watcher, 0, len(config.Watchers)) + for _, cw := range config.Watchers { + w, err := watcher.NewWatcher(logger, &cw, config.WebIP) if err != nil { logger.Warnf("Provider error: %v. Skipping...\n", err.Error()) continue @@ -51,24 +37,20 @@ func Start(logger logrus.FieldLogger, config ClientConfig) error { ws = append(ws, w) } - // * check for valid created watchers if len(ws) == 0 { return ErrWatcherCreationLen } - // * create signal watcher sigc := make(chan os.Signal, 1) signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) defer close(sigc) - // * create close and error channel chClose := make(chan struct{}) chErr := make(chan error) defer close(chClose) defer close(chErr) logger.Infoln("Start watching periodically for changes!") - // * run every created watchers in goroutines for _, w := range ws { tickTime := config.UpdateTime if tickTime == 0 { @@ -79,7 +61,6 @@ func Start(logger logrus.FieldLogger, config ClientConfig) error { go w.Run(t, chClose, chErr) } - // * listening for errors and exit signal for { select { case err := <-chErr: @@ -92,43 +73,3 @@ func Start(logger logrus.FieldLogger, config ClientConfig) error { } } } - -func CreateWatcher(provider, webIP string, logger logrus.FieldLogger, wc WatcherConfig, ps Providers, pendingDnsPropagation int) (watcher.Watcher, error) { - var sbs []string - var p providers.Provider - var err error - - // * check for implemented providers - switch provider { - case "ovh": - logger.Debugln("create OVH provider") - p, err = ovh.NewOVH(logger, &ps.Ovh) - if err != nil { - return nil, err - } - sbs = wc.Ovh - - case "google": - logger.Debugln("create GOOGLE provider") - p, err = google.NewGoogle(logger, &ps.Google) - if err != nil { - return nil, err - } - sbs = wc.Google - - default: - return nil, ErrInvalidProvider - } - - if len(sbs) == 0 { - return nil, ErrSbsLen - } - - // * create provider's watcher - w, err := watcher.NewWatcher(logger, p, sbs, webIP, provider, pendingDnsPropagation) - if err != nil { - return nil, err - } - - return w, nil -} diff --git a/pkg/provider/main.go b/pkg/provider/main.go new file mode 100644 index 0000000..33067f8 --- /dev/null +++ b/pkg/provider/main.go @@ -0,0 +1,119 @@ +package provider + +import ( + "errors" + "io/ioutil" + "net/http" + "strings" + + "github.com/datahearth/ddnsclient/pkg/utils" + "github.com/sirupsen/logrus" +) + +// Provider is the default interface for all providers +type Provider interface { + UpdateSubdomains(ip string) error +} + +type provider struct { + logger logrus.FieldLogger + config utils.Config + name string + url string +} + +func NewProvider(logger logrus.FieldLogger, config utils.Config, url, name string) (Provider, error) { + if logger == nil { + return nil, utils.ErrNilLogger + } + if name == "" { + return nil, utils.ErrInvalidName + } + if url == "" { + if utils.DefaultURLs[name] == "" { + return nil, utils.ErrInvalidURL + } + url = utils.DefaultURLs[name] + } + logger = logger.WithField("pkg", "providers") + + return &provider{ + config: config, + logger: logger, + name: name, + url: url, + }, nil +} + +func (p *provider) UpdateSubdomains(srvIP string) error { + for _, sb := range p.config.Subdomains { + ip, err := utils.RetrieveSubdomainIP(sb) + if err != nil { + return err + } + + if ip == srvIP { + continue + } + + p.logger.WithFields(logrus.Fields{ + "component": "UpdateSubdomains", + "server-ip": srvIP, + "subdomain-address": ip, + "subdomain": sb, + }).Infoln("IP addresses doesn't match. Updating subdomain's ip...") + if err := p.updateSubdomain(sb, srvIP); err != nil { + if err != utils.ErrReadBody && err != utils.ErrWrongStatusCode { + return err + } + p.logger.WithError(err).WithFields(logrus.Fields{ + "component": "UpdateSubdomains", + "subdomain": sb, + "new-ip": srvIP, + }).Warnln("failed to update subdomain ip") + } + } + + return nil +} + +func (p *provider) updateSubdomain(subdomain, ip string) error { + newURL := strings.ReplaceAll(p.url, "SUBDOMAIN", subdomain) + newURL = strings.ReplaceAll(newURL, "NEWIP", ip) + logger := p.logger.WithFields(logrus.Fields{ + "component": "UpdateIP", + "updated-url": newURL, + "subdomain": subdomain, + "new-ip": ip, + }) + + req, err := http.NewRequest("GET", newURL, nil) + if err != nil { + return utils.ErrCreateNewRequest + } + req.SetBasicAuth(p.config.Username, p.config.Password) + + logger.Debugln("calling DDNS provider for subdomain update") + c := new(http.Client) + resp, err := c.Do(req) + if err != nil { + return utils.ErrUpdateRequest + } + + if resp.ContentLength != 0 { + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return utils.ErrReadBody + } + + if !strings.Contains(string(b), "good "+ip) && !strings.Contains(string(b), "nochg "+ip) { + return errors.New("failed to update subdomain ip. Error: " + string(b)) + } + } + + if resp.StatusCode != 200 { + return utils.ErrWrongStatusCode + } + + return nil +} diff --git a/pkg/providers/google/main.go b/pkg/providers/google/main.go deleted file mode 100644 index 926caf1..0000000 --- a/pkg/providers/google/main.go +++ /dev/null @@ -1,99 +0,0 @@ -package google - -import ( - "errors" - "io/ioutil" - "net/http" - "strings" - - "github.com/datahearth/ddnsclient/pkg/providers" - "github.com/datahearth/ddnsclient/pkg/utils" - "github.com/sirupsen/logrus" -) - -var ( - // ErrNilGoogleConfig is thrown when GOOGLE configuration is empty - ErrNilGoogleConfig = errors.New("GOOGLE config is mandatory") - // ErrInvalidConfig is thrown when no username and password are provided and URL doesn't contains them - ErrInvalidConfig = errors.New("username and password are required if url doesn't contains them") - // ErrReadBody is thrown when reader failed to read response body - ErrReadBody = errors.New("failed to read response body") -) - -// GoogleConfig is the struct for the yaml configuration file -type GoogleConfig struct { - URL string `mapstructure:"url"` - Username string `mapstructure:"username,omitempty"` - Password string `mapstructure:"password,omitempty"` -} - -type google struct { - config *GoogleConfig - logger logrus.FieldLogger -} - -// NewGoogle returns a new instance of the GOOGLE provider -func NewGoogle(logger logrus.FieldLogger, googleConfig *GoogleConfig) (providers.Provider, error) { - if googleConfig == nil { - return nil, ErrNilGoogleConfig - } - if logger == nil { - return nil, utils.ErrNilLogger - } - if (googleConfig.Username == "" && googleConfig.Password == "") && !strings.Contains(googleConfig.URL, "@") { - return nil, ErrInvalidConfig - } - - logger = logger.WithField("pkg", "provider-google") - - return &google{ - config: googleConfig, - logger: logger, - }, nil -} - -// UpdateIP updates the subdomain A record -func (g *google) UpdateIP(subdomain, ip string) error { - newURL := strings.ReplaceAll(g.config.URL, "SUBDOMAIN", subdomain) - newURL = strings.ReplaceAll(newURL, "NEWIP", ip) - logger := g.logger.WithFields(logrus.Fields{ - "component": "update-ip", - "ovh-update-url": newURL, - "subdomain": subdomain, - "new-ip": ip, - }) - - // * create POST request - req, err := http.NewRequest("POST", newURL, nil) - if err != nil { - return utils.ErrCreateNewRequest - } - // * use basic auth if config is set - if g.config.Username != "" && g.config.Password != "" { - logger.Debugln("username and password passed in config. Use basic auth") - req.SetBasicAuth(g.config.Username, g.config.Password) - } - - // * perform POST request - logger.Debugln("calling Google DynDNS to update subdomain IP") - c := new(http.Client) - resp, err := c.Do(req) - if err != nil { - return utils.ErrUpdateRequest - } - - // * read response body - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - return ErrReadBody - } - - // * check for error response - // doc: https://support.google.com/domains/answer/6147083?hl=en#zippy=%2Cusing-the-api-to-update-your-dynamic-dns-record - // todo: check why the hell do I need to use () for conditions here !!!! - if (strings.Contains(string(b), "good "+ip) != true) || (strings.Contains(string(b), "nochg "+ip) != false) { - return errors.New("failed to update subdomain ip. Google error: " + string(b)) - } - - return nil -} diff --git a/pkg/providers/main.go b/pkg/providers/main.go deleted file mode 100644 index 7b54769..0000000 --- a/pkg/providers/main.go +++ /dev/null @@ -1,6 +0,0 @@ -package providers - -// Provider is the default interface for all providers -type Provider interface { - UpdateIP(subdomain, ip string) error -} diff --git a/pkg/providers/ovh/main.go b/pkg/providers/ovh/main.go deleted file mode 100644 index e1c2c78..0000000 --- a/pkg/providers/ovh/main.go +++ /dev/null @@ -1,76 +0,0 @@ -package ovh - -import ( - "errors" - "net/http" - "strings" - - "github.com/datahearth/ddnsclient/pkg/providers" - "github.com/datahearth/ddnsclient/pkg/utils" - "github.com/sirupsen/logrus" -) - -// ErrNilOvhConfig is thrown when OVH configuration is empty -var ErrNilOvhConfig = errors.New("OVH config is mandatory") - -type OvhConfig struct { - URL string `mapstructure:"url,omitempty"` - Username string `mapstructure:"username"` - Password string `mapstructure:"password"` -} - -type ovh struct { - config *OvhConfig - logger logrus.FieldLogger -} - -// NewOVH returns a new instance of the OVH provider -func NewOVH(logger logrus.FieldLogger, ovhConfig *OvhConfig) (providers.Provider, error) { - if ovhConfig == nil { - return nil, ErrNilOvhConfig - } - if logger == nil { - return nil, utils.ErrNilLogger - } - if ovhConfig.URL == "" { - ovhConfig.URL = "http://www.ovh.com/nic/update?system=dyndns&hostname=SUBDOMAIN&myip=NEWIP" - } - - logger = logger.WithField("pkg", "provider-ovh") - - return &ovh{ - config: ovhConfig, - logger: logger, - }, nil -} - -func (ovh *ovh) UpdateIP(subdomain, ip string) error { - newURL := strings.ReplaceAll(ovh.config.URL, "SUBDOMAIN", subdomain) - newURL = strings.ReplaceAll(newURL, "NEWIP", ip) - logger := ovh.logger.WithFields(logrus.Fields{ - "component": "update-ip", - "ovh-update-url": newURL, - "subdomain": subdomain, - "new-ip": ip, - }) - - // * create GET request - req, err := http.NewRequest("GET", newURL, nil) - if err != nil { - return utils.ErrCreateNewRequest - } - req.SetBasicAuth(ovh.config.Username, ovh.config.Password) - - // * perform GET request - logger.Debugln("calling OVH DynHost to update subdomain IP") - c := new(http.Client) - resp, err := c.Do(req) - if err != nil { - return utils.ErrUpdateRequest - } - if resp.StatusCode != 200 { - return utils.ErrWrongStatusCode - } - - return nil -} diff --git a/pkg/subdomain/main.go b/pkg/subdomain/main.go deleted file mode 100644 index 03372a1..0000000 --- a/pkg/subdomain/main.go +++ /dev/null @@ -1,92 +0,0 @@ -package subdomain - -import ( - "errors" - "net" - "strings" - "time" - - "github.com/datahearth/ddnsclient/pkg/utils" - "github.com/sirupsen/logrus" -) - -// ErrIpLength is thrown when subdomain no or multiples remote IP address -var ErrIpLenght = errors.New("zero or more than 1 ips have been found") - -type ( - PendingSubdomains map[time.Time]Subdomain - subdomain struct { - logger logrus.FieldLogger - subdomainAddr string - ip string - } - Subdomain interface { - CheckIPAddr(srvIP string) (bool, error) - GetSubdomainIP() string - retrieveSubdomainIP() error - GetSubdomainAddr() string - SubIsPending(sbs PendingSubdomains) bool - FindSubdomain(sbs PendingSubdomains) Subdomain - } -) - -// NewSubdomain instanciate a new http implementation -func NewSubdomain(logger logrus.FieldLogger, subdomainAddr string) (Subdomain, error) { - if logger == nil { - return nil, utils.ErrNilLogger - } - - logger = logger.WithField("pkg", "subdomain") - - return &subdomain{ - logger: logger, - subdomainAddr: subdomainAddr, - }, nil -} - -// RetrieveSubdomainIP will retrieve the subdomain IP with a HEAD request -func (sd *subdomain) retrieveSubdomainIP() error { - ips, err := net.LookupIP(sd.subdomainAddr) - 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 utils.ErrSplitAddr - } - } - - sd.ip = ip - - return nil -} - -// CheckIPAddr will compare the server IP and the subdomain IP -func (sd *subdomain) CheckIPAddr(srvIP string) (bool, error) { - if err := sd.retrieveSubdomainIP(); err != nil { - return false, err - } - - if srvIP != sd.ip { - return false, nil - } - - return true, nil -} - -// GetSubdomainIP returns the subdomain IP -func (sd *subdomain) GetSubdomainIP() string { - return sd.ip -} - -// GetSubdomainAddr returns the subdomain address -func (sd *subdomain) GetSubdomainAddr() string { - return sd.subdomainAddr -} diff --git a/pkg/subdomain/pending.go b/pkg/subdomain/pending.go deleted file mode 100644 index d57d640..0000000 --- a/pkg/subdomain/pending.go +++ /dev/null @@ -1,52 +0,0 @@ -package subdomain - -import "time" - -// SubIsPending check if the current subdomain is waiting the DNS propagation. -func (sb *subdomain) SubIsPending(sbs PendingSubdomains) bool { - for _, sub := range sbs { - if sb == sub { - return true - } - } - - return false -} - -// FindSubdomain returns a subdomain found in the pending map of subdomain. -// If not found, it returns nil. -func (sb *subdomain) FindSubdomain(sbs PendingSubdomains) Subdomain { - for _, sub := range sbs { - if sub == sb { - return sb - } - } - - return nil -} - -// CheckPendingSubdomains check if any pending subdomains are waiting to be restored. -// If so, it/they will be returned as a slice. -// If not, it returns nil. -func CheckPendingSubdomains(sbs PendingSubdomains, now time.Time) PendingSubdomains { - delSbs := make(PendingSubdomains) - for t, sb := range sbs { - if t.Add(5 * time.Minute).Before(now) { - delSbs[t] = sb - } - } - - if len(delSbs) < 1 { - return nil - } - - return delSbs -} - -func DeletePendingSubdomains(delSbs PendingSubdomains, pending PendingSubdomains) PendingSubdomains { - for t := range delSbs { - delete(pending, t) - } - - return pending -} diff --git a/pkg/utils/config.go b/pkg/utils/config.go new file mode 100644 index 0000000..8d681a6 --- /dev/null +++ b/pkg/utils/config.go @@ -0,0 +1,33 @@ +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", +} diff --git a/pkg/utils/types.go b/pkg/utils/types.go index 46533ca..96acc9f 100644 --- a/pkg/utils/types.go +++ b/pkg/utils/types.go @@ -8,18 +8,28 @@ import ( var ( // ErrNilLogger is thrown when the parameter logger is nil ErrNilLogger = errors.New("logger is mandatory") - // ErrNilProvider ... - ErrNilProvider = errors.New("provider is mandatory") // ErrWrongStatusCode is thrown when the response status code isn't a 200 ErrWrongStatusCode = errors.New("web-ip returns a non 200 status code") // ErrGetServerIP is thrown when HTTP can't contact the web-ip service ErrGetServerIP = errors.New("HTTP error") // ErrParseHTTPBody is thrown when the HTTP service can't parse the body response ErrParseHTTPBody = errors.New("can't parse response body") - // ErrSplitAddr ... + // ErrSplitAddr is thrown when the remote address can't be splitted ErrSplitAddr = errors.New("can't split subdomain remote IP address") - // ErrCreateNewRequest ... + // ErrCreateNewRequest is thrown when http request creation failed ErrCreateNewRequest = errors.New("can't create http request") - // ErrUpdateRequest ... + // 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 = errors.New("no url was provided") + // ErrInvalidName is thrown when provider name was not 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 = errors.New("watcher is mandatory") + // ErrIpLength is thrown when subdomain no or multiples remote IP address + ErrIpLenght = errors.New("zero or more than 1 ips have been found") + // ErrNilConfig is thrown when an empty config is provided + ErrNilConfig = errors.New("config is mandatory") ) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 628e486..3b8811c 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -2,7 +2,9 @@ package utils import ( "io/ioutil" + "net" "net/http" + "strings" "github.com/sirupsen/logrus" "github.com/spf13/viper" @@ -54,18 +56,8 @@ func SetupLogger(logger *logrus.Logger) { }) } -func AggregateSubdomains(subdomains []string, domain string) []string { - agdSub := make([]string, len(subdomains)) - for i, sd := range subdomains { - agdSub[i] = sd + "." + domain - } - - return agdSub -} - -// RetrieveServerIP will use the defined web-ip service to get the server public address and save it to the struct +// RetrieveServerIP will use the defined web-ip service to get the server public address func RetrieveServerIP(webIP string) (string, error) { - // * retrieve client's server IP resp, err := http.Get(webIP) if err != nil { return "", ErrGetServerIP @@ -74,7 +66,6 @@ func RetrieveServerIP(webIP string) (string, error) { return "", ErrWrongStatusCode } - // * get ip from body d, err := ioutil.ReadAll(resp.Body) if err != nil { return "", ErrParseHTTPBody @@ -82,3 +73,25 @@ func RetrieveServerIP(webIP string) (string, error) { 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 +} diff --git a/pkg/watcher/watcher.go b/pkg/watcher/watcher.go index 95030d0..5aab49f 100644 --- a/pkg/watcher/watcher.go +++ b/pkg/watcher/watcher.go @@ -3,8 +3,7 @@ package watcher import ( "time" - "github.com/datahearth/ddnsclient/pkg/providers" - "github.com/datahearth/ddnsclient/pkg/subdomain" + "github.com/datahearth/ddnsclient/pkg/provider" "github.com/datahearth/ddnsclient/pkg/utils" "github.com/sirupsen/logrus" ) @@ -14,58 +13,47 @@ type Watcher interface { } type watcher struct { - logger logrus.FieldLogger - provider providers.Provider - subdomains []subdomain.Subdomain - pendingSubdomains subdomain.PendingSubdomains - firstRun bool - pendingDnsPropagation int - webIP string - providerName string + logger logrus.FieldLogger + providers []provider.Provider + firstRun bool + webIP string + providerName string } -// NewWatcher creates a watcher a given provider and its subdomains -func NewWatcher(logger logrus.FieldLogger, provider providers.Provider, sbs []string, webIP, providerName string, pendingDnsPropagation int) (Watcher, error) { +// NewWatcher creates a watcher a given provider config +func NewWatcher(logger logrus.FieldLogger, w *utils.Watcher, webIP string) (Watcher, error) { if logger == nil { return nil, utils.ErrNilLogger } - if provider == nil { - return nil, utils.ErrNilProvider + if w == nil { + return nil, utils.ErrNilWatcher } if webIP == "" { - webIP = "http://dynamicdns.park-your-domain.com/getip" + webIP = utils.DefaultURLs["webIP"] } - if pendingDnsPropagation == 0 { - pendingDnsPropagation = 180 - } - logger = logger.WithField("pkg", "watcher") - subdomains := make([]subdomain.Subdomain, len(sbs)) - for i, sb := range sbs { - sub, err := subdomain.NewSubdomain(logger, sb) + providers := []provider.Provider{} + for _, c := range w.Config { + p, err := provider.NewProvider(logger, c, w.URL, w.Name) if err != nil { return nil, err } - - subdomains[i] = sub + providers = append(providers, p) } + logger = logger.WithField("pkg", "watcher") return &watcher{ - logger: logger, - provider: provider, - subdomains: subdomains, - webIP: webIP, - firstRun: true, - pendingSubdomains: make(map[time.Time]subdomain.Subdomain), - pendingDnsPropagation: pendingDnsPropagation, - providerName: providerName, + logger: logger, + providers: providers, + webIP: webIP, + firstRun: true, + providerName: w.Name, }, nil } func (w *watcher) Run(t *time.Ticker, chClose chan struct{}, chErr chan error) { logger := w.logger.WithField("component", "Run") - go w.checkPendingSubdomains(chClose) if w.firstRun { if err := w.runDDNSCheck(); err != nil { chErr <- err @@ -77,7 +65,7 @@ func (w *watcher) Run(t *time.Ticker, chClose chan struct{}, chErr chan error) { select { case <-chClose: t.Stop() - logger.Infoln("Close watcher channel triggered. Ticker stopped") + logger.WithField("provider", w.providerName).Infoln("Close watcher channel triggered. Ticker stopped") return case <-t.C: if err := w.runDDNSCheck(); err != nil { @@ -92,64 +80,18 @@ func (w *watcher) runDDNSCheck() error { logger.Infof("Starting [%s] DDNS check...\n", w.providerName) + logger.Debugln("Checking server IP...") srvIP, err := utils.RetrieveServerIP(w.webIP) if err != nil { return err } - logger.Debugln("Checking server IP...") - for _, sb := range w.subdomains { - if sb.SubIsPending(w.pendingSubdomains) { - continue - } - - logger.Debugf("Checking subdomain %s...\n", sb.GetSubdomainAddr()) - ok, err := sb.CheckIPAddr(srvIP) - if err != nil { + for _, p := range w.providers { + if err := p.UpdateSubdomains(srvIP); err != nil { return err } - subAddr := sb.GetSubdomainAddr() - if !ok { - logger.WithFields(logrus.Fields{ - "server-ip": srvIP, - "subdomain-address": subAddr, - }).Infoln("IP addresses doesn't match. Updating subdomain's ip...") - if err := w.provider.UpdateIP(subAddr, srvIP); err != nil { - return err - } - logger.WithFields(logrus.Fields{ - "server-ip": srvIP, - "subdomain-address": subAddr, - }).Infoln("Subdomain's ip updated! Removing from checks for 5 mins") - - w.pendingSubdomains[time.Now()] = sb - - continue - } - - logger.Debugf("%s is up to date. \n", subAddr) } logger.Infof("[%s] DDNS check finished\n", w.providerName) return nil } - -func (w *watcher) checkPendingSubdomains(chClose chan struct{}) { - logger := w.logger.WithField("component", "checkPendingSubdomains") - t := time.NewTicker(time.Second * time.Duration(w.pendingDnsPropagation)) - - logger.Debugln("Start checking for pending subdomains...") - for { - select { - case <-chClose: - logger.Debugln("Close pending subdomains") - return - case <-t.C: - logger.Debugln("Checking pending subdomains...") - if delSbs := subdomain.CheckPendingSubdomains(w.pendingSubdomains, time.Now()); delSbs != nil { - w.pendingSubdomains = subdomain.DeletePendingSubdomains(delSbs, w.pendingSubdomains) - logger.Debugln("Pendings subdomains found. Cleaned.") - } - } - } -}