Skip to content

Commit f1e3f34

Browse files
committed
feat: manager, connectors, configuration and OVH connector
1 parent 586838b commit f1e3f34

File tree

12 files changed

+1215
-25
lines changed

12 files changed

+1215
-25
lines changed

Diff for: .env.example

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
OVH_APPLICATION_KEY=
2+
OVH_APPLICATION_SECRET=
3+
OVH_CONSUMER_KEY=

Diff for: .gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.env*
2+
!.env.example

Diff for: config/updater.yaml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Record to update define the rules to follow when found an ip change
2+
# Each record must contain:
3+
# connector => name of the connector to use
4+
# domain => which domain to update
5+
# subDomain => when your record is on a subdomain enter the name of
6+
# the sub domain here without domain extension
7+
# awesome.domain.tld must be `awesome`
8+
# type => type of the record. Actually must be A or AAAA only
9+
10+
ipFetchInterval: 30s
11+
12+
records:
13+
- connector: ovh
14+
domain: example.space
15+
subDomain: null
16+
type: A
17+
# interval: 1m

Diff for: go.mod

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
module gitlab.com/atomys-universe/dns-updater
22

33
go 1.16
4+
5+
require (
6+
github.com/ovh/go-ovh v1.1.0
7+
github.com/rs/zerolog v1.26.1
8+
github.com/spf13/viper v1.10.1
9+
)

Diff for: go.sum

+780
Large diffs are not rendered by default.

Diff for: internal/pkg/ip/ip.go

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package ip
2+
3+
import (
4+
"io/ioutil"
5+
"net"
6+
"net/http"
7+
8+
"github.com/rs/zerolog/log"
9+
)
10+
11+
var (
12+
// CurrentIPv4 of the machine
13+
CurrentIPv4 net.IP = net.ParseIP("0.0.0.0")
14+
// CurrentIPv6 of the machine - only if machine have ipv6
15+
CurrentIPv6 net.IP = net.ParseIP("0:0:0:0:0:0:0:0")
16+
)
17+
18+
/**
19+
* Fetch the IPv4 of current machine with https://ipconfig.co service
20+
* Thanks to him
21+
*/
22+
func fetchIPv4() net.IP {
23+
return fetch("https://v4.ifconfig.co/ip")
24+
}
25+
26+
/**
27+
* Fetch the IPv6 of current machine with https://ipconfig.co service
28+
* Thanks to him
29+
*/
30+
func fetchIPv6() net.IP {
31+
return fetch("https://v6.ifconfig.co/ip")
32+
}
33+
34+
/**
35+
* Fetch ip of current machine with given url
36+
* url needs to return ip on text/plain Content-Type
37+
*/
38+
func fetch(url string) net.IP {
39+
log.Debug().Msg("Getting current IP")
40+
resp, err := http.Get(url)
41+
if err != nil {
42+
log.Error().Err(err).Msg("cannot fetch current IP")
43+
}
44+
45+
bodyBytes, err := ioutil.ReadAll(resp.Body)
46+
if err != nil {
47+
log.Error().Err(err).Msg("cannot fetch current IP")
48+
}
49+
50+
return net.ParseIP(string(bodyBytes))
51+
}

Diff for: internal/pkg/ip/state.go

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package ip
2+
3+
import (
4+
"time"
5+
)
6+
7+
type IPChangeState struct {
8+
IPV4Change bool
9+
IPV6Change bool
10+
}
11+
12+
/**
13+
* This function will fetch the difference of ip and send change in the given
14+
* chan
15+
*/
16+
func FetchIPChangeRoutine(c chan IPChangeState, interval time.Duration) {
17+
for {
18+
var state = IPChangeState{}
19+
20+
var oldIpv4 = CurrentIPv4
21+
var fetchedIPv4 = fetchIPv4()
22+
if !oldIpv4.Equal(fetchedIPv4) {
23+
CurrentIPv4 = fetchedIPv4
24+
state.IPV4Change = true
25+
}
26+
27+
var oldIpv6 = CurrentIPv6
28+
var fetchedIPv6 = fetchIPv6()
29+
if !oldIpv6.Equal(fetchedIPv6) {
30+
CurrentIPv6 = fetchedIPv6
31+
state.IPV6Change = true
32+
}
33+
34+
c <- state
35+
time.Sleep(interval)
36+
}
37+
}

Diff for: main.go

+54-25
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package main
22

33
import (
4-
"bytes"
5-
"encoding/json"
6-
"fmt"
7-
"io/ioutil"
8-
"log"
9-
"net/http"
4+
"os"
5+
"os/signal"
6+
"syscall"
7+
8+
"github.com/rs/zerolog"
9+
"github.com/rs/zerolog/log"
10+
"github.com/spf13/viper"
11+
"gitlab.com/atomys-universe/dns-updater/pkg/connectors/ovh"
12+
"gitlab.com/atomys-universe/dns-updater/pkg/manager"
1013
)
1114

1215
const WebhookURL = "https://canary.discord.com/api/webhooks/858713820200566804/NbsedN-G2yzbtM2vM9TyKXODYe4Jw0HVtC_AcZxPk9yTsqA5LhBsAxsBo23SYFJ0hKmK"
@@ -16,33 +19,59 @@ type Content struct {
1619
Username string `json:"username"`
1720
}
1821

19-
func main() {
22+
func init() {
23+
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
24+
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
25+
if os.Getenv("DEBUG") == "true" {
26+
zerolog.SetGlobalLevel(zerolog.DebugLevel)
27+
} else {
28+
zerolog.SetGlobalLevel(zerolog.InfoLevel)
29+
}
2030

21-
log.Println("Getting current IP")
22-
resp, err := http.Get("https://ifconfig.co/ip")
23-
bodyBytes, err := ioutil.ReadAll(resp.Body)
31+
viper.SetConfigName("updater")
32+
viper.SetConfigType("yaml")
33+
viper.AddConfigPath("./config")
34+
viper.AutomaticEnv()
35+
err := viper.ReadInConfig()
2436
if err != nil {
25-
log.Fatal(err)
37+
log.Panic().Err(err).Msg("Fatal error on reading config file")
2638
}
39+
}
2740

28-
c := Content{
29-
Content: fmt.Sprintf("IP: %s", string(bodyBytes)),
30-
Username: "DNS Updater",
31-
}
41+
func main() {
42+
log.Info().Msg("DNS Updater starting...")
3243

33-
var jsonData []byte
34-
jsonData, err = json.Marshal(c)
35-
if err != nil {
36-
log.Fatal(err)
37-
}
44+
manager.RegisterConnector(ovh.New())
3845

39-
log.Println("Post to Discord")
40-
_, err = http.Post(WebhookURL, "application/json", bytes.NewReader(jsonData))
41-
if err != nil {
42-
log.Fatal(err)
46+
if err := manager.ValidateConfiguration(); err != nil {
47+
log.Fatal().Err(err).Msg("configuration is invalid")
4348
}
4449

45-
log.Println("Done !")
50+
go manager.Run()
51+
52+
log.Info().Msg("DNS Updater is running")
53+
c := make(chan os.Signal, 2)
54+
<-c
55+
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
56+
57+
// c := Content{
58+
// Content: fmt.Sprintf("IP: %s", string(bodyBytes)),
59+
// Username: "DNS Updater",
60+
// }
61+
62+
// var jsonData []byte
63+
// jsonData, err = json.Marshal(c)
64+
// if err != nil {
65+
// log.Fatal().Err(err).Msg("Cannot Marshall")
66+
// }
67+
68+
// log.Print("Post to Discord")
69+
// _, err = http.Post(WebhookURL, "application/json", bytes.NewReader(jsonData))
70+
// if err != nil {
71+
// log.Fatal().Err(err).Msg("Cannot post")
72+
// }
73+
74+
// log.Print("Done !")
4675
}
4776

4877
// https://discord.com/api/webhooks/858713820200566804/NbsedN-G2yzbtM2vM9TyKXODYe4Jw0HVtC_AcZxPk9yTsqA5LhBsAxsBo23SYFJ0hKmK

Diff for: pkg/connectors/ovh/ovh.go

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package ovh
2+
3+
import (
4+
"fmt"
5+
"net"
6+
"os"
7+
8+
"github.com/ovh/go-ovh/ovh"
9+
"github.com/rs/zerolog/log"
10+
"gitlab.com/atomys-universe/dns-updater/pkg/manager"
11+
)
12+
13+
var client *ovh.Client
14+
15+
type OvhConnector struct{}
16+
17+
func New() OvhConnector {
18+
return OvhConnector{}
19+
}
20+
21+
func (OvhConnector) Name() string {
22+
return "ovh"
23+
}
24+
25+
func (OvhConnector) Initialize() {
26+
if os.Getenv("OVH_APPLICATION_KEY") == "" || os.Getenv("OVH_APPLICATION_SECRET") == "" || os.Getenv("OVH_CONSUMER_KEY") == "" {
27+
log.Fatal().Msg("Missing env configuration for OVH connector")
28+
}
29+
30+
client, _ = ovh.NewClient(
31+
"ovh-eu",
32+
os.Getenv("OVH_APPLICATION_KEY"),
33+
os.Getenv("OVH_APPLICATION_SECRET"),
34+
os.Getenv("OVH_CONSUMER_KEY"),
35+
)
36+
}
37+
38+
type record struct {
39+
target string
40+
zone string
41+
id int
42+
fieldType string
43+
ttl int
44+
subDomain string
45+
}
46+
47+
func (c OvhConnector) UpdateDNS(domainName, subDomain string, fieldType manager.RecordType, ip net.IP) error {
48+
var recordIds = []int{}
49+
client.Get(fmt.Sprintf("/domain/zone/%s/record?fieldType=%s", domainName, fieldType), &recordIds)
50+
51+
for _, recordID := range recordIds {
52+
var rec = record{}
53+
client.Get(fmt.Sprintf("/domain/zone/%s/record/%d", domainName, recordID), &rec)
54+
55+
rec.target = ip.String()
56+
if err := client.Put(fmt.Sprintf("/domain/zone/%s/record/%d", domainName, recordID), &rec, nil); err != nil {
57+
return err
58+
}
59+
}
60+
61+
if err := client.Post(fmt.Sprintf("/domain/zone/%s/refresh", domainName), struct{ zoneName string }{domainName}, nil); err != nil {
62+
return err
63+
}
64+
65+
return nil
66+
}

Diff for: pkg/manager/config.go

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package manager
2+
3+
import (
4+
"errors"
5+
"time"
6+
7+
"github.com/rs/zerolog/log"
8+
"github.com/spf13/viper"
9+
)
10+
11+
type Configuration struct {
12+
IPFetchInterval time.Duration
13+
Records []*Record
14+
}
15+
16+
type Record struct {
17+
Connector string
18+
Domain string
19+
SubDomain string
20+
Type RecordType
21+
Interval time.Duration
22+
}
23+
24+
type RecordType string
25+
26+
const (
27+
TypeA RecordType = "A"
28+
TypeAAAA RecordType = "AAAA"
29+
)
30+
31+
var (
32+
Config = &Configuration{}
33+
ErrIncorrectType = errors.New("type defined is incorrect. Must be A or AAAA")
34+
ErrIncorrectInterval = errors.New("interval defined is incorrect. Must be longer or equal than 1 minute")
35+
)
36+
37+
/**
38+
* Validate the configuration file and her content
39+
*/
40+
func ValidateConfiguration() error {
41+
err := viper.Unmarshal(&Config)
42+
if err != nil {
43+
return err
44+
}
45+
46+
for _, record := range Config.Records {
47+
if !ConnectorIsRegistered(record.Connector) {
48+
return ErrConnectorNotFound
49+
}
50+
51+
switch record.Type {
52+
case TypeA, TypeAAAA:
53+
default:
54+
log.Error().Err(ErrIncorrectType).Str("domain", record.Domain).Str("type", string(record.Type)).Msg("Invalid configuration")
55+
return ErrIncorrectType
56+
}
57+
}
58+
59+
return nil
60+
}

0 commit comments

Comments
 (0)