Skip to content

Commit

Permalink
#29 Add optional parameter to set the domain mode
Browse files Browse the repository at this point in the history
  • Loading branch information
kutzilla committed Oct 17, 2023
1 parent de41f6c commit f3981e4
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 55 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ docker run kutzilla/hetzner-ddns example.com my-secret-api-token A

## Optional Parameters

* `-e RECORD_NAME` - The name of the DNS-record that DDNS updates should be applied to. This could be `sub` if you like to update the subdomain `sub.example.com` of `example.com`. The default value is `@` If you want to update multiple Records you can use a pattern. The pattern which can be used is `RECORD_NAME_<NAME>` e.g. `RECORD_NAME_EXAMPLE`, `RECORD_NAME` will be ignored the multi domain approach is used.
* `-e RECORD_NAME` - The name of the DNS-record that DDNS updates should be applied to. This could be `sub` if you like to update the subdomain `sub.example.com` of `example.com`. The default value is `@` If you want to update multiple Records you can use a pattern. This functionality only works if the `MULTIPLE_DOMAIN_MODE` is set to `true`.
* `-e CRON_EXPRESSION` - The cron expression of the DDNS update interval. The default is every 5 minutes - `*/5 * * * *`.
* `-e TTL` - The TTL (Time To Live) value specifies how long the record is valid before the nameservers are prompted to reload the zone file. The default is `86400`.
* `-e RECORD_NAME_<NAME>_TTL` - In case you have multiple records, you can specify the TTL per record referenced by `<NAME>` for example `RECORD_NAME_EXAMPLE_TTL`, if no TTL is specified the TTL will be `86400`. The `TTL` argument will be ignored if this parameter is used.
* `-e MULTIPLE_DOMAIN_MODE` - Sets the mode to update a single record or multiple records at once.


## Build
Expand Down
102 changes: 56 additions & 46 deletions pkg/conf/conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,42 @@ import (
)

const (
EnvZoneName = "ZONE_NAME"
EnvApiToken = "API_TOKEN"
EnvRecordType = "RECORD_TYPE"
EnvRecordName = "RECORD_NAME"
EnvCronExpression = "CRON_EXPRESSION"
EnvTimeToLive = "TTL"

DescZoneName = "The DNS zone that DDNS updates should be applied to."
DescApiToken = "Your Hetzner API token."
DescRecordType = "The record type of your zone. If your zone uses an IPv4 address use `A`. Use `AAAA` if it uses an IPv6 address."
DescRecordName = "The name of the DNS-record that DDNS updates should be applied to. This could be `sub` if you like to update the subdomain `sub.example.com` of `example.com`. The default value is `@`"
DescCronExpression = "The cron expression of the DDNS update interval. The default is every 5 minutes - `*/5 * * * *`"
DescTimeToLive = "Time to live of the record"

DefaultRecordKey = "default"
DefaultRecordName = "@"
DefaultRecordType = "A"
DefaultCronExpression = "*/5 * * * *"
DefaultTimeToLive = 86400
EnvZoneName = "ZONE_NAME"
EnvApiToken = "API_TOKEN"
EnvRecordType = "RECORD_TYPE"
EnvRecordName = "RECORD_NAME"
EnvCronExpression = "CRON_EXPRESSION"
EnvTimeToLive = "TTL"
EnvMultipleDomainMode = "MULTIPLE_DOMAIN_MODE"

DescZoneName = "The DNS zone that DDNS updates should be applied to."
DescApiToken = "Your Hetzner API token."
DescRecordType = "The record type of your zone. If your zone uses an IPv4 address use `A`. Use `AAAA` if it uses an IPv6 address."
DescRecordName = "The name of the DNS-record that DDNS updates should be applied to. This could be `sub` if you like to update the subdomain `sub.example.com` of `example.com`. The default value is `@`"
DescCronExpression = "The cron expression of the DDNS update interval. The default is every 5 minutes - `*/5 * * * *`"
DescTimeToLive = "Time to live of the record"
DescMultipleDomainMode = "Sets the mode to update a single record or multiple records at once"

DefaultRecordKey = "default"
DefaultRecordName = "@"
DefaultRecordType = "A"
DefaultCronExpression = "*/5 * * * *"
DefaultTimeToLive = 86400
DefaultMultipleDomainMode = false

IPv4 = "IPv4"
IPv6 = "IPv6"
IPv6RecordType = "AAAA"
)

type RecordConfig map[string]*RecordConf
type RecordConfig struct {
mode bool
records map[string]*RecordConf
}

type DynDnsConf struct {
DnsConf DnsConf
RecordConf map[string]*RecordConf
RecordConf RecordConfig
ProviderConf ProviderConf
CronConf CronConf
}
Expand Down Expand Up @@ -71,45 +77,46 @@ func (e *ArgumentMissingError) Error() string {
return "The mandatory argument " + e.argumentName + " is missing"
}

func setupRecordConfig(records RecordConfig) {
useDefaultConfig := true
envPrefix := fmt.Sprintf("%s_", EnvRecordName)
func setupRecordConfig(recordConf RecordConfig) {
flag.BoolVar(&recordConf.mode, EnvMultipleDomainMode, DefaultMultipleDomainMode, DescMultipleDomainMode)

for _, envRecord := range os.Environ() {
if strings.HasPrefix(envRecord, envPrefix) {
useDefaultConfig = false
envKey := strings.Split(envRecord, "=")[0]
if recordConf.mode {
envPrefix := fmt.Sprintf("%s_", EnvRecordName)
for _, envRecord := range os.Environ() {
if strings.HasPrefix(envRecord, envPrefix) {
envKey := strings.Split(envRecord, "=")[0]

if strings.HasSuffix(envKey, "_TTL") {
continue
}
if strings.HasSuffix(envKey, "_TTL") {
continue
}

if _, exists := records[envKey]; exists {
continue
}
if _, exists := recordConf.records[envKey]; exists {
continue
}

var record = &RecordConf{
RecordType: DefaultRecordType, // assume its ipv4, fix later after arg parse if not
}
records[envKey] = record
var record = &RecordConf{
RecordType: DefaultRecordType, // assume its ipv4, fix later after arg parse if not
}
recordConf.records[envKey] = record

flag.StringVar(&record.RecordName, envKey, DefaultRecordName, DescRecordName)
flag.IntVar(&record.TTL, fmt.Sprintf("%s_TTL", envKey), DefaultTimeToLive, DescTimeToLive)
flag.StringVar(&record.RecordName, envKey, DefaultRecordName, DescRecordName)
flag.IntVar(&record.TTL, fmt.Sprintf("%s_TTL", envKey), DefaultTimeToLive, DescTimeToLive)
}
}
}

if useDefaultConfig {
if !recordConf.mode {
var record = &RecordConf{
RecordType: DefaultRecordType,
}
flag.StringVar(&record.RecordName, EnvRecordName, DefaultRecordName, DescRecordName)
flag.IntVar(&record.TTL, EnvTimeToLive, DefaultTimeToLive, DescTimeToLive)
records[DefaultRecordKey] = record
recordConf.records[DefaultRecordKey] = record
}
}

func setRecordType(records RecordConfig, recordType string) {
for _, record := range records {
func setRecordType(recordConf RecordConfig, recordType string) {
for _, record := range recordConf.records {
record.RecordType = recordType
}
}
Expand All @@ -121,7 +128,10 @@ func Read() DynDnsConf {
flag.StringVar(&apiToken, EnvApiToken, apiToken, DescApiToken)
flag.StringVar(&recordType, EnvRecordType, recordType, DescRecordType)

records := make(map[string]*RecordConf)
records := RecordConfig{
mode: DefaultMultipleDomainMode,
records: make(map[string]*RecordConf),
}
setupRecordConfig(records)

var cronExpression = DefaultCronExpression
Expand Down Expand Up @@ -175,7 +185,7 @@ func validate(dynDnsConf DynDnsConf) (DynDnsConf, error) {
}

// Check record type
for _, record := range dynDnsConf.RecordConf {
for _, record := range dynDnsConf.RecordConf.records {
if record.RecordType == "" {
return dynDnsConf, &ArgumentMissingError{
argumentName: EnvRecordType,
Expand Down
34 changes: 26 additions & 8 deletions pkg/conf/conf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,24 @@ import (
"github.com/stretchr/testify/assert"
)

func TestSingleDomain(t *testing.T) {
assert := assert.New(t)

os.Setenv(EnvZoneName, "example.com")
os.Setenv(EnvApiToken, "abcdefghi1234567890")
os.Setenv(EnvRecordType, "A")

dynDnsConf := Read()

assert.Equal("@", dynDnsConf.RecordConf.records["default"].RecordName)
assert.Equal("A", dynDnsConf.RecordConf.records["default"].RecordType)
}

func TestReadMultipleDomains(t *testing.T) {
assert := assert.New(t)

os.Setenv(EnvMultipleDomainMode, "true")

os.Setenv(EnvZoneName, "example.com")
os.Setenv(EnvApiToken, "abcdefghi1234567890")
os.Setenv(EnvRecordType, "A")
Expand All @@ -23,18 +38,21 @@ func TestReadMultipleDomains(t *testing.T) {
os.Setenv(EnvCronExpression, "*/10 * * * *")

dynDnsConf := Read()

assert.Equal(true, dynDnsConf.RecordConf.mode)

// Check optional args
assert.NotEqual(DefaultRecordName, dynDnsConf.RecordConf["RECORD_NAME_TEST1"].RecordName)
assert.Equal("test1", dynDnsConf.RecordConf["RECORD_NAME_TEST1"].RecordName)
assert.NotEqual(DefaultRecordName, dynDnsConf.RecordConf.records["RECORD_NAME_TEST1"].RecordName)
assert.Equal("test1", dynDnsConf.RecordConf.records["RECORD_NAME_TEST1"].RecordName)

assert.NotEqual(DefaultTimeToLive, dynDnsConf.RecordConf["RECORD_NAME_TEST1"].TTL)
assert.Equal(4711, dynDnsConf.RecordConf["RECORD_NAME_TEST1"].TTL)
assert.NotEqual(DefaultTimeToLive, dynDnsConf.RecordConf.records["RECORD_NAME_TEST1"].TTL)
assert.Equal(4711, dynDnsConf.RecordConf.records["RECORD_NAME_TEST1"].TTL)

assert.NotEqual(DefaultRecordName, dynDnsConf.RecordConf["RECORD_NAME_TEST2"].RecordName)
assert.Equal("test2", dynDnsConf.RecordConf["RECORD_NAME_TEST2"].RecordName)
assert.NotEqual(DefaultRecordName, dynDnsConf.RecordConf.records["RECORD_NAME_TEST2"].RecordName)
assert.Equal("test2", dynDnsConf.RecordConf.records["RECORD_NAME_TEST2"].RecordName)

assert.NotEqual(DefaultTimeToLive, dynDnsConf.RecordConf["RECORD_NAME_TEST2"].TTL)
assert.Equal(1337, dynDnsConf.RecordConf["RECORD_NAME_TEST2"].TTL)
assert.NotEqual(DefaultTimeToLive, dynDnsConf.RecordConf.records["RECORD_NAME_TEST2"].TTL)
assert.Equal(1337, dynDnsConf.RecordConf.records["RECORD_NAME_TEST2"].TTL)

assert.NotEqual(DefaultCronExpression, dynDnsConf.CronConf.CronExpression)
assert.Equal("*/10 * * * *", dynDnsConf.CronConf.CronExpression)
Expand Down

0 comments on commit f3981e4

Please sign in to comment.