You've already forked ddns-updater
FEAT: Refactor allowing multiple DNS Providers
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
@@ -9,12 +9,14 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
defaultCheckInterval = 5 * 60
|
||||
|
||||
envKeyDnsToCheck = "CLOUDFLARE_DNS_TO_CHECK"
|
||||
defaultCheckInterval = 5 * 60
|
||||
envKeyDnsToCheck = "DNS_NAMES"
|
||||
envKeyPublicIpResolverTag = "PUBLIC_IP_RESOLVER"
|
||||
envKeyCloudflareApiKey = "CLOUDFLARE_API_KEY"
|
||||
envKeyCloudflareZone = "CLOUDFLARE_ZONE"
|
||||
envKeyDirectadminUser = "DA_USER"
|
||||
envKeyDirectadminKey = "DA_LOGIN_KEY"
|
||||
envKeyDirectadminUrl = "DA_LOGIN_URL"
|
||||
envKeyOnChangeComment = "ON_CHANGE_COMMENT"
|
||||
envKeyCheckIntervalSeconds = "CHECK_INTERVAL_SECONDS"
|
||||
envKeyNotifiers = "NOTIFIERS"
|
||||
@@ -23,6 +25,10 @@ const (
|
||||
type Config struct {
|
||||
DnsRecordsToCheck []string
|
||||
PublicIpResolverTag string
|
||||
DNSProviderTag string
|
||||
DirectadminUsername string
|
||||
DirectadminKey string
|
||||
DirectadminUrl string
|
||||
ApiToken string
|
||||
WebhookToken string
|
||||
CloudflareZone string
|
||||
@@ -32,12 +38,28 @@ type Config struct {
|
||||
}
|
||||
|
||||
func (c Config) Validate() error {
|
||||
if c.ApiToken == "" {
|
||||
return fmt.Errorf("empty api token env key %s", envKeyCloudflareApiKey)
|
||||
}
|
||||
switch c.DNSProviderTag {
|
||||
case "cloudflare":
|
||||
if c.ApiToken == "" {
|
||||
return fmt.Errorf("empty api token env key %s", envKeyCloudflareApiKey)
|
||||
}
|
||||
|
||||
if c.CloudflareZone == "" {
|
||||
return fmt.Errorf("empty zone in env key %s", envKeyCloudflareZone)
|
||||
// if c.CloudflareZone == "" {
|
||||
// return fmt.Errorf("empty zone in env key %s", envKeyCloudflareZone)
|
||||
// }
|
||||
|
||||
case "directadmin":
|
||||
if c.DirectadminUrl == "" {
|
||||
return fmt.Errorf("empty DirectAdmin URL env key %s", envKeyDirectadminUrl)
|
||||
}
|
||||
|
||||
if c.DirectadminUsername == "" {
|
||||
return fmt.Errorf("empty Username in env key %s", envKeyDirectadminUser)
|
||||
}
|
||||
|
||||
if c.DirectadminKey == "" {
|
||||
return fmt.Errorf("empty Login Key in env key %s", envKeyDirectadminKey)
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.DnsRecordsToCheck) == 0 {
|
||||
@@ -58,6 +80,9 @@ func NewConfig() Config {
|
||||
DnsRecordsToCheck: parseCommaDelimited(os.Getenv(envKeyDnsToCheck)),
|
||||
PublicIpResolverTag: os.Getenv(envKeyPublicIpResolverTag),
|
||||
ApiToken: os.Getenv(envKeyCloudflareApiKey),
|
||||
DirectadminUsername: os.Getenv(envKeyDirectadminUser),
|
||||
DirectadminKey: os.Getenv(envKeyDirectadminKey),
|
||||
DirectadminUrl: os.Getenv(envKeyDirectadminUrl),
|
||||
CloudflareZone: os.Getenv(envKeyCloudflareZone),
|
||||
OnChangeComment: os.Getenv(envKeyOnChangeComment),
|
||||
Notifiers: parseCommaDelimited(os.Getenv(envKeyNotifiers)),
|
||||
|
||||
25
internal/dns_providers/base_provider.go
Normal file
25
internal/dns_providers/base_provider.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package dns_providers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DNSProvider interface {
|
||||
UpdateRecord(hostname string, ip string, old_ip string) error
|
||||
}
|
||||
|
||||
type DomainParts struct {
|
||||
Name string
|
||||
Domain string
|
||||
}
|
||||
|
||||
func GetDomainParts(hostname string) *DomainParts {
|
||||
data := arrayToSlice(strings.Split(hostname, "."))
|
||||
|
||||
out := DomainParts{Name: data[0], Domain: strings.Join(data[1:], ".")}
|
||||
return &out
|
||||
}
|
||||
|
||||
func arrayToSlice(array []string) []string {
|
||||
return array[:]
|
||||
}
|
||||
85
internal/dns_providers/cloudflare.go
Normal file
85
internal/dns_providers/cloudflare.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package dns_providers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/cloudflare/cloudflare-go"
|
||||
"github.com/mkelcik/cloudflare-ddns-update/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
CloudflareTag = "cloudflare"
|
||||
)
|
||||
|
||||
type CloudflareProvider struct {
|
||||
Context context.Context
|
||||
Client *cloudflare.API
|
||||
Config internal.Config
|
||||
}
|
||||
|
||||
func (d *CloudflareProvider) UpdateRecord(hostname string, ip string, old_ip string) error {
|
||||
// old_ip is not required for Cloudflare updates
|
||||
domain_parts := GetDomainParts(hostname)
|
||||
zoneId := d.FindZoneIdByName(domain_parts.Domain)
|
||||
dnsRecords, err := d.GetDnsRecord(hostname, zoneId)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
update := cloudflare.UpdateDNSRecordParams{
|
||||
ID: dnsRecords[0].ID,
|
||||
Content: ip,
|
||||
}
|
||||
|
||||
if d.Config.OnChangeComment != "" {
|
||||
update.Comment = &d.Config.OnChangeComment
|
||||
}
|
||||
|
||||
_, err = d.Client.UpdateDNSRecord(d.Context, cloudflare.ZoneIdentifier(zoneId), update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CloudflareProvider) NewClient(api_token string) {
|
||||
api, err := cloudflare.NewWithAPIToken(api_token)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
d.Client = api
|
||||
}
|
||||
|
||||
func (d *CloudflareProvider) FindZoneIdByName(hostname string) string {
|
||||
// Fetch user details on the account
|
||||
zoneID, err := d.Client.ZoneIDByName(hostname)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return zoneID
|
||||
}
|
||||
|
||||
func (d *CloudflareProvider) GetDnsRecord(name string, zoneId string) ([]cloudflare.DNSRecord, error) {
|
||||
|
||||
params := cloudflare.ListDNSRecordsParams{
|
||||
Name: name,
|
||||
}
|
||||
|
||||
page, _, err := d.Client.ListDNSRecords(d.Context, cloudflare.ZoneIdentifier(zoneId), params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return page, nil
|
||||
}
|
||||
|
||||
func NewCloudflareProvider(ctx context.Context, config internal.Config) *CloudflareProvider {
|
||||
c := &CloudflareProvider{}
|
||||
c.Context = ctx
|
||||
c.Config = config
|
||||
c.NewClient(c.Config.ApiToken)
|
||||
return c
|
||||
}
|
||||
55
internal/dns_providers/directadmin.go
Normal file
55
internal/dns_providers/directadmin.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package dns_providers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/levelzerotechnology/directadmin-go"
|
||||
"github.com/mkelcik/cloudflare-ddns-update/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
DirectadminTag = "directadmin"
|
||||
)
|
||||
|
||||
type Directadmin struct {
|
||||
Client *directadmin.UserContext
|
||||
Context context.Context
|
||||
Config internal.Config
|
||||
}
|
||||
|
||||
func (d *Directadmin) UpdateRecord(hostname string, ip string, old_ip string) error {
|
||||
|
||||
result := GetDomainParts(hostname)
|
||||
|
||||
a := directadmin.DNSRecord{Name: result.Name, Ttl: 300, Type: "A", Value: old_ip}
|
||||
b := directadmin.DNSRecord{Name: result.Name, Ttl: 300, Type: "A", Value: ip}
|
||||
|
||||
err := d.Client.UpdateDNSRecord(result.Domain, a, b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Directadmin) NewClient(server_url string, username string, key string) {
|
||||
api, err := directadmin.New(server_url, 5*time.Second, false, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
userCtx, err := api.LoginAsUser(username, key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
d.Client = userCtx
|
||||
}
|
||||
|
||||
func NewDirectAdminProvider(ctx context.Context, config internal.Config) *Directadmin {
|
||||
c := &Directadmin{}
|
||||
c.Context = ctx
|
||||
c.Config = config
|
||||
c.NewClient(config.DirectadminUrl, config.DirectadminUsername, config.DirectadminKey)
|
||||
return c
|
||||
}
|
||||
29
internal/dns_resolver/dns_resolver.go
Normal file
29
internal/dns_resolver/dns_resolver.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package dns_resolver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ResolveHostname(host string, dns_resolver_ip string) net.IP {
|
||||
r := &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
d := net.Dialer{
|
||||
Timeout: time.Millisecond * time.Duration(10000),
|
||||
}
|
||||
return d.DialContext(ctx, network, dns_resolver_ip+":53")
|
||||
},
|
||||
}
|
||||
|
||||
ips, err := r.LookupHost(context.Background(), host)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Could not get IPs: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return net.ParseIP(ips[len(ips)-1])
|
||||
}
|
||||
Reference in New Issue
Block a user