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"
|
||||
)
|
||||
|
||||
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
|
||||
func Start(logger logrus.FieldLogger, config utils.ClientConfig) error {
|
||||
log := logger.WithFields(logrus.Fields{
|
||||
|
@ -38,7 +31,7 @@ func Start(logger logrus.FieldLogger, config utils.ClientConfig) error {
|
|||
}
|
||||
|
||||
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)
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
|
@ -10,9 +11,10 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Provider is the default interface for all providers
|
||||
type Provider interface {
|
||||
UpdateSubdomains(ip string) error
|
||||
updateSubdomain(subdomain, ip string) error
|
||||
retrieveSubdomainIP(addr string) (string, error)
|
||||
}
|
||||
|
||||
type provider struct {
|
||||
|
@ -22,6 +24,7 @@ type provider struct {
|
|||
url string
|
||||
}
|
||||
|
||||
// NewProvider creates a new instance of the `Provider` interface
|
||||
func NewProvider(logger logrus.FieldLogger, config utils.Config, url, name string) (Provider, error) {
|
||||
if logger == nil {
|
||||
return nil, utils.ErrNilLogger
|
||||
|
@ -45,9 +48,11 @@ func NewProvider(logger logrus.FieldLogger, config utils.Config, url, name strin
|
|||
}, 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 {
|
||||
for _, sb := range p.config.Subdomains {
|
||||
ip, err := utils.RetrieveSubdomainIP(sb)
|
||||
ip, err := p.retrieveSubdomainIP(sb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -63,14 +68,11 @@ func (p *provider) UpdateSubdomains(srvIP string) error {
|
|||
"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")
|
||||
}).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 {
|
||||
tokenBased := p.config.Token != "" && (p.config.Username == "" && p.config.Password == "")
|
||||
|
||||
newURL := strings.ReplaceAll(p.url, "SUBDOMAIN", subdomain)
|
||||
newURL = strings.ReplaceAll(newURL, "NEWIP", ip)
|
||||
if tokenBased {
|
||||
newURL = strings.ReplaceAll(newURL, "TOKEN", p.config.Token)
|
||||
}
|
||||
logger := p.logger.WithFields(logrus.Fields{
|
||||
"component": "UpdateIP",
|
||||
"updated-url": newURL,
|
||||
|
@ -91,23 +98,25 @@ func (p *provider) updateSubdomain(subdomain, ip string) error {
|
|||
if err != nil {
|
||||
return utils.ErrCreateNewRequest
|
||||
}
|
||||
if !tokenBased {
|
||||
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
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.ContentLength != 0 {
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return utils.ErrReadBody
|
||||
return err
|
||||
}
|
||||
|
||||
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 err := p.checkResponse(b, tokenBased, ip); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,3 +126,44 @@ func (p *provider) updateSubdomain(subdomain, ip string) error {
|
|||
|
||||
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 ** //
|
||||
var (
|
||||
// ErrNilLogger is thrown when the parameter logger is nil
|
||||
ErrNilLogger = errors.New("logger is mandatory")
|
||||
|
@ -18,14 +18,10 @@ var (
|
|||
ErrSplitAddr = errors.New("can't split subdomain remote IP address")
|
||||
// ErrCreateNewRequest is thrown when http request creation failed
|
||||
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 = 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
|
||||
|
@ -33,3 +29,37 @@ var (
|
|||
// ErrNilConfig is thrown when an empty config is provided
|
||||
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
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
@ -55,43 +50,3 @@ func SetupLogger(logger *logrus.Logger) {
|
|||
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
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/datahearth/ddnsclient/pkg/provider"
|
||||
|
@ -10,6 +12,8 @@ import (
|
|||
|
||||
type Watcher interface {
|
||||
Run(*time.Ticker, chan struct{}, chan error)
|
||||
runDDNSCheck() error
|
||||
retrieveServerIP() (string, error)
|
||||
}
|
||||
|
||||
type watcher struct {
|
||||
|
@ -20,7 +24,7 @@ type watcher struct {
|
|||
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) {
|
||||
if logger == nil {
|
||||
return nil, utils.ErrNilLogger
|
||||
|
@ -51,6 +55,7 @@ func NewWatcher(logger logrus.FieldLogger, w *utils.Watcher, webIP string) (Watc
|
|||
}, 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) {
|
||||
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.Debugln("Checking server IP...")
|
||||
srvIP, err := utils.RetrieveServerIP(w.webIP)
|
||||
srvIP, err := w.retrieveServerIP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -95,3 +100,20 @@ func (w *watcher) runDDNSCheck() error {
|
|||
logger.Infof("[%s] DDNS check finished\n", w.providerName)
|
||||
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