feat: add new provider and update config template (#2)
This commit is contained in:
parent
6fead37859
commit
1190574d8c
35
config.go
35
config.go
|
@ -1,30 +1,31 @@
|
||||||
package ddnsclient
|
package ddnsclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/datahearth/ddnsclient/pkg/providers/google"
|
||||||
|
"github.com/datahearth/ddnsclient/pkg/providers/ovh"
|
||||||
|
)
|
||||||
|
|
||||||
type ClientConfig struct {
|
type ClientConfig struct {
|
||||||
Logger Logger `mapstructure:"logger"`
|
Logger Logger `mapstructure:"logger"`
|
||||||
Providers Providers `mapstructure:"providers"`
|
Providers Providers `mapstructure:"providers"`
|
||||||
Watcher Watcher `mapstructure:"watcher"`
|
Watchers WatcherConfig `mapstructure:"watchers"`
|
||||||
UpdateTime int `mapstructure:"update-time"`
|
UpdateTime int `mapstructure:"update-time,omitempty"`
|
||||||
WebIP string `mapstructure:"web-ip"`
|
PendingDnsPropagation int `mapstructure:"pending-dns-propagation,omitempty"`
|
||||||
|
WebIP string `mapstructure:"web-ip,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Logger struct {
|
type Logger struct {
|
||||||
Level string `mapstructure:"level"`
|
Level string `mapstructure:"level"`
|
||||||
DisableTimestamp bool `mapstructure:"disable-timestamp"`
|
DisableTimestamp bool `mapstructure:"disable-timestamp,omitempty"`
|
||||||
DisableColor bool `mapstructure:"disable-color"`
|
DisableColor bool `mapstructure:"disable-color,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Providers struct {
|
type Providers struct {
|
||||||
Ovh Ovh `mapstructure:"ovh,omitempty"`
|
Ovh ovh.OvhConfig `mapstructure:"ovh,omitempty"`
|
||||||
|
Google google.GoogleConfig `mapstructure:"google,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Ovh struct {
|
type WatcherConfig struct {
|
||||||
URL string `mapstructure:"url"`
|
Ovh []string `yaml:"ovh,omitempty"`
|
||||||
Username string `mapstructure:"username"`
|
Google []string `yaml:"google,omitempty"`
|
||||||
Password string `mapstructure:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Watcher struct {
|
|
||||||
Domain string `mapstructure:"domain"`
|
|
||||||
Subdomains []string `mapstructure:"subdomains"`
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,31 @@
|
||||||
logger:
|
logger:
|
||||||
level: info # panic, fatal, error, warn, info, debug, trace
|
level: info # panic, fatal, error, warn, info, debug, trace
|
||||||
disable-timestamp: false
|
disable-timestamp: false # default false
|
||||||
disable-color: true
|
disable-color: true # default false
|
||||||
|
|
||||||
provider:
|
providers:
|
||||||
ovh:
|
# ! WARNING: DO NOT REPLACE `SUBDOMAIN` and `NEWIP`
|
||||||
# replace YOUR_DOMAIN_NAME with the desired domain name
|
# Uncomment your DDNS provider
|
||||||
# ! WARNING: DO NOT REPLACE `SUBDOMAIN` and `NEWIP`
|
# ovh:
|
||||||
url: http://www.ovh.com/nic/update?system=dyndns&hostname=SUBDOMAIN&myip=NEWIP
|
# url: http://www.ovh.com/nic/update?system=dyndns&hostname=SUBDOMAIN&myip=NEWIP
|
||||||
username: SOME_USERNAME
|
# username: SOME_USERNAME
|
||||||
password: SOME_PASSWORD
|
# password: SOME_PASSWORD
|
||||||
|
# google:
|
||||||
|
# url: https://username:password@domains.google.com/nic/update?hostname=SUBDOMAIN&myip=NEWIP
|
||||||
|
# OR
|
||||||
|
# url: https://domains.google.com/nic/update?hostname=SUBDOMAIN&myip=NEWIP
|
||||||
|
# username: SOME_USERNAME
|
||||||
|
# password: SOME_PASSWORD
|
||||||
|
|
||||||
watcher:
|
watchers:
|
||||||
domain: some.com
|
# google:
|
||||||
subdomains:
|
# Currently, only one subdomain is supported...
|
||||||
- example # just put the host. Ignore the base domain
|
# - sub.domain-google.com
|
||||||
|
# ovh:
|
||||||
|
# - sub.domain-ovh.com
|
||||||
|
|
||||||
update-time: 300 # in seconds
|
update-time: 300 # in seconds, default 180
|
||||||
|
pending-dns-propagation: 120 # in seconds, default 180
|
||||||
|
|
||||||
web-ip: http://dynamicdns.park-your-domain.com/getip # default http://dynamicdns.park-your-domain.com/getip
|
web-ip: http://dynamicdns.park-your-domain.com/getip # default http://dynamicdns.park-your-domain.com/getip
|
||||||
|
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -15,7 +15,9 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
|
@ -142,6 +144,7 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
|
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
|
||||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
|
@ -296,6 +299,7 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvx
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
|
|
103
main.go
103
main.go
|
@ -1,15 +1,26 @@
|
||||||
package ddnsclient
|
package ddnsclient
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"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/providers/ovh"
|
||||||
"github.com/datahearth/ddnsclient/pkg/watcher"
|
"github.com/datahearth/ddnsclient/pkg/watcher"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/viper"
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
@ -19,30 +30,56 @@ func Start(logger logrus.FieldLogger, config ClientConfig) error {
|
||||||
"component": "root",
|
"component": "root",
|
||||||
})
|
})
|
||||||
|
|
||||||
log.Debugln("create OVH provider")
|
fields := reflect.ValueOf(config.Watchers)
|
||||||
ovh, err := ovh.NewOVH(logger)
|
ws := []watcher.Watcher{}
|
||||||
if err != nil {
|
|
||||||
return err
|
// * 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
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugln("creating watcher with OVH provider")
|
for i := 0; i < fields.NumField(); i++ {
|
||||||
w, err := watcher.NewWatcher(logger, ovh, viper.GetString("web-ip"))
|
providerName := strings.ToLower(fields.Type().Field(i).Name)
|
||||||
if err != nil {
|
|
||||||
return err
|
w, err := CreateWatcher(providerName, config.WebIP, logger, config.Watchers, config.Providers, config.PendingDnsPropagation)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warnf("Provider error: %v. Skipping...\n", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ws = append(ws, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// * check for valid created watchers
|
||||||
|
if len(ws) == 0 {
|
||||||
|
return ErrWatcherCreationLen
|
||||||
|
}
|
||||||
|
|
||||||
|
// * create signal watcher
|
||||||
sigc := make(chan os.Signal, 1)
|
sigc := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||||
|
defer close(sigc)
|
||||||
|
|
||||||
|
// * create close and error channel
|
||||||
chClose := make(chan struct{})
|
chClose := make(chan struct{})
|
||||||
chErr := make(chan error)
|
chErr := make(chan error)
|
||||||
defer close(chClose)
|
defer close(chClose)
|
||||||
defer close(chErr)
|
defer close(chErr)
|
||||||
defer close(sigc)
|
|
||||||
|
|
||||||
log.Infoln("Start watching periodically for changes!")
|
logger.Infoln("Start watching periodically for changes!")
|
||||||
t := time.NewTicker(viper.GetDuration("update-time") * time.Second)
|
// * run every created watchers in goroutines
|
||||||
go w.Run(t, chClose, chErr)
|
for _, w := range ws {
|
||||||
|
tickTime := config.UpdateTime
|
||||||
|
if tickTime == 0 {
|
||||||
|
tickTime = 180
|
||||||
|
}
|
||||||
|
|
||||||
|
t := time.NewTicker(time.Duration(tickTime) * time.Second)
|
||||||
|
go w.Run(t, chClose, chErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// * listening for errors and exit signal
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case err := <-chErr:
|
case err := <-chErr:
|
||||||
|
@ -55,3 +92,43 @@ 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
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -1,46 +1,57 @@
|
||||||
package ovh
|
package ovh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/datahearth/ddnsclient/pkg/providers"
|
"github.com/datahearth/ddnsclient/pkg/providers"
|
||||||
"github.com/datahearth/ddnsclient/pkg/utils"
|
"github.com/datahearth/ddnsclient/pkg/utils"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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 {
|
type ovh struct {
|
||||||
ovhConfig utils.ProviderConfig
|
config *OvhConfig
|
||||||
logger logrus.FieldLogger
|
logger logrus.FieldLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOVH returns a new instance of the OVH provider
|
// NewOVH returns a new instance of the OVH provider
|
||||||
func NewOVH(logger logrus.FieldLogger) (providers.Provider, error) {
|
func NewOVH(logger logrus.FieldLogger, ovhConfig *OvhConfig) (providers.Provider, error) {
|
||||||
var ovhConfig utils.ProviderConfig
|
if ovhConfig == nil {
|
||||||
if c, ok := viper.GetStringMap("providers")["ovh"]; ok {
|
return nil, ErrNilOvhConfig
|
||||||
ovhConfig = c.(map[string]interface{})
|
|
||||||
} else {
|
|
||||||
return nil, utils.ErrNilOvhConfig
|
|
||||||
}
|
}
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
return nil, utils.ErrNilLogger
|
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")
|
logger = logger.WithField("pkg", "provider-ovh")
|
||||||
|
|
||||||
return &ovh{
|
return &ovh{
|
||||||
ovhConfig: ovhConfig,
|
config: ovhConfig,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ovh *ovh) UpdateIP(subdomain, ip string) error {
|
func (ovh *ovh) UpdateIP(subdomain, ip string) error {
|
||||||
newURL := strings.ReplaceAll(ovh.ovhConfig["url"].(string), "SUBDOMAIN", subdomain)
|
newURL := strings.ReplaceAll(ovh.config.URL, "SUBDOMAIN", subdomain)
|
||||||
newURL = strings.ReplaceAll(newURL, "NEWIP", ip)
|
newURL = strings.ReplaceAll(newURL, "NEWIP", ip)
|
||||||
logger := ovh.logger.WithFields(logrus.Fields{
|
logger := ovh.logger.WithFields(logrus.Fields{
|
||||||
"component": "update-ip",
|
"component": "update-ip",
|
||||||
"ovh-update-url": newURL,
|
"ovh-update-url": newURL,
|
||||||
|
"subdomain": subdomain,
|
||||||
|
"new-ip": ip,
|
||||||
})
|
})
|
||||||
|
|
||||||
// * create GET request
|
// * create GET request
|
||||||
|
@ -48,13 +59,10 @@ func (ovh *ovh) UpdateIP(subdomain, ip string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return utils.ErrCreateNewRequest
|
return utils.ErrCreateNewRequest
|
||||||
}
|
}
|
||||||
req.SetBasicAuth(ovh.ovhConfig["username"].(string), ovh.ovhConfig["password"].(string))
|
req.SetBasicAuth(ovh.config.Username, ovh.config.Password)
|
||||||
|
|
||||||
// * perform GET request
|
// * perform GET request
|
||||||
logger.WithFields(logrus.Fields{
|
logger.Debugln("calling OVH DynHost to update subdomain IP")
|
||||||
"subdomain": subdomain,
|
|
||||||
"new-ip": ip,
|
|
||||||
}).Debugln("calling OVH DynHost to update subdomain IP")
|
|
||||||
c := new(http.Client)
|
c := new(http.Client)
|
||||||
resp, err := c.Do(req)
|
resp, err := c.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package subdomain
|
package subdomain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -9,6 +10,9 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"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 (
|
type (
|
||||||
PendingSubdomains map[time.Time]Subdomain
|
PendingSubdomains map[time.Time]Subdomain
|
||||||
subdomain struct {
|
subdomain struct {
|
||||||
|
@ -48,7 +52,7 @@ func (sd *subdomain) retrieveSubdomainIP() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ips) != 1 {
|
if len(ips) != 1 {
|
||||||
return utils.ErrIpLenght
|
return ErrIpLenght
|
||||||
}
|
}
|
||||||
|
|
||||||
ip := ips[0].String()
|
ip := ips[0].String()
|
||||||
|
|
|
@ -6,34 +6,20 @@ import (
|
||||||
|
|
||||||
// * Errors
|
// * Errors
|
||||||
var (
|
var (
|
||||||
// ErrReadConfigFile is thrown when viper failed to read config file
|
|
||||||
ErrReadConfigFile = errors.New("failed to read config file")
|
|
||||||
// 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")
|
||||||
// ErrNilOvhConfig is thrown when OVH configuration is empty
|
|
||||||
ErrNilOvhConfig = errors.New("OVH config is mandatory")
|
|
||||||
// ErrNilProvider ...
|
// ErrNilProvider ...
|
||||||
ErrNilProvider = errors.New("provider is mandatory")
|
ErrNilProvider = errors.New("provider is mandatory")
|
||||||
// ErrNilHTTP ...
|
|
||||||
ErrNilHTTP = errors.New("http is mandatory")
|
|
||||||
// ErrWrongStatusCode is thrown when the response status code isn't a 200
|
// ErrWrongStatusCode is thrown when the response status code isn't a 200
|
||||||
ErrWrongStatusCode = errors.New("web-ip returns a 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 is thrown when HTTP can't contact the web-ip service
|
||||||
ErrGetServerIP = errors.New("HTTP error")
|
ErrGetServerIP = errors.New("HTTP error")
|
||||||
// ErrParseHTTPBody is thrown when the HTTP service can't parse the body response
|
// ErrParseHTTPBody is thrown when the HTTP service can't parse the body response
|
||||||
ErrParseHTTPBody = errors.New("can't parse response body")
|
ErrParseHTTPBody = errors.New("can't parse response body")
|
||||||
// ErrHeadRemoteIP ...
|
|
||||||
ErrHeadRemoteIP = errors.New("failed to fetch subdomain informations with HEAD")
|
|
||||||
// ErrSplitAddr ...
|
// ErrSplitAddr ...
|
||||||
ErrSplitAddr = errors.New("can't split subdomain remote IP address")
|
ErrSplitAddr = errors.New("can't split subdomain remote IP address")
|
||||||
// ErrCreateNewRequest ...
|
// ErrCreateNewRequest ...
|
||||||
ErrCreateNewRequest = errors.New("can't create http request")
|
ErrCreateNewRequest = errors.New("can't create http request")
|
||||||
// ErrUpdateRequest ...
|
// ErrUpdateRequest ...
|
||||||
ErrUpdateRequest = errors.New("failed to set new IP address")
|
ErrUpdateRequest = errors.New("failed to set new IP address")
|
||||||
// ErrIpLength ...
|
|
||||||
ErrIpLenght = errors.New("zero or more than 1 ips have been found")
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
ProviderConfig map[string]interface{}
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
package watcher
|
package watcher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/datahearth/ddnsclient/pkg/providers"
|
"github.com/datahearth/ddnsclient/pkg/providers"
|
||||||
"github.com/datahearth/ddnsclient/pkg/subdomain"
|
"github.com/datahearth/ddnsclient/pkg/subdomain"
|
||||||
"github.com/datahearth/ddnsclient/pkg/utils"
|
"github.com/datahearth/ddnsclient/pkg/utils"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Watcher interface {
|
type Watcher interface {
|
||||||
|
@ -16,16 +14,18 @@ type Watcher interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type watcher struct {
|
type watcher struct {
|
||||||
logger logrus.FieldLogger
|
logger logrus.FieldLogger
|
||||||
provider providers.Provider
|
provider providers.Provider
|
||||||
subdomains []subdomain.Subdomain
|
subdomains []subdomain.Subdomain
|
||||||
domain string
|
pendingSubdomains subdomain.PendingSubdomains
|
||||||
webIP string
|
firstRun bool
|
||||||
firstRun bool
|
pendingDnsPropagation int
|
||||||
pendingSubdomains subdomain.PendingSubdomains
|
webIP string
|
||||||
|
providerName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWatcher(logger logrus.FieldLogger, provider providers.Provider, webIP string) (Watcher, error) {
|
// 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) {
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
return nil, utils.ErrNilLogger
|
return nil, utils.ErrNilLogger
|
||||||
}
|
}
|
||||||
|
@ -35,20 +35,14 @@ func NewWatcher(logger logrus.FieldLogger, provider providers.Provider, webIP st
|
||||||
if webIP == "" {
|
if webIP == "" {
|
||||||
webIP = "http://dynamicdns.park-your-domain.com/getip"
|
webIP = "http://dynamicdns.park-your-domain.com/getip"
|
||||||
}
|
}
|
||||||
|
if pendingDnsPropagation == 0 {
|
||||||
|
pendingDnsPropagation = 180
|
||||||
|
}
|
||||||
logger = logger.WithField("pkg", "watcher")
|
logger = logger.WithField("pkg", "watcher")
|
||||||
|
|
||||||
domain := viper.GetStringMap("watcher")["domain"].(string)
|
|
||||||
var sbs []string
|
|
||||||
if sb, ok := viper.GetStringMap("watcher")["subdomains"].([]interface{}); ok {
|
|
||||||
for _, v := range sb {
|
|
||||||
sbs = append(sbs, fmt.Sprint(v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sbs = utils.AggregateSubdomains(sbs, domain)
|
|
||||||
subdomains := make([]subdomain.Subdomain, len(sbs))
|
subdomains := make([]subdomain.Subdomain, len(sbs))
|
||||||
for i, sd := range sbs {
|
for i, sb := range sbs {
|
||||||
sub, err := subdomain.NewSubdomain(logger, sd)
|
sub, err := subdomain.NewSubdomain(logger, sb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -57,13 +51,14 @@ func NewWatcher(logger logrus.FieldLogger, provider providers.Provider, webIP st
|
||||||
}
|
}
|
||||||
|
|
||||||
return &watcher{
|
return &watcher{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
provider: provider,
|
provider: provider,
|
||||||
domain: domain,
|
subdomains: subdomains,
|
||||||
subdomains: subdomains,
|
webIP: webIP,
|
||||||
webIP: webIP,
|
firstRun: true,
|
||||||
firstRun: true,
|
pendingSubdomains: make(map[time.Time]subdomain.Subdomain),
|
||||||
pendingSubdomains: make(map[time.Time]subdomain.Subdomain),
|
pendingDnsPropagation: pendingDnsPropagation,
|
||||||
|
providerName: providerName,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +77,7 @@ func (w *watcher) Run(t *time.Ticker, chClose chan struct{}, chErr chan error) {
|
||||||
select {
|
select {
|
||||||
case <-chClose:
|
case <-chClose:
|
||||||
t.Stop()
|
t.Stop()
|
||||||
logger.Infoln("Close watcher channel triggered. Ticker stoped")
|
logger.Infoln("Close watcher channel triggered. Ticker stopped")
|
||||||
return
|
return
|
||||||
case <-t.C:
|
case <-t.C:
|
||||||
if err := w.runDDNSCheck(); err != nil {
|
if err := w.runDDNSCheck(); err != nil {
|
||||||
|
@ -95,7 +90,7 @@ func (w *watcher) Run(t *time.Ticker, chClose chan struct{}, chErr chan error) {
|
||||||
func (w *watcher) runDDNSCheck() error {
|
func (w *watcher) runDDNSCheck() error {
|
||||||
logger := w.logger.WithField("component", "runDDNSCheck")
|
logger := w.logger.WithField("component", "runDDNSCheck")
|
||||||
|
|
||||||
logger.Infoln("Starting DDNS check...")
|
logger.Infof("Starting [%s] DDNS check...\n", w.providerName)
|
||||||
|
|
||||||
srvIP, err := utils.RetrieveServerIP(w.webIP)
|
srvIP, err := utils.RetrieveServerIP(w.webIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -103,8 +98,6 @@ func (w *watcher) runDDNSCheck() error {
|
||||||
}
|
}
|
||||||
logger.Debugln("Checking server IP...")
|
logger.Debugln("Checking server IP...")
|
||||||
|
|
||||||
srvIP = "109.14.53.74" // tmp
|
|
||||||
|
|
||||||
for _, sb := range w.subdomains {
|
for _, sb := range w.subdomains {
|
||||||
if sb.SubIsPending(w.pendingSubdomains) {
|
if sb.SubIsPending(w.pendingSubdomains) {
|
||||||
continue
|
continue
|
||||||
|
@ -122,10 +115,6 @@ func (w *watcher) runDDNSCheck() error {
|
||||||
"subdomain-address": subAddr,
|
"subdomain-address": subAddr,
|
||||||
}).Infoln("IP addresses doesn't match. Updating subdomain's ip...")
|
}).Infoln("IP addresses doesn't match. Updating subdomain's ip...")
|
||||||
if err := w.provider.UpdateIP(subAddr, srvIP); err != nil {
|
if err := w.provider.UpdateIP(subAddr, srvIP); err != nil {
|
||||||
logger.WithError(err).WithFields(logrus.Fields{
|
|
||||||
"server-ip": srvIP,
|
|
||||||
"subdomain-address": subAddr,
|
|
||||||
}).Errorln("failed to update subdomain's ip")
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.WithFields(logrus.Fields{
|
logger.WithFields(logrus.Fields{
|
||||||
|
@ -137,16 +126,17 @@ func (w *watcher) runDDNSCheck() error {
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debugf("%s is up to date. \n", subAddr)
|
logger.Debugf("%s is up to date. \n", subAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Infoln("DDNS check finished")
|
logger.Infof("[%s] DDNS check finished\n", w.providerName)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *watcher) checkPendingSubdomains(chClose chan struct{}) {
|
func (w *watcher) checkPendingSubdomains(chClose chan struct{}) {
|
||||||
logger := w.logger.WithField("component", "checkPendingSubdomains")
|
logger := w.logger.WithField("component", "checkPendingSubdomains")
|
||||||
t := time.NewTicker(time.Second * 10)
|
t := time.NewTicker(time.Second * time.Duration(w.pendingDnsPropagation))
|
||||||
|
|
||||||
logger.Debugln("Start checking for pending subdomains...")
|
logger.Debugln("Start checking for pending subdomains...")
|
||||||
for {
|
for {
|
||||||
|
|
Loading…
Reference in New Issue