diff --git a/Makefile b/Makefile index 45b5ce4..560ab71 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ deploy-image-release: ifndef DOCKER @echo "docker is required!" endif - @echo "Pushing image ddnsclient:$(RELEASE_VERSION) to docker hub..." + @echo "Pushing image with tag to docker hub..." @docker push ddnsclient:$(RELEASE_VERSION) @echo "Image pushed!" diff --git a/cmd/main.go b/cmd/main.go index f29014a..c25dfc0 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -16,12 +16,13 @@ var ( Checkout the documentation for parameters in the yaml config file. `, Run: func(cmd *cobra.Command, args []string) { - if err := ddnsclient.Start(logger); err != nil { + if err := ddnsclient.Start(logger, config); err != nil { logrus.Error(err) } }, } logger = logrus.StandardLogger() + config ddnsclient.ClientConfig ) func init() { @@ -34,6 +35,9 @@ func init() { } utils.LoadConfig() + if err := viper.Unmarshal(&config); err != nil { + logger.WithError(err).Fatalln("failed to map yaml config file into ClientConfig struct") + } utils.SetupLogger(logger) } diff --git a/config.go b/config.go new file mode 100644 index 0000000..5854f12 --- /dev/null +++ b/config.go @@ -0,0 +1,30 @@ +package ddnsclient + +type ClientConfig struct { + Logger Logger `mapstructure:"logger"` + Providers Providers `mapstructure:"providers"` + Watcher Watcher `mapstructure:"watcher"` + UpdateTime int `mapstructure:"update-time"` + WebIP string `mapstructure:"web-ip"` +} + +type Logger struct { + Level string `mapstructure:"level"` + DisableTimestamp bool `mapstructure:"disable-timestamp"` + DisableColor bool `mapstructure:"disable-color"` +} + +type Providers struct { + Ovh Ovh `mapstructure:"ovh,omitempty"` +} + +type Ovh struct { + URL string `mapstructure:"url"` + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` +} + +type Watcher struct { + Domain string `mapstructure:"domain"` + Subdomains []string `mapstructure:"subdomains"` +} diff --git a/main.go b/main.go index f28486b..35782a8 100644 --- a/main.go +++ b/main.go @@ -13,7 +13,7 @@ import ( ) // Start create a new instance of ddns-client -func Start(logger logrus.FieldLogger) error { +func Start(logger logrus.FieldLogger, config ClientConfig) error { log := logger.WithFields(logrus.Fields{ "pkg": "ddnsclient", "component": "root", @@ -40,13 +40,13 @@ func Start(logger logrus.FieldLogger) error { defer close(sigc) log.Infoln("Start watching periodically for changes!") - t := time.NewTicker(viper.GetDuration("update-time")*time.Second) + t := time.NewTicker(viper.GetDuration("update-time") * time.Second) go w.Run(t, chClose, chErr) for { select { case err := <-chErr: - log.WithError(err).Errorln("An error occured while running the watcher. Retrying in the next tick") + log.Errorln(err.Error()) continue case <-sigc: log.Infoln("Interrupt signal received. Stopping watcher...") diff --git a/pkg/providers/ovh/main.go b/pkg/providers/ovh/main.go index a38d64c..2e75d7a 100644 --- a/pkg/providers/ovh/main.go +++ b/pkg/providers/ovh/main.go @@ -46,7 +46,6 @@ func (ovh *ovh) UpdateIP(subdomain, ip string) error { // * create GET request req, err := http.NewRequest("GET", newURL, nil) if err != nil { - logger.WithError(err).WithField("request-type", "GET").Errorln(utils.ErrCreateNewRequest.Error()) return utils.ErrCreateNewRequest } req.SetBasicAuth(ovh.ovhConfig["username"].(string), ovh.ovhConfig["password"].(string)) @@ -54,19 +53,16 @@ func (ovh *ovh) UpdateIP(subdomain, ip string) error { // * perform GET request logger.WithFields(logrus.Fields{ "subdomain": subdomain, - "new-ip": ip, + "new-ip": ip, }).Debugln("calling OVH DynHost to update subdomain IP") c := new(http.Client) resp, err := c.Do(req) if err != nil { - logger.WithError(err).Errorln(utils.ErrUpdateRequest.Error()) return utils.ErrUpdateRequest } if resp.StatusCode != 200 { - logger.WithField("status-code", resp.StatusCode).Errorln(utils.ErrWrongStatusCode.Error()) return utils.ErrWrongStatusCode } - return nil } diff --git a/pkg/subdomain/main.go b/pkg/subdomain/main.go index 152be3a..c6e0803 100644 --- a/pkg/subdomain/main.go +++ b/pkg/subdomain/main.go @@ -2,9 +2,6 @@ package subdomain import ( "net" - "net/http" - h "net/http" - "net/http/httptrace" "strings" "time" @@ -45,54 +42,24 @@ func NewSubdomain(logger logrus.FieldLogger, subdomainAddr string) (Subdomain, e // RetrieveSubdomainIP will retrieve the subdomain IP with a HEAD request func (sd *subdomain) retrieveSubdomainIP() error { - var remoteAddr string - logger := sd.logger.WithField("component", "retrieve-subdomain-ip") - - // * create HEAD request - req, err := http.NewRequest("HEAD", "https://"+sd.subdomainAddr, nil) + ips, err := net.LookupIP(sd.subdomainAddr) if err != nil { return err } - // * create a trace to get server remote address - trace := &httptrace.ClientTrace{ - GotConn: func(gci httptrace.GotConnInfo) { - remoteAddr = gci.Conn.RemoteAddr().String() - }, - } - req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace)) - // * create a client to perform the request - client := new(h.Client) - client.Timeout = 5 * time.Second - - // * perform the request - resp, err := client.Do(req) - if err != nil { - // todo: ignoring errors is bad. Implement a solution to scrape 100% of the time remote addr - logger.WithError(err).WithFields(logrus.Fields{ - "subdomain": sd.subdomainAddr, - }).Errorln(utils.ErrHeadRemoteIP.Error()) - sd.ip = "" - return nil - } - if resp.StatusCode != 200 && remoteAddr == "" { - logger.WithFields(logrus.Fields{ - "status-code": resp.StatusCode, - "subdomain": sd.subdomainAddr, - }).Errorln(utils.ErrWrongStatusCode.Error()) - return utils.ErrWrongStatusCode + if len(ips) != 1 { + return utils.ErrIpLenght } - // * check if remote address contains a port - if strings.Contains(remoteAddr, ":") { - remoteAddr, _, err = net.SplitHostPort(remoteAddr) + ip := ips[0].String() + if strings.Contains(ip, ":") { + ip, _, err = net.SplitHostPort(ip) if err != nil { - logger.WithError(err).WithField("remote-address", remoteAddr).Errorln(utils.ErrSplitAddr.Error()) return utils.ErrSplitAddr } } - sd.ip = remoteAddr + sd.ip = ip return nil } diff --git a/pkg/subdomain/pending.go b/pkg/subdomain/pending.go index 0ec230d..d57d640 100644 --- a/pkg/subdomain/pending.go +++ b/pkg/subdomain/pending.go @@ -13,6 +13,18 @@ func (sb *subdomain) SubIsPending(sbs PendingSubdomains) bool { 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. @@ -31,18 +43,6 @@ func CheckPendingSubdomains(sbs PendingSubdomains, now time.Time) PendingSubdoma return delSbs } -// 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 -} - func DeletePendingSubdomains(delSbs PendingSubdomains, pending PendingSubdomains) PendingSubdomains { for t := range delSbs { delete(pending, t) diff --git a/pkg/utils/types.go b/pkg/utils/types.go index 8415396..c9d9c99 100644 --- a/pkg/utils/types.go +++ b/pkg/utils/types.go @@ -17,9 +17,9 @@ var ( // ErrNilHTTP ... ErrNilHTTP = errors.New("http is mandatory") // ErrWrongStatusCode is thrown when the response status code isn't a 200 - ErrWrongStatusCode = errors.New("response sent an non 200 status code") + 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("failed to fetch server IP") + 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") // ErrHeadRemoteIP ... @@ -30,6 +30,8 @@ var ( ErrCreateNewRequest = errors.New("can't create http request") // ErrUpdateRequest ... ErrUpdateRequest = errors.New("failed to set new IP address") + // ErrIpLength ... + ErrIpLenght = errors.New("zero or more than 1 ips have been found") ) type ( diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 9350c3c..628e486 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -65,29 +65,18 @@ func AggregateSubdomains(subdomains []string, domain string) []string { // RetrieveServerIP will use the defined web-ip service to get the server public address and save it to the struct func RetrieveServerIP(webIP string) (string, error) { - logger := logrus.WithFields(logrus.Fields{ - "pkg": "utils", - "component": "server-ip", - }) - // * retrieve client's server IP resp, err := http.Get(webIP) if err != nil { - logger.WithError(err).WithField("web-ip", webIP).Errorln(ErrGetServerIP.Error()) return "", ErrGetServerIP } if resp.StatusCode != 200 { - logger.WithError(err).WithFields(logrus.Fields{ - "web-ip": webIP, - "statuc-code": resp.StatusCode, - }).Errorln(ErrWrongStatusCode.Error()) return "", ErrWrongStatusCode } // * get ip from body d, err := ioutil.ReadAll(resp.Body) if err != nil { - logger.WithError(err).WithField("web-ip", webIP).Errorln(ErrParseHTTPBody.Error()) return "", ErrParseHTTPBody } diff --git a/pkg/watcher/watcher.go b/pkg/watcher/watcher.go index 96a8438..d1fc62a 100644 --- a/pkg/watcher/watcher.go +++ b/pkg/watcher/watcher.go @@ -94,11 +94,16 @@ func (w *watcher) Run(t *time.Ticker, chClose chan struct{}, chErr chan error) { func (w *watcher) runDDNSCheck() error { logger := w.logger.WithField("component", "runDDNSCheck") + logger.Infoln("Starting DDNS check...") + srvIP, err := utils.RetrieveServerIP(w.webIP) if err != nil { return err } + logger.Debugln("Checking server IP...") + + srvIP = "109.14.53.74" // tmp for _, sb := range w.subdomains { if sb.SubIsPending(w.pendingSubdomains) {