From df6842b06072618d3813cc9560522de1729f77f4 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 10 Jul 2023 01:22:24 +0000 Subject: [PATCH] Add a TTL model This unifies the logic for handling the dnscontrol-default TTL, and frees up the TTL value 0 for use by providers (e.g. Linode, which uses it as its sentinel for its default TTL). Closes StackExchange/dnscontrol#2444. --- commands/getZones.go | 31 +++++++------- commands/r53_test.go | 8 ++-- integrationTest/integration_test.go | 4 +- models/dns.go | 42 +++++++++++++++++++ models/dns_test.go | 10 +++-- models/dnsrr.go | 2 +- models/record.go | 16 +++---- models/record_test.go | 4 +- models/target.go | 2 +- pkg/diff/diff_test.go | 4 +- pkg/diff2/analyze.go | 2 +- pkg/diff2/analyze_test.go | 32 +++++++------- pkg/diff2/compareconfig.go | 4 +- pkg/diff2/groupsort.go | 2 +- pkg/diff2/groupsort_test.go | 4 +- pkg/js/helpers.js | 10 ++--- pkg/js/js_test.go | 3 +- pkg/nameservers/nameservers.go | 4 +- pkg/normalize/validate.go | 10 ++--- pkg/normalize/validate_test.go | 32 +++++++------- pkg/prettyzone/prettyzone.go | 25 +++++------ pkg/prettyzone/prettyzone_test.go | 24 +++++------ .../akamaiedgedns/akamaiEdgeDnsService.go | 4 +- providers/autodns/autoDnsProvider.go | 28 ++++++------- providers/azuredns/azureDnsProvider.go | 30 ++++++------- providers/bind/bindProvider.go | 2 +- providers/bind/soa.go | 16 ++++++- providers/cloudflare/cloudflareProvider.go | 22 +++++----- providers/cloudflare/rest.go | 18 ++++---- providers/cloudns/cloudnsProvider.go | 6 +-- providers/cscglobal/convert.go | 16 +++---- providers/cscglobal/dns.go | 4 +- providers/desec/convert.go | 10 ++--- providers/desec/desecProvider.go | 4 +- .../digitalocean/digitaloceanProvider.go | 4 +- providers/dnsimple/dnsimpleProvider.go | 6 +-- providers/dnsmadeeasy/dnsMadeEasyProvider.go | 2 +- providers/dnsmadeeasy/types.go | 4 +- providers/domainnameshop/convert.go | 4 +- providers/domainnameshop/dns.go | 2 +- providers/exoscale/exoscaleProvider.go | 10 ++--- providers/gandiv5/convert.go | 10 ++--- providers/gandiv5/gandi_v5Provider.go | 8 ++-- providers/gcloud/gcloudProvider.go | 4 +- providers/gcore/convert.go | 10 ++--- providers/hedns/hednsProvider.go | 4 +- providers/hetzner/types.go | 4 +- providers/hexonet/records.go | 4 +- providers/hostingde/hostingdeProvider.go | 24 ++++++++--- providers/hostingde/types.go | 4 +- providers/inwx/inwxProvider.go | 4 +- providers/linode/linodeProvider.go | 8 ++-- providers/loopia/convert.go | 4 +- providers/loopia/convert_test.go | 4 +- providers/loopia/loopiaProvider.go | 8 ++-- providers/luadns/api.go | 6 +-- providers/msdns/convert.go | 2 +- providers/msdns/corrections.go | 2 +- providers/msdns/naptr.go | 2 +- providers/msdns/powershell.go | 4 +- providers/namecheap/namecheapProvider.go | 4 +- providers/namedotcom/records.go | 4 +- providers/netcup/netcupProvider.go | 2 +- providers/netcup/types.go | 2 +- providers/netlify/netlifyProvider.go | 4 +- providers/ns1/ns1Provider.go | 4 +- providers/oracle/oracleProvider.go | 8 ++-- providers/ovh/ovhProvider.go | 6 +-- providers/ovh/protocol.go | 4 +- providers/packetframe/packetframeProvider.go | 4 +- providers/porkbun/porkbunProvider.go | 6 +-- providers/powerdns/convert.go | 2 +- providers/powerdns/diff.go | 7 ++-- providers/route53/route53Provider.go | 8 ++-- providers/rwth/convert.go | 2 +- providers/softlayer/softlayerProvider.go | 6 +-- providers/transip/transipProvider.go | 4 +- providers/vultr/vultrProvider.go | 4 +- 78 files changed, 367 insertions(+), 297 deletions(-) diff --git a/commands/getZones.go b/commands/getZones.go index 89d39da918..180e2517a0 100644 --- a/commands/getZones.go +++ b/commands/getZones.go @@ -227,12 +227,12 @@ func GetZone(args GetZoneArgs) error { for i, recs := range zoneRecs { zoneName := zones[i] - z := prettyzone.PrettySort(recs, zoneName, 0, nil) + z := prettyzone.PrettySort(recs, zoneName, models.EmptyTTL(), nil) switch args.OutputFormat { case "zone": fmt.Fprintf(w, "$ORIGIN %s.\n", zoneName) - prettyzone.WriteZoneFileRC(w, z.Records, zoneName, uint32(args.DefaultTTL), nil) + prettyzone.WriteZoneFileRC(w, z.Records, zoneName, models.NewTTL(uint32(args.DefaultTTL)), nil) fmt.Fprintln(w) case "js", "djs": @@ -243,12 +243,15 @@ func GetZone(args GetZoneArgs) error { fmt.Fprintf(w, `D("%s", REG_CHANGEME%s`, zoneName, sep) var o []string o = append(o, fmt.Sprintf("DnsProvider(%s)", dspVariableName)) - defaultTTL := uint32(args.DefaultTTL) - if defaultTTL == 0 { + defaultTTL := models.EmptyTTL() + if args.DefaultTTL != 0 { + defaultTTL = models.NewTTL(uint32(args.DefaultTTL)) + } + if !defaultTTL.IsSet() { defaultTTL = prettyzone.MostCommonTTL(recs) } - if defaultTTL != models.DefaultTTL && defaultTTL != 0 { - o = append(o, fmt.Sprintf("DefaultTTL(%d)", defaultTTL)) + if defaultTTL.IsSet() { + o = append(o, fmt.Sprintf("DefaultTTL(%d)", defaultTTL.Value())) } for _, rec := range recs { if (rec.Type == "CNAME") && (rec.Name == "@") { @@ -287,7 +290,7 @@ func GetZone(args GetZoneArgs) error { } fmt.Fprintf(w, "%s\t%s\t%d\tIN\t%s\t%s%s\n", - rec.NameFQDN, rec.Name, rec.TTL, rec.Type, rec.GetTargetCombined(), cfproxy) + rec.NameFQDN, rec.Name, rec.TTL.Value(), rec.Type, rec.GetTargetCombined(), cfproxy) } default: @@ -307,15 +310,15 @@ func jsonQuoted(i string) string { return string(b) } -func formatDsl(zonename string, rec *models.RecordConfig, defaultTTL uint32) string { +func formatDsl(zonename string, rec *models.RecordConfig, defaultTTL models.TTL) string { target := rec.GetTargetCombined() - ttl := uint32(0) + ttl := models.EmptyTTL() ttlop := "" - if rec.TTL != defaultTTL && rec.TTL != 0 { + if rec.TTL != defaultTTL && rec.TTL.IsSet() { ttl = rec.TTL - ttlop = fmt.Sprintf(", TTL(%d)", ttl) + ttlop = fmt.Sprintf(", TTL(%d)", ttl.Value()) } cfproxy := "" @@ -386,7 +389,7 @@ func makeCaa(rec *models.RecordConfig, ttlop string) string { // TODO(tlim): Generate a CAA_BUILDER() instead? } -func makeR53alias(rec *models.RecordConfig, ttl uint32) string { +func makeR53alias(rec *models.RecordConfig, ttl models.TTL) string { items := []string{ "'" + rec.Name + "'", "'" + rec.R53Alias["type"] + "'", @@ -395,8 +398,8 @@ func makeR53alias(rec *models.RecordConfig, ttl uint32) string { if z, ok := rec.R53Alias["zone_id"]; ok { items = append(items, "R53_ZONE('"+z+"')") } - if ttl != 0 { - items = append(items, fmt.Sprintf("TTL(%d)", ttl)) + if ttl.IsSet() { + items = append(items, fmt.Sprintf("TTL(%d)", ttl.Value())) } return rec.Type + "(" + strings.Join(items, ", ") + ")" } diff --git a/commands/r53_test.go b/commands/r53_test.go index a856b94947..9b975550e1 100644 --- a/commands/r53_test.go +++ b/commands/r53_test.go @@ -17,7 +17,7 @@ func TestR53Test_1(t *testing.T) { rec.R53Alias = make(map[string]string) rec.R53Alias["type"] = "A" w := `R53_ALIAS('foo', 'A', 'bar')` - if g := makeR53alias(&rec, 0); g != w { + if g := makeR53alias(&rec, models.EmptyTTL()); g != w { t.Errorf("makeR53alias failure: got `%s` want `%s`", g, w) } } @@ -32,7 +32,7 @@ func TestR53Test_1ttl(t *testing.T) { rec.R53Alias = make(map[string]string) rec.R53Alias["type"] = "A" w := `R53_ALIAS('foo', 'A', 'bar', TTL(321))` - if g := makeR53alias(&rec, 321); g != w { + if g := makeR53alias(&rec, models.NewTTL(321)); g != w { t.Errorf("makeR53alias failure: got `%s` want `%s`", g, w) } } @@ -48,7 +48,7 @@ func TestR53Test_2(t *testing.T) { rec.R53Alias["type"] = "A" rec.R53Alias["zone_id"] = "blarg" w := `R53_ALIAS('foo', 'A', 'bar', R53_ZONE('blarg'))` - if g := makeR53alias(&rec, 0); g != w { + if g := makeR53alias(&rec, models.EmptyTTL()); g != w { t.Errorf("makeR53alias failure: got `%s` want `%s`", g, w) } } @@ -64,7 +64,7 @@ func TestR53Test_2ttl(t *testing.T) { rec.R53Alias["type"] = "A" rec.R53Alias["zone_id"] = "blarg" w := `R53_ALIAS('foo', 'A', 'bar', R53_ZONE('blarg'), TTL(123))` - if g := makeR53alias(&rec, 123); g != w { + if g := makeR53alias(&rec, models.NewTTL(123)); g != w { t.Errorf("makeR53alias failure: got `%s` want `%s`", g, w) } } diff --git a/integrationTest/integration_test.go b/integrationTest/integration_test.go index 84ea112892..5788d5fe01 100644 --- a/integrationTest/integration_test.go +++ b/integrationTest/integration_test.go @@ -579,7 +579,7 @@ func loc(name string, d1 uint8, m1 uint8, s1 float32, ns string, func makeRec(name, target, typ string) *models.RecordConfig { r := &models.RecordConfig{ Type: typ, - TTL: 300, + TTL: models.NewTTL(300), } SetLabel(r, name, "**current-domain**") r.SetTarget(target) @@ -734,7 +734,7 @@ func txt(name, target string) *models.RecordConfig { // func (r *models.RecordConfig) ttl(t uint32) *models.RecordConfig { func ttl(r *models.RecordConfig, t uint32) *models.RecordConfig { - r.TTL = t + r.TTL = models.NewTTL(t) return r } diff --git a/models/dns.go b/models/dns.go index a3dd1cf3a2..6b7d3c3e62 100644 --- a/models/dns.go +++ b/models/dns.go @@ -9,6 +9,48 @@ import ( // DefaultTTL is applied to any DNS record without an explicit TTL. const DefaultTTL = uint32(300) +// TTL implements an optional TTL field, with a default of DefaultTTL. +type TTL struct { + value *uint32 +} + +func NewTTL(ttl uint32) TTL { + value := new(uint32) + *value = ttl + return TTL{ + value, + } +} + +// EmptyTTL returns a new TTL without an explicit value. +func EmptyTTL() TTL { + return TTL{ + value: nil, + } +} + +func (ttl TTL) IsSet() bool { + return ttl.value != nil +} + +func (ttl TTL) Value() uint32 { + if ttl.IsSet() { + return *ttl.value + } else { + return DefaultTTL + } +} + +func (ttl *TTL) ValueRef() *uint32 { + if ttl.IsSet() { + return ttl.value + } else { + defaultTTL := new(uint32) + *defaultTTL = DefaultTTL + return defaultTTL + } +} + // DNSConfig describes the desired DNS configuration, usually loaded from dnsconfig.js. type DNSConfig struct { Registrars []*RegistrarConfig `json:"registrars"` diff --git a/models/dns_test.go b/models/dns_test.go index 9879e924fb..32453242c4 100644 --- a/models/dns_test.go +++ b/models/dns_test.go @@ -1,6 +1,8 @@ package models -import "testing" +import ( + "testing" +) func TestRR(t *testing.T) { experiment := RecordConfig{ @@ -8,7 +10,7 @@ func TestRR(t *testing.T) { Name: "foo", NameFQDN: "foo.example.com", target: "1.2.3.4", - TTL: 0, + TTL: EmptyTTL(), MxPreference: 0, } expected := "foo.example.com.\t300\tIN\tA\t1.2.3.4" @@ -22,7 +24,7 @@ func TestRR(t *testing.T) { Name: "@", NameFQDN: "example.com", target: "mailto:test@example.com", - TTL: 300, + TTL: NewTTL(300), CaaTag: "iodef", CaaFlag: 1, } @@ -37,7 +39,7 @@ func TestRR(t *testing.T) { Name: "@", NameFQDN: "_443._tcp.example.com", target: "abcdef0123456789", - TTL: 300, + TTL: NewTTL(300), TlsaUsage: 0, TlsaSelector: 0, TlsaMatchingType: 1, diff --git a/models/dnsrr.go b/models/dnsrr.go index 2361c7f383..40b669c41d 100644 --- a/models/dnsrr.go +++ b/models/dnsrr.go @@ -61,7 +61,7 @@ func RRtoRC(rr dns.RR, origin string) (RecordConfig, error) { header := rr.Header() rc := new(RecordConfig) rc.Type = dns.TypeToString[header.Rrtype] - rc.TTL = header.Ttl + rc.TTL = NewTTL(header.Ttl) rc.Original = rr rc.SetLabelFromFQDN(strings.TrimSuffix(header.Name, "."), origin) var err error diff --git a/models/record.go b/models/record.go index 80b206223b..22134ec0e7 100644 --- a/models/record.go +++ b/models/record.go @@ -89,7 +89,7 @@ type RecordConfig struct { SubDomain string `json:"subdomain,omitempty"` NameFQDN string `json:"-"` // Must end with ".$origin". See above. target string // If a name, must end with "." - TTL uint32 `json:"ttl,omitempty"` + TTL TTL `json:"ttl,omitempty"` Metadata map[string]string `json:"meta,omitempty"` Original interface{} `json:"-"` // Store pointer to provider-specific record object. Used in diffing. @@ -158,7 +158,7 @@ func (rc *RecordConfig) UnmarshalJSON(b []byte) error { SubDomain string `json:"subdomain,omitempty"` NameFQDN string `json:"-"` // Must end with ".$origin". See above. target string // If a name, must end with "." - TTL uint32 `json:"ttl,omitempty"` + TTL *uint32 `json:"ttl,omitempty"` Metadata map[string]string `json:"meta,omitempty"` Original interface{} `json:"-"` // Store pointer to provider-specific record object. Used in diffing. @@ -212,6 +212,9 @@ func (rc *RecordConfig) UnmarshalJSON(b []byte) error { copier.CopyWithOption(&rc, &recj, copier.Option{IgnoreEmpty: true, DeepCopy: true}) // Set each unexported field. rc.SetTarget(recj.Target) + if recj.TTL != nil { + rc.TTL = NewTTL(*recj.TTL) + } // Some sanity checks: if recj.Type != rc.Type { @@ -321,10 +324,10 @@ func (rc *RecordConfig) ToDiffable(extraMaps ...map[string]string) string { var content string switch rc.Type { case "SOA": - content = fmt.Sprintf("%s %v %d %d %d %d ttl=%d", rc.target, rc.SoaMbox, rc.SoaRefresh, rc.SoaRetry, rc.SoaExpire, rc.SoaMinttl, rc.TTL) + content = fmt.Sprintf("%s %v %d %d %d %d ttl=%d", rc.target, rc.SoaMbox, rc.SoaRefresh, rc.SoaRetry, rc.SoaExpire, rc.SoaMinttl, rc.TTL.Value()) // SoaSerial is not used in comparison default: - content = fmt.Sprintf("%v ttl=%d", rc.GetTargetCombined(), rc.TTL) + content = fmt.Sprintf("%v ttl=%d", rc.GetTargetCombined(), rc.TTL.Value()) } for _, valueMap := range extraMaps { // sort the extra values map keys to perform a deterministic @@ -373,10 +376,7 @@ func (rc *RecordConfig) ToRR() dns.RR { rr.Header().Name = rc.NameFQDN + "." rr.Header().Rrtype = rdtype rr.Header().Class = dns.ClassINET - rr.Header().Ttl = rc.TTL - if rc.TTL == 0 { - rr.Header().Ttl = DefaultTTL - } + rr.Header().Ttl = rc.TTL.Value() // Fill in the data. switch rdtype { // #rtype_variations diff --git a/models/record_test.go b/models/record_test.go index f4015197ec..db752bb2ae 100644 --- a/models/record_test.go +++ b/models/record_test.go @@ -146,7 +146,7 @@ func TestRecordConfig_Copy(t *testing.T) { SubDomain: "sub", NameFQDN: "namef", target: "targette", - TTL: 12345, + TTL: NewTTL(12345), Metadata: map[string]string{"me": "ah", "da": "ta"}, MxPreference: 123, SrvPriority: 223, @@ -189,7 +189,7 @@ func TestRecordConfig_Copy(t *testing.T) { SubDomain: tt.fields.SubDomain, NameFQDN: tt.fields.NameFQDN, target: tt.fields.target, - TTL: tt.fields.TTL, + TTL: NewTTL(tt.fields.TTL), Metadata: tt.fields.Metadata, MxPreference: tt.fields.MxPreference, SrvPriority: tt.fields.SrvPriority, diff --git a/models/target.go b/models/target.go index 6d3c386204..6b7741741c 100644 --- a/models/target.go +++ b/models/target.go @@ -99,7 +99,7 @@ func (rc *RecordConfig) GetTargetSortable() string { // GetTargetDebug returns a string with the various fields spelled out. func (rc *RecordConfig) GetTargetDebug() string { - content := fmt.Sprintf("%s %s %s %d", rc.Type, rc.NameFQDN, rc.target, rc.TTL) + content := fmt.Sprintf("%s %s %s %d", rc.Type, rc.NameFQDN, rc.target, rc.TTL.Value()) switch rc.Type { // #rtype_variations case "A", "AAAA", "CNAME", "NS", "PTR", "TXT", "AKAMAICDN": // Nothing special. diff --git a/pkg/diff/diff_test.go b/pkg/diff/diff_test.go index 6ea1832458..4584bb2471 100644 --- a/pkg/diff/diff_test.go +++ b/pkg/diff/diff_test.go @@ -14,7 +14,7 @@ func myRecord(s string) *models.RecordConfig { ttl, _ := strconv.ParseUint(parts[2], 10, 32) r := &models.RecordConfig{ Type: parts[1], - TTL: uint32(ttl), + TTL: models.NewTTL(uint32(ttl)), Metadata: map[string]string{}, } r.SetLabel(parts[0], "example.com") @@ -72,7 +72,7 @@ func TestUnchangedWithAddition(t *testing.T) { // s stringifies a RecordConfig for testing purposes. func s(rc *models.RecordConfig) string { - return fmt.Sprintf("%s %s %d %s", rc.GetLabel(), rc.Type, rc.TTL, rc.GetTargetCombined()) + return fmt.Sprintf("%s %s %d %s", rc.GetLabel(), rc.Type, rc.TTL.Value(), rc.GetTargetCombined()) } func TestOutOfOrderRecords(t *testing.T) { diff --git a/pkg/diff2/analyze.go b/pkg/diff2/analyze.go index ccb17d5f21..752b65661e 100644 --- a/pkg/diff2/analyze.go +++ b/pkg/diff2/analyze.go @@ -231,7 +231,7 @@ func humanDiff(a, b targetConfig) string { } // Just the TTLs are different: - return fmt.Sprintf("%s ttl=(%d->%d)", a.comparableNoTTL, a.rec.TTL, b.rec.TTL) + return fmt.Sprintf("%s ttl=(%d->%d)", a.comparableNoTTL, a.rec.TTL.Value(), b.rec.TTL.Value()) } func diffTargets(existing, desired []targetConfig) ChangeList { diff --git a/pkg/diff2/analyze_test.go b/pkg/diff2/analyze_test.go index 8093904488..659e4cf2ce 100644 --- a/pkg/diff2/analyze_test.go +++ b/pkg/diff2/analyze_test.go @@ -18,22 +18,22 @@ func init() { color.NoColor = true } -var testDataAA1234 = makeRec("laba", "A", "1.2.3.4") // [0] -var testDataAA5678 = makeRec("laba", "A", "5.6.7.8") // -var testDataAA1234ttl700 = makeRecTTL("laba", "A", "1.2.3.4", 700) // -var testDataAA5678ttl700 = makeRecTTL("laba", "A", "5.6.7.8", 700) // -var testDataAMX10a = makeRec("laba", "MX", "10 laba") // [1] -var testDataCCa = makeRec("labc", "CNAME", "laba") // [2] -var testDataEA15 = makeRec("labe", "A", "10.10.10.15") // [3] -var e4 = makeRec("labe", "A", "10.10.10.16") // [4] -var e5 = makeRec("labe", "A", "10.10.10.17") // [5] -var e6 = makeRec("labe", "A", "10.10.10.18") // [6] -var e7 = makeRec("labg", "NS", "10.10.10.15") // [7] -var e8 = makeRec("labg", "NS", "10.10.10.16") // [8] -var e9 = makeRec("labg", "NS", "10.10.10.17") // [9] -var e10 = makeRec("labg", "NS", "10.10.10.18") // [10] -var e11mx = makeRec("labh", "MX", "22 ttt") // [11] -var e11 = makeRec("labh", "CNAME", "labd") // [11] +var testDataAA1234 = makeRec("laba", "A", "1.2.3.4") // [0] +var testDataAA5678 = makeRec("laba", "A", "5.6.7.8") // +var testDataAA1234ttl700 = makeRecTTL("laba", "A", "1.2.3.4", models.NewTTL(700)) // +var testDataAA5678ttl700 = makeRecTTL("laba", "A", "5.6.7.8", models.NewTTL(700)) // +var testDataAMX10a = makeRec("laba", "MX", "10 laba") // [1] +var testDataCCa = makeRec("labc", "CNAME", "laba") // [2] +var testDataEA15 = makeRec("labe", "A", "10.10.10.15") // [3] +var e4 = makeRec("labe", "A", "10.10.10.16") // [4] +var e5 = makeRec("labe", "A", "10.10.10.17") // [5] +var e6 = makeRec("labe", "A", "10.10.10.18") // [6] +var e7 = makeRec("labg", "NS", "10.10.10.15") // [7] +var e8 = makeRec("labg", "NS", "10.10.10.16") // [8] +var e9 = makeRec("labg", "NS", "10.10.10.17") // [9] +var e10 = makeRec("labg", "NS", "10.10.10.18") // [10] +var e11mx = makeRec("labh", "MX", "22 ttt") // [11] +var e11 = makeRec("labh", "CNAME", "labd") // [11] var testDataApexMX1aaa = makeRec("", "MX", "1 aaa") var testDataAA1234clone = makeRec("laba", "A", "1.2.3.4") // [0'] diff --git a/pkg/diff2/compareconfig.go b/pkg/diff2/compareconfig.go index 0b75d32a22..160e801392 100644 --- a/pkg/diff2/compareconfig.go +++ b/pkg/diff2/compareconfig.go @@ -216,7 +216,7 @@ func mkCompareBlobs(rc *models.RecordConfig, f func(*models.RecordConfig) string } lenWithoutTTL := len(comp) - compFull := comp + fmt.Sprintf(" ttl=%d", rc.TTL) + compFull := comp + fmt.Sprintf(" ttl=%d", rc.TTL.Value()) return compFull[:lenWithoutTTL], compFull } @@ -230,7 +230,7 @@ func (cc *CompareConfig) addRecords(recs models.Records, storeInExisting bool) { // of the same label+rtype are grouped. We use PrettySort because it works, // has been extensively tested, and assures that the ChangeList will // be in an order that is pretty to look at. - z := prettyzone.PrettySort(recs, cc.origin, 0, nil) + z := prettyzone.PrettySort(recs, cc.origin, models.EmptyTTL(), nil) for _, rec := range z.Records { diff --git a/pkg/diff2/groupsort.go b/pkg/diff2/groupsort.go index ea282ef7a8..dd2369f2d8 100644 --- a/pkg/diff2/groupsort.go +++ b/pkg/diff2/groupsort.go @@ -19,7 +19,7 @@ func groupbyRSet(recs models.Records, origin string) []recset { // Sort the NameFQDN to a consistent order. The actual sort methodology // doesn't matter as long as equal values are adjacent. // Use the PrettySort ordering so that the records are extra pretty. - pretty := prettyzone.PrettySort(recs, origin, 0, nil) + pretty := prettyzone.PrettySort(recs, origin, models.EmptyTTL(), nil) recs = pretty.Records var result []recset diff --git a/pkg/diff2/groupsort_test.go b/pkg/diff2/groupsort_test.go index cca1e416c3..b5738febbf 100644 --- a/pkg/diff2/groupsort_test.go +++ b/pkg/diff2/groupsort_test.go @@ -9,12 +9,12 @@ import ( func makeRec(label, rtype, content string) *models.RecordConfig { origin := "f.com" - r := models.RecordConfig{TTL: 300} + r := models.RecordConfig{TTL: models.NewTTL(300)} r.SetLabel(label, origin) r.PopulateFromString(rtype, content, origin) return &r } -func makeRecTTL(label, rtype, content string, ttl uint32) *models.RecordConfig { +func makeRecTTL(label, rtype, content string, ttl models.TTL) *models.RecordConfig { r := makeRec(label, rtype, content) r.TTL = ttl return r diff --git a/pkg/js/helpers.js b/pkg/js/helpers.js index 6581cfaf20..782342464c 100644 --- a/pkg/js/helpers.js +++ b/pkg/js/helpers.js @@ -106,7 +106,7 @@ function newDomain(name, registrar) { records: [], recordsabsent: [], dnsProviders: {}, - defaultTTL: 0, + defaultTTL: null, nameservers: [], ignored_names: [], ignored_targets: [], @@ -1310,7 +1310,7 @@ function LOC_builder_push(value, dms) { // rawloc = ""; // Generate a LOC record with the metaparameters. - if (value.ttl) { + if (value.ttl !== null) { if (value.alt) r.push( LOC( @@ -1426,7 +1426,7 @@ function SPF_BUILDER(value) { // Only add the raw spf record if it isn't an empty string if (value.raw !== '') { rp = {}; - if (value.ttl) { + if (value.ttl !== null) { r.push(TXT(value.raw, rawspf, rp, TTL(value.ttl))); } else { r.push(TXT(value.raw, rawspf, rp)); @@ -1448,7 +1448,7 @@ function SPF_BUILDER(value) { } // Generate a TXT record with the metaparameters. - if (value.ttl) { + if (value.ttl !== null) { r.push(TXT(value.label, rawspf, p, TTL(value.ttl))); } else { r.push(TXT(value.label, rawspf, p)); @@ -1649,7 +1649,7 @@ function DMARC_BUILDER(value) { record.push('ri=' + value.reportInterval); } - if (value.ttl) { + if (value.ttl !== null) { return TXT(label, record.join('; '), TTL(value.ttl)); } return TXT(label, record.join('; ')); diff --git a/pkg/js/js_test.go b/pkg/js/js_test.go index 9b1ef438ad..eebeea832c 100644 --- a/pkg/js/js_test.go +++ b/pkg/js/js_test.go @@ -9,6 +9,7 @@ import ( "testing" "unicode" + "github.com/StackExchange/dnscontrol/v4/models" "github.com/StackExchange/dnscontrol/v4/pkg/normalize" "github.com/StackExchange/dnscontrol/v4/pkg/prettyzone" "github.com/StackExchange/dnscontrol/v4/providers" @@ -107,7 +108,7 @@ func TestParsedFiles(t *testing.T) { // Generate the zonefile var buf bytes.Buffer - err = prettyzone.WriteZoneFileRC(&buf, dc.Records, dc.Name, 300, nil) + err = prettyzone.WriteZoneFileRC(&buf, dc.Records, dc.Name, models.NewTTL(300), nil) if err != nil { t.Fatal(err) } diff --git a/pkg/nameservers/nameservers.go b/pkg/nameservers/nameservers.go index 6df91f8e6f..ec234d7800 100644 --- a/pkg/nameservers/nameservers.go +++ b/pkg/nameservers/nameservers.go @@ -56,13 +56,13 @@ func DetermineNameserversForProviders(dc *models.DomainConfig, providers []*mode // AddNSRecords creates NS records on a domain corresponding to the nameservers specified. func AddNSRecords(dc *models.DomainConfig) { - ttl := uint32(300) + ttl := models.EmptyTTL() if ttls, ok := dc.Metadata["ns_ttl"]; ok { t, err := strconv.ParseUint(ttls, 10, 32) if err != nil { fmt.Printf("WARNING: ns_ttl for %s (%s) is not a valid int", dc.Name, ttls) } else { - ttl = uint32(t) + ttl = models.NewTTL(uint32(t)) } } for _, ns := range dc.Nameservers { diff --git a/pkg/normalize/validate.go b/pkg/normalize/validate.go index 0a52cad1a7..6a5c0f567e 100644 --- a/pkg/normalize/validate.go +++ b/pkg/normalize/validate.go @@ -243,7 +243,7 @@ func transformCNAME(target, oldDomain, newDomain string) string { } // import_transform imports the records of one zone into another, modifying records along the way. -func importTransform(srcDomain, dstDomain *models.DomainConfig, transforms []transform.IPConversion, ttl uint32) error { +func importTransform(srcDomain, dstDomain *models.DomainConfig, transforms []transform.IPConversion, ttl models.TTL) error { // Read srcDomain.Records, transform, and append to dstDomain.Records: // 1. Skip any that aren't A or CNAMEs. // 2. Append destDomainname to the end of the label. @@ -258,7 +258,7 @@ func importTransform(srcDomain, dstDomain *models.DomainConfig, transforms []tra rec2, _ := rec.Copy() newlabel := rec2.GetLabelFQDN() rec2.SetLabel(newlabel, dstDomain.Name) - if ttl != 0 { + if ttl.IsSet() { rec2.TTL = ttl } return rec2 @@ -349,10 +349,6 @@ func ValidateAndNormalizeConfig(config *models.DNSConfig) (errs []error) { models.PostProcessRecords(domain.Records) for _, rec := range domain.Records { - if rec.TTL == 0 { - rec.TTL = models.DefaultTTL - } - // Canonicalize Label: if rec.GetLabel() == (domain.Name + ".") { // If label == ${domain}DOT, change to "@" @@ -601,7 +597,7 @@ func checkRecordSetHasMultipleTTLs(records []*models.RecordConfig) (errs []error m := make(map[string]map[uint32]map[string]bool) for _, r := range records { label := r.GetLabelFQDN() - ttl := r.TTL + ttl := r.TTL.Value() rtype := r.Type if _, ok := m[label]; !ok { diff --git a/pkg/normalize/validate_test.go b/pkg/normalize/validate_test.go index 528827f6ce..5e08ac2979 100644 --- a/pkg/normalize/validate_test.go +++ b/pkg/normalize/validate_test.go @@ -315,8 +315,8 @@ func TestCheckDuplicates(t *testing.T) { makeRC("aaa", "example.com", "uniquestring.com.", models.RecordConfig{Type: "NS"}), makeRC("aaa", "example.com", "uniquestring.com.", models.RecordConfig{Type: "PTR"}), // The only difference is the TTL. - makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: 111}), - makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: 222}), + makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: models.NewTTL(111)}), + makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: models.NewTTL(222)}), // Three records each with a different target. makeRC("@", "example.com", "ns1.foo.com.", models.RecordConfig{Type: "NS"}), makeRC("@", "example.com", "ns2.foo.com.", models.RecordConfig{Type: "NS"}), @@ -357,8 +357,8 @@ func TestCheckDuplicates_dup_ns(t *testing.T) { func TestCheckRecordSetHasMultipleTTLs_err_1type_2ttl(t *testing.T) { records := []*models.RecordConfig{ // different ttl per record - makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: 111}), - makeRC("zzz", "example.com", "4.4.4.5", models.RecordConfig{Type: "A", TTL: 222}), + makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: models.NewTTL(111)}), + makeRC("zzz", "example.com", "4.4.4.5", models.RecordConfig{Type: "A", TTL: models.NewTTL(222)}), } errs := checkRecordSetHasMultipleTTLs(records) if len(errs) == 0 { @@ -369,8 +369,8 @@ func TestCheckRecordSetHasMultipleTTLs_err_1type_2ttl(t *testing.T) { func TestCheckRecordSetHasMultipleTTLs_noerr_1type_1ttl(t *testing.T) { records := []*models.RecordConfig{ // different ttl per record - makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: 111}), - makeRC("zzz", "example.com", "4.4.4.5", models.RecordConfig{Type: "A", TTL: 111}), + makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: models.NewTTL(111)}), + makeRC("zzz", "example.com", "4.4.4.5", models.RecordConfig{Type: "A", TTL: models.NewTTL(111)}), } errs := checkRecordSetHasMultipleTTLs(records) if len(errs) != 0 { @@ -381,8 +381,8 @@ func TestCheckRecordSetHasMultipleTTLs_noerr_1type_1ttl(t *testing.T) { func TestCheckRecordSetHasMultipleTTLs_noerr_2type_2ttl(t *testing.T) { records := []*models.RecordConfig{ // different record types, different TTLs - makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: 333}), - makeRC("zzz", "example.com", "4.4.4.5", models.RecordConfig{Type: "NS", TTL: 444}), + makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: models.NewTTL(333)}), + makeRC("zzz", "example.com", "4.4.4.5", models.RecordConfig{Type: "NS", TTL: models.NewTTL(444)}), } errs := checkRecordSetHasMultipleTTLs(records) if len(errs) != 0 { @@ -393,8 +393,8 @@ func TestCheckRecordSetHasMultipleTTLs_noerr_2type_2ttl(t *testing.T) { func TestCheckRecordSetHasMultipleTTLs_noerr_2type_1ttl(t *testing.T) { records := []*models.RecordConfig{ // different record types, different TTLs - makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: 333}), - makeRC("zzz", "example.com", "4.4.4.5", models.RecordConfig{Type: "NS", TTL: 333}), + makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: models.NewTTL(333)}), + makeRC("zzz", "example.com", "4.4.4.5", models.RecordConfig{Type: "NS", TTL: models.NewTTL(333)}), } errs := checkRecordSetHasMultipleTTLs(records) if len(errs) != 0 { @@ -405,9 +405,9 @@ func TestCheckRecordSetHasMultipleTTLs_noerr_2type_1ttl(t *testing.T) { func TestCheckRecordSetHasMultipleTTLs_err_3type_2ttl(t *testing.T) { records := []*models.RecordConfig{ // different record types, different TTLs - makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: 555}), - makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: 555}), - makeRC("zzz", "example.com", "4.4.4.5", models.RecordConfig{Type: "NS", TTL: 666}), + makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: models.NewTTL(555)}), + makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: models.NewTTL(555)}), + makeRC("zzz", "example.com", "4.4.4.5", models.RecordConfig{Type: "NS", TTL: models.NewTTL(666)}), } errs := checkRecordSetHasMultipleTTLs(records) if len(errs) != 0 { @@ -418,9 +418,9 @@ func TestCheckRecordSetHasMultipleTTLs_err_3type_2ttl(t *testing.T) { func TestCheckRecordSetHasMultipleTTLs_err_3type_3ttl(t *testing.T) { records := []*models.RecordConfig{ // different record types, different TTLs - makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: 777}), - makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: 888}), - makeRC("zzz", "example.com", "4.4.4.5", models.RecordConfig{Type: "NS", TTL: 999}), + makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: models.NewTTL(777)}), + makeRC("zzz", "example.com", "4.4.4.4", models.RecordConfig{Type: "A", TTL: models.NewTTL(888)}), + makeRC("zzz", "example.com", "4.4.4.5", models.RecordConfig{Type: "NS", TTL: models.NewTTL(999)}), } errs := checkRecordSetHasMultipleTTLs(records) if len(errs) != 1 { diff --git a/pkg/prettyzone/prettyzone.go b/pkg/prettyzone/prettyzone.go index ae8a485b4c..2d8e459a02 100644 --- a/pkg/prettyzone/prettyzone.go +++ b/pkg/prettyzone/prettyzone.go @@ -16,9 +16,9 @@ import ( // MostCommonTTL returns the most common TTL in a set of records. If there is // a tie, the highest TTL is selected. This makes the results consistent. // NS records are not included in the analysis because Tom said so. -func MostCommonTTL(records models.Records) uint32 { +func MostCommonTTL(records models.Records) models.TTL { // Index the TTLs in use: - d := make(map[uint32]int) + d := make(map[models.TTL]int) for _, r := range records { if r.Type != "NS" { d[r.TTL]++ @@ -32,10 +32,10 @@ func MostCommonTTL(records models.Records) uint32 { } } // Find the largest key with that count: - var mk uint32 + var mk models.TTL for key, value := range d { if value == mc { - if key > mk { + if key.Value() > mk.Value() { mk = key } } @@ -50,11 +50,11 @@ func WriteZoneFileRR(w io.Writer, records []dns.RR, origin string) error { return err } - return WriteZoneFileRC(w, rcs, origin, 0, nil) + return WriteZoneFileRC(w, rcs, origin, models.EmptyTTL(), nil) } // WriteZoneFileRC writes a beautifully formatted zone file. -func WriteZoneFileRC(w io.Writer, records models.Records, origin string, defaultTTL uint32, comments []string) error { +func WriteZoneFileRC(w io.Writer, records models.Records, origin string, defaultTTL models.TTL, comments []string) error { // This function prioritizes beauty over output size. // * The zone records are sorted by label, grouped by subzones to // be easy to read and pleasant to the eye. @@ -66,7 +66,7 @@ func WriteZoneFileRC(w io.Writer, records models.Records, origin string, default // * $TTL is used to eliminate clutter. The most common TTL value is used. // * "@" is used instead of the apex domain name. - if defaultTTL == 0 { + if !defaultTTL.IsSet() { defaultTTL = MostCommonTTL(records) } @@ -76,18 +76,15 @@ func WriteZoneFileRC(w io.Writer, records models.Records, origin string, default } // PrettySort sorts the records in a pretty order. -func PrettySort(records models.Records, origin string, defaultTTL uint32, comments []string) *ZoneGenData { - if defaultTTL == 0 { +func PrettySort(records models.Records, origin string, defaultTTL models.TTL, comments []string) *ZoneGenData { + if !defaultTTL.IsSet() { defaultTTL = MostCommonTTL(records) } z := &ZoneGenData{ Origin: origin + ".", - DefaultTTL: defaultTTL, + DefaultTTL: defaultTTL.Value(), Comments: comments, } - if z.DefaultTTL == 0 { - z.DefaultTTL = 300 - } z.Records = nil z.Records = append(z.Records, records...) sort.Sort(z) @@ -130,7 +127,7 @@ func (z *ZoneGenData) generateZoneFileHelper(w io.Writer) error { // ttl ttl := "" - if rr.TTL != z.DefaultTTL && rr.TTL != 0 { + if rr.TTL.Value() != z.DefaultTTL { ttl = fmt.Sprint(rr.TTL) } diff --git a/pkg/prettyzone/prettyzone_test.go b/pkg/prettyzone/prettyzone_test.go index 85e2f3c934..1f3d00ae08 100644 --- a/pkg/prettyzone/prettyzone_test.go +++ b/pkg/prettyzone/prettyzone_test.go @@ -38,7 +38,7 @@ func parseAndRegen(t *testing.T, buf *bytes.Buffer, expected string) { func TestMostCommonTtl(t *testing.T) { var records []dns.RR - var g, e uint32 + var g, e models.TTL r1, _ := dns.NewRR("bosun.org. 100 IN A 1.1.1.1") r2, _ := dns.NewRR("bosun.org. 200 IN A 1.1.1.1") r3, _ := dns.NewRR("bosun.org. 300 IN A 1.1.1.1") @@ -47,50 +47,50 @@ func TestMostCommonTtl(t *testing.T) { // All records are TTL=100 records = nil - records, e = append(records, r1, r1, r1), 100 + records, e = append(records, r1, r1, r1), models.NewTTL(100) x, err := models.RRstoRCs(records, "bosun.org") if err != nil { panic(err) } g = MostCommonTTL(x) if e != g { - t.Fatalf("expected %d; got %d\n", e, g) + t.Fatalf("expected %d; got %d\n", e.Value(), g.Value()) } // Mixture of TTLs with an obvious winner. records = nil - records, e = append(records, r1, r2, r2), 200 + records, e = append(records, r1, r2, r2), models.NewTTL(200) rcs, err := models.RRstoRCs(records, "bosun.org") if err != nil { panic(err) } g = MostCommonTTL(rcs) if e != g { - t.Fatalf("expected %d; got %d\n", e, g) + t.Fatalf("expected %d; got %d\n", e.Value(), g.Value()) } // 3-way tie. Largest TTL should be used. records = nil - records, e = append(records, r1, r2, r3), 300 + records, e = append(records, r1, r2, r3), models.NewTTL(300) rcs, err = models.RRstoRCs(records, "bosun.org") if err != nil { panic(err) } g = MostCommonTTL(rcs) if e != g { - t.Fatalf("expected %d; got %d\n", e, g) + t.Fatalf("expected %d; got %d\n", e.Value(), g.Value()) } // NS records are ignored. records = nil - records, e = append(records, r1, r4, r5), 100 + records, e = append(records, r1, r4, r5), models.NewTTL(100) rcs, err = models.RRstoRCs(records, "bosun.org") if err != nil { panic(err) } g = MostCommonTTL(rcs) if e != g { - t.Fatalf("expected %d; got %d\n", e, g) + t.Fatalf("expected %d; got %d\n", e.Value(), g.Value()) } } @@ -299,9 +299,9 @@ func TestWriteZoneFileSynth(t *testing.T) { r1, _ := dns.NewRR("bosun.org. 300 IN A 192.30.252.153") r2, _ := dns.NewRR("bosun.org. 300 IN A 192.30.252.154") r3, _ := dns.NewRR("www.bosun.org. 300 IN CNAME bosun.org.") - rsynm := &models.RecordConfig{Type: "R53_ALIAS", TTL: 300} + rsynm := &models.RecordConfig{Type: "R53_ALIAS", TTL: models.NewTTL(300)} rsynm.SetLabel("myalias", "bosun.org") - rsynz := &models.RecordConfig{Type: "R53_ALIAS", TTL: 300} + rsynz := &models.RecordConfig{Type: "R53_ALIAS", TTL: models.NewTTL(300)} rsynz.SetLabel("zalias", "bosun.org") recs, err := models.RRstoRCs([]dns.RR{r1, r2, r3}, "bosun.org") @@ -313,7 +313,7 @@ func TestWriteZoneFileSynth(t *testing.T) { recs = append(recs, rsynz) buf := &bytes.Buffer{} - WriteZoneFileRC(buf, recs, "bosun.org", 0, []string{"c1", "c2", "c3\nc4"}) + WriteZoneFileRC(buf, recs, "bosun.org", models.EmptyTTL(), []string{"c1", "c2", "c3\nc4"}) expected := `$TTL 300 ; c1 ; c2 diff --git a/providers/akamaiedgedns/akamaiEdgeDnsService.go b/providers/akamaiedgedns/akamaiEdgeDnsService.go index b62c898acf..d032bc73af 100644 --- a/providers/akamaiedgedns/akamaiEdgeDnsService.go +++ b/providers/akamaiedgedns/akamaiEdgeDnsService.go @@ -194,7 +194,7 @@ func rcToRs(records []*models.RecordConfig, zonename string) (*dnsv2.RecordBody, akaRecord := &dnsv2.RecordBody{ Name: records[0].NameFQDN, RecordType: records[0].Type, - TTL: int(records[0].TTL), + TTL: int(records[0].TTL.Value()), } for _, r := range records { @@ -290,7 +290,7 @@ func getRecords(zonename string) ([]*models.RecordConfig, error) { for _, r := range akarecset.Rdata { rc := &models.RecordConfig{ Type: akatype, - TTL: uint32(akattl), + TTL: models.NewTTL(uint32(akattl)), } rc.SetLabelFromFQDN(akaname, zonename) err = rc.PopulateFromString(akatype, r, zonename) diff --git a/providers/autodns/autoDnsProvider.go b/providers/autodns/autoDnsProvider.go index 7682118c57..dc2db7af56 100644 --- a/providers/autodns/autoDnsProvider.go +++ b/providers/autodns/autoDnsProvider.go @@ -118,11 +118,11 @@ func (api *autoDNSProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, e Name: strings.TrimSuffix(record.GetTargetField(), "."), }) - zoneTTL = record.TTL + zoneTTL = record.TTL.Value() } else { resourceRecord := &ResourceRecord{ Name: record.Name, - TTL: int64(record.TTL), + TTL: int64(record.TTL.Value()), Type: record.Type, Value: record.GetTargetField(), } @@ -210,11 +210,11 @@ func recordsToNative(recs models.Records) ([]*models.Nameserver, uint32, []*Reso Name: strings.TrimSuffix(record.GetTargetField(), "."), }) - zoneTTL = record.TTL + zoneTTL = record.TTL.Value() } else { resourceRecord := &ResourceRecord{ Name: record.Name, - TTL: int64(record.TTL), + TTL: int64(record.TTL.Value()), Type: record.Type, Value: record.GetTargetField(), } @@ -261,15 +261,15 @@ func (api *autoDNSProvider) GetZoneRecords(domain string, meta map[string]string existingRecords[i] = toRecordConfig(domain, resourceRecord) // If TTL is not set for an individual RR AutoDNS defaults to the zone TTL defined in SOA - if existingRecords[i].TTL == 0 { - existingRecords[i].TTL = zone.Soa.TTL + if !existingRecords[i].TTL.IsSet() { + existingRecords[i].TTL = models.NewTTL(zone.Soa.TTL) } } // AutoDNS doesn't respond with APEX nameserver records as regular RR but rather as a zone property for _, nameServer := range zone.NameServers { nameServerRecord := &models.RecordConfig{ - TTL: zone.Soa.TTL, + TTL: models.NewTTL(zone.Soa.TTL), } nameServerRecord.SetLabel("", domain) @@ -282,12 +282,12 @@ func (api *autoDNSProvider) GetZoneRecords(domain string, meta map[string]string if zone.MainRecord != nil && zone.MainRecord.Value != "" { addressRecord := &models.RecordConfig{ - TTL: uint32(zone.MainRecord.TTL), + TTL: models.NewTTL(uint32(zone.MainRecord.TTL)), } // If TTL is not set for an individual RR AutoDNS defaults to the zone TTL defined in SOA - if addressRecord.TTL == 0 { - addressRecord.TTL = zone.Soa.TTL + if !addressRecord.TTL.IsSet() { + addressRecord.TTL = models.NewTTL(zone.Soa.TTL) } addressRecord.SetLabel("", domain) @@ -298,12 +298,12 @@ func (api *autoDNSProvider) GetZoneRecords(domain string, meta map[string]string if zone.IncludeWwwForMain { prefixedAddressRecord := &models.RecordConfig{ - TTL: uint32(zone.MainRecord.TTL), + TTL: models.NewTTL(uint32(zone.MainRecord.TTL)), } // If TTL is not set for an individual RR AutoDNS defaults to the zone TTL defined in SOA - if prefixedAddressRecord.TTL == 0 { - prefixedAddressRecord.TTL = zone.Soa.TTL + if !prefixedAddressRecord.TTL.IsSet() { + prefixedAddressRecord.TTL = models.NewTTL(zone.Soa.TTL) } prefixedAddressRecord.SetLabel("www", domain) @@ -320,7 +320,7 @@ func (api *autoDNSProvider) GetZoneRecords(domain string, meta map[string]string func toRecordConfig(domain string, record *ResourceRecord) *models.RecordConfig { rc := &models.RecordConfig{ Type: record.Type, - TTL: uint32(record.TTL), + TTL: models.NewTTL(uint32(record.TTL)), Original: record, } rc.SetLabel(record.Name, domain) diff --git a/providers/azuredns/azureDnsProvider.go b/providers/azuredns/azureDnsProvider.go index 484f1c5724..b947e2a31d 100644 --- a/providers/azuredns/azureDnsProvider.go +++ b/providers/azuredns/azureDnsProvider.go @@ -271,7 +271,7 @@ func (a *azurednsProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, ex } var recordName string for _, r := range recs { - i := int64(r.TTL) + i := int64(r.TTL.Value()) rrset.Properties.TTL = &i // TODO: make sure that ttls are consistent within a set recordName = r.Name } @@ -386,7 +386,7 @@ func (a *azurednsProvider) recordCreate(zoneName string, reckey models.RecordKey var recordName string var i int64 for _, r := range recs { - i = int64(r.TTL) + i = int64(r.TTL.Value()) recordName = r.Name } rrset.Properties.TTL = &i @@ -487,7 +487,7 @@ func nativeToRecords(set *adns.RecordSet, origin string) []*models.RecordConfig if set.Properties.ARecords != nil { // This is an A recordset. Process all the targets there. for _, rec := range set.Properties.ARecords { - rc := &models.RecordConfig{TTL: uint32(*set.Properties.TTL), Original: set} + rc := &models.RecordConfig{TTL: models.NewTTL(uint32(*set.Properties.TTL)), Original: set} rc.SetLabelFromFQDN(*set.Properties.Fqdn, origin) rc.Type = "A" _ = rc.SetTarget(*rec.IPv4Address) @@ -497,7 +497,7 @@ func nativeToRecords(set *adns.RecordSet, origin string) []*models.RecordConfig // This is an AZURE_ALIAS of an "A" record. rc := &models.RecordConfig{ Type: "AZURE_ALIAS", - TTL: uint32(*set.Properties.TTL), + TTL: models.NewTTL(uint32(*set.Properties.TTL)), AzureAlias: map[string]string{ "type": "A", }, @@ -511,7 +511,7 @@ func nativeToRecords(set *adns.RecordSet, origin string) []*models.RecordConfig if set.Properties.AaaaRecords != nil { // This is an AAAA recordset. Process all the targets there. for _, rec := range set.Properties.AaaaRecords { - rc := &models.RecordConfig{TTL: uint32(*set.Properties.TTL), Original: set} + rc := &models.RecordConfig{TTL: models.NewTTL(uint32(*set.Properties.TTL)), Original: set} rc.SetLabelFromFQDN(*set.Properties.Fqdn, origin) rc.Type = "AAAA" _ = rc.SetTarget(*rec.IPv6Address) @@ -521,7 +521,7 @@ func nativeToRecords(set *adns.RecordSet, origin string) []*models.RecordConfig // This is an AZURE_ALIAS of an "AAAA" record. rc := &models.RecordConfig{ Type: "AZURE_ALIAS", - TTL: uint32(*set.Properties.TTL), + TTL: models.NewTTL(uint32(*set.Properties.TTL)), AzureAlias: map[string]string{ "type": "AAAA", }, @@ -534,7 +534,7 @@ func nativeToRecords(set *adns.RecordSet, origin string) []*models.RecordConfig case "Microsoft.Network/dnszones/CNAME": if set.Properties.CnameRecord != nil { // This is a CNAME recordset. Process the targets. (there can only be one) - rc := &models.RecordConfig{TTL: uint32(*set.Properties.TTL), Original: set} + rc := &models.RecordConfig{TTL: models.NewTTL(uint32(*set.Properties.TTL)), Original: set} rc.SetLabelFromFQDN(*set.Properties.Fqdn, origin) rc.Type = "CNAME" _ = rc.SetTarget(*set.Properties.CnameRecord.Cname) @@ -543,7 +543,7 @@ func nativeToRecords(set *adns.RecordSet, origin string) []*models.RecordConfig // This is an AZURE_ALIAS of a "CNAME" record. rc := &models.RecordConfig{ Type: "AZURE_ALIAS", - TTL: uint32(*set.Properties.TTL), + TTL: models.NewTTL(uint32(*set.Properties.TTL)), AzureAlias: map[string]string{ "type": "CNAME", }, @@ -555,7 +555,7 @@ func nativeToRecords(set *adns.RecordSet, origin string) []*models.RecordConfig } case "Microsoft.Network/dnszones/NS": for _, rec := range set.Properties.NsRecords { - rc := &models.RecordConfig{TTL: uint32(*set.Properties.TTL), Original: set} + rc := &models.RecordConfig{TTL: models.NewTTL(uint32(*set.Properties.TTL)), Original: set} rc.SetLabelFromFQDN(*set.Properties.Fqdn, origin) rc.Type = "NS" _ = rc.SetTarget(*rec.Nsdname) @@ -563,7 +563,7 @@ func nativeToRecords(set *adns.RecordSet, origin string) []*models.RecordConfig } case "Microsoft.Network/dnszones/PTR": for _, rec := range set.Properties.PtrRecords { - rc := &models.RecordConfig{TTL: uint32(*set.Properties.TTL), Original: set} + rc := &models.RecordConfig{TTL: models.NewTTL(uint32(*set.Properties.TTL)), Original: set} rc.SetLabelFromFQDN(*set.Properties.Fqdn, origin) rc.Type = "PTR" _ = rc.SetTarget(*rec.Ptrdname) @@ -572,7 +572,7 @@ func nativeToRecords(set *adns.RecordSet, origin string) []*models.RecordConfig case "Microsoft.Network/dnszones/TXT": if len(set.Properties.TxtRecords) == 0 { // Empty String Record Parsing // This is a null TXT record. - rc := &models.RecordConfig{TTL: uint32(*set.Properties.TTL), Original: set} + rc := &models.RecordConfig{TTL: models.NewTTL(uint32(*set.Properties.TTL)), Original: set} rc.SetLabelFromFQDN(*set.Properties.Fqdn, origin) rc.Type = "TXT" _ = rc.SetTargetTXT("") @@ -580,7 +580,7 @@ func nativeToRecords(set *adns.RecordSet, origin string) []*models.RecordConfig } else { // This is a normal TXT record. Collect all its segments. for _, rec := range set.Properties.TxtRecords { - rc := &models.RecordConfig{TTL: uint32(*set.Properties.TTL), Original: set} + rc := &models.RecordConfig{TTL: models.NewTTL(uint32(*set.Properties.TTL)), Original: set} rc.SetLabelFromFQDN(*set.Properties.Fqdn, origin) rc.Type = "TXT" var txts []string @@ -593,7 +593,7 @@ func nativeToRecords(set *adns.RecordSet, origin string) []*models.RecordConfig } case "Microsoft.Network/dnszones/MX": for _, rec := range set.Properties.MxRecords { - rc := &models.RecordConfig{TTL: uint32(*set.Properties.TTL), Original: set} + rc := &models.RecordConfig{TTL: models.NewTTL(uint32(*set.Properties.TTL)), Original: set} rc.SetLabelFromFQDN(*set.Properties.Fqdn, origin) rc.Type = "MX" _ = rc.SetTargetMX(uint16(*rec.Preference), *rec.Exchange) @@ -601,7 +601,7 @@ func nativeToRecords(set *adns.RecordSet, origin string) []*models.RecordConfig } case "Microsoft.Network/dnszones/SRV": for _, rec := range set.Properties.SrvRecords { - rc := &models.RecordConfig{TTL: uint32(*set.Properties.TTL), Original: set} + rc := &models.RecordConfig{TTL: models.NewTTL(uint32(*set.Properties.TTL)), Original: set} rc.SetLabelFromFQDN(*set.Properties.Fqdn, origin) rc.Type = "SRV" _ = rc.SetTargetSRV(uint16(*rec.Priority), uint16(*rec.Weight), uint16(*rec.Port), *rec.Target) @@ -609,7 +609,7 @@ func nativeToRecords(set *adns.RecordSet, origin string) []*models.RecordConfig } case "Microsoft.Network/dnszones/CAA": for _, rec := range set.Properties.CaaRecords { - rc := &models.RecordConfig{TTL: uint32(*set.Properties.TTL), Original: set} + rc := &models.RecordConfig{TTL: models.NewTTL(uint32(*set.Properties.TTL)), Original: set} rc.SetLabelFromFQDN(*set.Properties.Fqdn, origin) rc.Type = "CAA" _ = rc.SetTargetCAA(uint8(*rec.Flags), *rec.Tag, *rec.Value) diff --git a/providers/bind/bindProvider.go b/providers/bind/bindProvider.go index fdb746625b..3e63a19555 100644 --- a/providers/bind/bindProvider.go +++ b/providers/bind/bindProvider.go @@ -331,7 +331,7 @@ func (c *bindProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, foundR // Beware that if there are any fake types, then they will // be commented out on write, but we don't reverse that when // reading, so there will be a diff on every invocation. - err = prettyzone.WriteZoneFileRC(zf, dc.Records, dc.Name, 0, comments) + err = prettyzone.WriteZoneFileRC(zf, dc.Records, dc.Name, models.EmptyTTL(), comments) if err != nil { return fmt.Errorf("failed WriteZoneFile: %w", err) diff --git a/providers/bind/soa.go b/providers/bind/soa.go index be6302df2c..4da14e54ea 100644 --- a/providers/bind/soa.go +++ b/providers/bind/soa.go @@ -29,7 +29,12 @@ func makeSoa(origin string, defSoa *SoaDefaults, existing, desired *models.Recor soaMail = soautil.RFC5322MailToBind(soaMail) } - soaRec.TTL = firstNonZero(desired.TTL, defSoa.TTL, existing.TTL, models.DefaultTTL) + defSoaTTL := models.EmptyTTL() + if defSoa.TTL != 0 { + defSoaTTL = models.NewTTL(defSoa.TTL) + } + + soaRec.TTL = firstSet(desired.TTL, defSoaTTL, existing.TTL) soaRec.SetTargetSOA( firstNonNull(desired.GetTargetField(), existing.GetTargetField(), defSoa.Ns, "DEFAULT_NOT_SET."), soaMail, @@ -60,3 +65,12 @@ func firstNonZero(items ...uint32) uint32 { } return 999 } + +func firstSet(items ...models.TTL) models.TTL { + for _, item := range items { + if item.IsSet() { + return item + } + } + return models.EmptyTTL() +} diff --git a/providers/cloudflare/cloudflareProvider.go b/providers/cloudflare/cloudflareProvider.go index 53f2f4596d..9494024b04 100644 --- a/providers/cloudflare/cloudflareProvider.go +++ b/providers/cloudflare/cloudflareProvider.go @@ -124,8 +124,8 @@ func (c *cloudflareProvider) GetZoneRecords(domain string, meta map[string]strin } for _, rec := range records { - if rec.TTL == 0 { - rec.TTL = 1 + if !rec.TTL.IsSet() { + rec.TTL = models.NewTTL(1) } // Store the proxy status ("orange cloud") for use by get-zones: m := getProxyMetadata(rec) @@ -213,7 +213,7 @@ func (c *cloudflareProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, // When not forcing this property change here, dnscontrol tries each time to update // the TTL of a record which simply cannot be changed anyway. if rec.Metadata[metaProxy] != "off" { - rec.TTL = 1 + rec.TTL = models.NewTTL(1) } // if labelMatches(rec.GetLabel(), c.ignoredLabels) { // log.Fatalf("FATAL: dnsconfig contains label that matches ignored_labels: %#v is in %v)\n", rec.GetLabel(), c.ignoredLabels) @@ -569,13 +569,13 @@ func (c *cloudflareProvider) preprocessConfig(dc *models.DomainConfig) error { rec.Metadata = map[string]string{} } // cloudflare uses "1" to mean "auto-ttl" - // if we get here and ttl is not specified (or is the dnscontrol default of 300), + // if we get here and ttl is not specified, // use automatic mode instead. - if rec.TTL == 0 || rec.TTL == 300 { - rec.TTL = 1 + if !rec.TTL.IsSet() { + rec.TTL = models.NewTTL(1) } - if rec.TTL != 1 && rec.TTL < 60 { - rec.TTL = 60 + if rec.TTL.Value() != 1 && rec.TTL.Value() < 60 { + rec.TTL = models.NewTTL(60) } if rec.Type != "A" && rec.Type != "CNAME" && rec.Type != "AAAA" && rec.Type != "ALIAS" { @@ -611,7 +611,7 @@ func (c *cloudflareProvider) preprocessConfig(dc *models.DomainConfig) error { } rec.SetTarget(fmt.Sprintf("%s,%d,%d", rec.GetTargetField(), currentPrPrio, code)) currentPrPrio++ - rec.TTL = 1 + rec.TTL = models.NewTTL(1) rec.Type = "PAGE_RULE" } @@ -621,7 +621,7 @@ func (c *cloudflareProvider) preprocessConfig(dc *models.DomainConfig) error { if len(parts) != 2 { return fmt.Errorf("invalid data specified for cloudflare worker record") } - rec.TTL = 1 + rec.TTL = models.NewTTL(1) rec.Type = "WORKER_ROUTE" } } @@ -826,7 +826,7 @@ func (c *cloudflareProvider) nativeToRecord(domain string, cr cloudflare.DNSReco } rc := &models.RecordConfig{ - TTL: uint32(cr.TTL), + TTL: models.NewTTL(uint32(cr.TTL)), Original: cr, Metadata: map[string]string{}, } diff --git a/providers/cloudflare/rest.go b/providers/cloudflare/rest.go index b3de4b6143..88c6a7a93f 100644 --- a/providers/cloudflare/rest.go +++ b/providers/cloudflare/rest.go @@ -129,12 +129,12 @@ func (c *cloudflareProvider) createRec(rec *models.RecordConfig, domainID string content = fmt.Sprintf("%d %d %d %s", rec.DsKeyTag, rec.DsAlgorithm, rec.DsDigestType, rec.DsDigest) } arr := []*models.Correction{{ - Msg: fmt.Sprintf("CREATE record: %s %s %d%s %s", rec.GetLabel(), rec.Type, rec.TTL, prio, content), + Msg: fmt.Sprintf("CREATE record: %s %s %d%s %s", rec.GetLabel(), rec.Type, rec.TTL.Value(), prio, content), F: func() error { cf := cloudflare.CreateDNSRecordParams{ Name: rec.GetLabel(), Type: rec.Type, - TTL: int(rec.TTL), + TTL: int(rec.TTL.Value()), Content: content, Priority: &rec.MxPreference, } @@ -165,7 +165,7 @@ func (c *cloudflareProvider) createRec(rec *models.RecordConfig, domainID string }} if rec.Metadata[metaProxy] != "off" { arr = append(arr, &models.Correction{ - Msg: fmt.Sprintf("ACTIVATE PROXY for new record %s %s %d %s", rec.GetLabel(), rec.Type, rec.TTL, rec.GetTargetField()), + Msg: fmt.Sprintf("ACTIVATE PROXY for new record %s %s %d %s", rec.GetLabel(), rec.Type, rec.TTL.Value(), rec.GetTargetField()), F: func() error { return c.modifyRecord(domainID, id, true, rec) }, }) } @@ -189,10 +189,10 @@ func (c *cloudflareProvider) createRecDiff2(rec *models.RecordConfig, domainID s content = fmt.Sprintf("%d %d %d %s", rec.DsKeyTag, rec.DsAlgorithm, rec.DsDigestType, rec.DsDigest) } if msg == "" { - msg = fmt.Sprintf("CREATE record: %s %s %d%s %s", rec.GetLabel(), rec.Type, rec.TTL, prio, content) + msg = fmt.Sprintf("CREATE record: %s %s %d%s %s", rec.GetLabel(), rec.Type, rec.TTL.Value(), prio, content) } if rec.Metadata[metaProxy] == "on" { - msg = msg + fmt.Sprintf("\nACTIVATE PROXY for new record %s %s %d %s", rec.GetLabel(), rec.Type, rec.TTL, rec.GetTargetField()) + msg = msg + fmt.Sprintf("\nACTIVATE PROXY for new record %s %s %d %s", rec.GetLabel(), rec.Type, rec.TTL.Value(), rec.GetTargetField()) } arr := []*models.Correction{{ Msg: msg, @@ -200,7 +200,7 @@ func (c *cloudflareProvider) createRecDiff2(rec *models.RecordConfig, domainID s cf := cloudflare.CreateDNSRecordParams{ Name: rec.GetLabel(), Type: rec.Type, - TTL: int(rec.TTL), + TTL: int(rec.TTL.Value()), Content: content, Priority: &rec.MxPreference, } @@ -248,7 +248,7 @@ func (c *cloudflareProvider) modifyRecord(domainID, recID string, proxied bool, Type: rec.Type, Content: rec.GetTargetField(), Priority: &rec.MxPreference, - TTL: int(rec.TTL), + TTL: int(rec.TTL.Value()), } if rec.Type == "TXT" { r.Content = rec.GetTargetTXTJoined() @@ -305,7 +305,7 @@ func (c *cloudflareProvider) getPageRules(id string, domain string) ([]*models.R r := &models.RecordConfig{ Type: "PAGE_RULE", Original: thisPr, - TTL: 1, + TTL: models.NewTTL(1), } r.SetLabel("@", domain) r.SetTarget(fmt.Sprintf("%s,%s,%d,%d", // $FROM,$TO,$PRIO,$CODE @@ -365,7 +365,7 @@ func (c *cloudflareProvider) getWorkerRoutes(id string, domain string) ([]*model r := &models.RecordConfig{ Type: "WORKER_ROUTE", Original: thisPr, - TTL: 1, + TTL: models.NewTTL(1), } r.SetLabel("@", domain) r.SetTarget(fmt.Sprintf("%s,%s", // $PATTERN,$SCRIPT diff --git a/providers/cloudns/cloudnsProvider.go b/providers/cloudns/cloudnsProvider.go index e24685a1b7..83bce078e9 100644 --- a/providers/cloudns/cloudnsProvider.go +++ b/providers/cloudns/cloudnsProvider.go @@ -126,7 +126,7 @@ func (c *cloudnsProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, exi c.fetchAvailableTTLValues(dc.Name) // ClouDNS can only be specified from a specific TTL list, so change the TTL in advance. for _, record := range dc.Records { - record.TTL = fixTTL(record.TTL) + record.TTL = models.NewTTL(fixTTL(record.TTL.Value())) } var corrections []*models.Correction @@ -260,7 +260,7 @@ func toRc(domain string, r *domainRecord) *models.RecordConfig { rc := &models.RecordConfig{ Type: r.Type, - TTL: uint32(ttl), + TTL: models.NewTTL(uint32(ttl)), MxPreference: uint16(priority), SrvPriority: uint16(priority), SrvWeight: uint16(weight), @@ -318,7 +318,7 @@ func toReq(rc *models.RecordConfig) (requestParams, error) { "record-type": rc.Type, "host": rc.GetLabel(), "record": rc.GetTargetField(), - "ttl": strconv.Itoa(int(rc.TTL)), + "ttl": strconv.Itoa(int(rc.TTL.Value())), } // ClouDNS doesn't use "@", it uses an empty name diff --git a/providers/cscglobal/convert.go b/providers/cscglobal/convert.go index 4ffab7c17e..37ebf37e22 100644 --- a/providers/cscglobal/convert.go +++ b/providers/cscglobal/convert.go @@ -16,7 +16,7 @@ func nativeToRecordA(nr nativeRecordA, origin string, defaultTTL uint32) *models } rc := &models.RecordConfig{ Type: "A", - TTL: ttl, + TTL: models.NewTTL(ttl), } rc.SetLabel(nr.Key, origin) rc.SetTargetIP(net.ParseIP(nr.Value).To4()) @@ -31,7 +31,7 @@ func nativeToRecordCNAME(nr nativeRecordCNAME, origin string, defaultTTL uint32) } rc := &models.RecordConfig{ Type: "CNAME", - TTL: ttl, + TTL: models.NewTTL(ttl), } rc.SetLabel(nr.Key, origin) rc.SetTarget(nr.Value) @@ -46,7 +46,7 @@ func nativeToRecordAAAA(nr nativeRecordAAAA, origin string, defaultTTL uint32) * } rc := &models.RecordConfig{ Type: "AAAA", - TTL: ttl, + TTL: models.NewTTL(ttl), } rc.SetLabel(nr.Key, origin) rc.SetTargetIP(net.ParseIP(nr.Value).To16()) @@ -61,7 +61,7 @@ func nativeToRecordTXT(nr nativeRecordTXT, origin string, defaultTTL uint32) *mo } rc := &models.RecordConfig{ Type: "TXT", - TTL: ttl, + TTL: models.NewTTL(ttl), } rc.SetLabel(nr.Key, origin) rc.SetTargetTXT(nr.Value) @@ -76,7 +76,7 @@ func nativeToRecordMX(nr nativeRecordMX, origin string, defaultTTL uint32) *mode } rc := &models.RecordConfig{ Type: "MX", - TTL: ttl, + TTL: models.NewTTL(ttl), } rc.SetLabel(nr.Key, origin) rc.SetTargetMX(nr.Priority, nr.Value) @@ -91,7 +91,7 @@ func nativeToRecordNS(nr nativeRecordNS, origin string, defaultTTL uint32) *mode } rc := &models.RecordConfig{ Type: "NS", - TTL: ttl, + TTL: models.NewTTL(ttl), } rc.SetLabel(nr.Key, origin) rc.SetTarget(nr.Value) @@ -106,7 +106,7 @@ func nativeToRecordSRV(nr nativeRecordSRV, origin string, defaultTTL uint32) *mo } rc := &models.RecordConfig{ Type: "SRV", - TTL: ttl, + TTL: models.NewTTL(ttl), } rc.SetLabel(nr.Key, origin) rc.SetTargetSRV(nr.Priority, nr.Weight, nr.Port, nr.Value) @@ -121,7 +121,7 @@ func nativeToRecordCAA(nr nativeRecordCAA, origin string, defaultTTL uint32) *mo } rc := &models.RecordConfig{ Type: "CAA", - TTL: ttl, + TTL: models.NewTTL(ttl), } rc.SetLabel(nr.Key, origin) rc.SetTargetCAA(nr.Flag, nr.Tag, nr.Value) diff --git a/providers/cscglobal/dns.go b/providers/cscglobal/dns.go index e00f1b3d1e..564c670b19 100644 --- a/providers/cscglobal/dns.go +++ b/providers/cscglobal/dns.go @@ -176,7 +176,7 @@ func makeAdd(domainname string, cre diff.Correlation) zoneResourceRecordEdit { RecordType: rec.Type, NewKey: rec.Name, NewValue: recTarget, - NewTTL: rec.TTL, + NewTTL: rec.TTL.Value(), } switch rec.Type { @@ -225,7 +225,7 @@ func makeEdit(domainname string, m diff.Correlation) zoneResourceRecordEdit { zer.NewValue = recTarget } if old.TTL != rec.TTL { - zer.NewTTL = rec.TTL + zer.NewTTL = rec.TTL.Value() } switch old.Type { diff --git a/providers/desec/convert.go b/providers/desec/convert.go index 3b71b7316e..cd784f099e 100644 --- a/providers/desec/convert.go +++ b/providers/desec/convert.go @@ -19,7 +19,7 @@ func nativeToRecords(n resourceRecord, origin string) (rcs []*models.RecordConfi // We must split them out into individual records, one for each value. for _, value := range n.Records { rc := &models.RecordConfig{ - TTL: n.TTL, + TTL: models.NewTTL(n.TTL), Original: n, } rc.SetLabel(n.Subname, origin) @@ -53,7 +53,7 @@ func recordsToNative(rcs []*models.RecordConfig, origin string) []resourceRecord // Allocate a new ZoneRecord: zr := resourceRecord{ Type: r.Type, - TTL: r.TTL, + TTL: r.TTL.Value(), Subname: label, Records: []string{r.GetTargetCombined()}, } @@ -61,10 +61,10 @@ func recordsToNative(rcs []*models.RecordConfig, origin string) []resourceRecord } else { zr.Records = append(zr.Records, r.GetTargetCombined()) - if r.TTL != zr.TTL { + if r.TTL.Value() != zr.TTL { printer.Warnf("All TTLs for a rrset (%v) must be the same. Using smaller of %v and %v.\n", key, r.TTL, zr.TTL) - if r.TTL < zr.TTL { - zr.TTL = r.TTL + if r.TTL.Value() < zr.TTL { + zr.TTL = r.TTL.Value() } } diff --git a/providers/desec/desecProvider.go b/providers/desec/desecProvider.go index 275a9a45e7..80db05e3ae 100644 --- a/providers/desec/desecProvider.go +++ b/providers/desec/desecProvider.go @@ -139,11 +139,11 @@ func PrepDesiredRecords(dc *models.DomainConfig, minTTL uint32) { printer.Warnf("deSEC does not support alias records\n") continue } - if rec.TTL < minTTL { + if rec.TTL.Value() < minTTL { if rec.Type != "NS" { printer.Warnf("Please contact support@desec.io if you need TTLs < %d. Setting TTL of %s type %s from %d to %d\n", minTTL, rec.GetLabelFQDN(), rec.Type, rec.TTL, minTTL) } - rec.TTL = minTTL + rec.TTL = models.NewTTL(minTTL) } recordsToKeep = append(recordsToKeep, rec) } diff --git a/providers/digitalocean/digitaloceanProvider.go b/providers/digitalocean/digitaloceanProvider.go index 6bcf8b1061..9efa4f0d04 100644 --- a/providers/digitalocean/digitaloceanProvider.go +++ b/providers/digitalocean/digitaloceanProvider.go @@ -294,7 +294,7 @@ func toRc(domain string, r *godo.DomainRecord) *models.RecordConfig { t := &models.RecordConfig{ Type: r.Type, - TTL: uint32(r.TTL), + TTL: models.NewTTL(uint32(r.TTL)), MxPreference: uint16(r.Priority), SrvPriority: uint16(r.Priority), SrvWeight: uint16(r.Weight), @@ -339,7 +339,7 @@ func toReq(dc *models.DomainConfig, rc *models.RecordConfig) *godo.DomainRecordE Type: rc.Type, Name: name, Data: target, - TTL: int(rc.TTL), + TTL: int(rc.TTL.Value()), Priority: priority, Port: int(rc.SrvPort), Weight: int(rc.SrvWeight), diff --git a/providers/dnsimple/dnsimpleProvider.go b/providers/dnsimple/dnsimpleProvider.go index fb5d269237..e402c4a48d 100644 --- a/providers/dnsimple/dnsimpleProvider.go +++ b/providers/dnsimple/dnsimpleProvider.go @@ -102,7 +102,7 @@ func (c *dnsimpleProvider) GetZoneRecords(domain string, meta map[string]string) } rec := &models.RecordConfig{ - TTL: uint32(r.TTL), + TTL: models.NewTTL(uint32(r.TTL)), Original: r, } rec.SetLabel(r.Name, domain) @@ -499,7 +499,7 @@ func (c *dnsimpleProvider) createRecordFunc(rc *models.RecordConfig, domainName Name: dnsimpleapi.String(rc.GetLabel()), Type: rc.Type, Content: getTargetRecordContent(rc), - TTL: int(rc.TTL), + TTL: int(rc.TTL.Value()), Priority: getTargetRecordPriority(rc), } _, err = client.Zones.CreateRecord(context.Background(), accountID, domainName, record) @@ -561,7 +561,7 @@ func (c *dnsimpleProvider) updateRecordFunc(old *dnsimpleapi.ZoneRecord, rc *mod Name: dnsimpleapi.String(rc.GetLabel()), Type: rc.Type, Content: getTargetRecordContent(rc), - TTL: int(rc.TTL), + TTL: int(rc.TTL.Value()), Priority: getTargetRecordPriority(rc), } diff --git a/providers/dnsmadeeasy/dnsMadeEasyProvider.go b/providers/dnsmadeeasy/dnsMadeEasyProvider.go index d6942b16d3..fdb96c2b11 100644 --- a/providers/dnsmadeeasy/dnsMadeEasyProvider.go +++ b/providers/dnsmadeeasy/dnsMadeEasyProvider.go @@ -114,7 +114,7 @@ func (api *dnsMadeEasyProvider) GetZoneRecordsCorrections(dc *models.DomainConfi rec.Type = "ANAME" } else if rec.Type == "NS" { // NS records have fixed TTL on DNS Made Easy and it cannot be changed - rec.TTL = fixedNameServerRecordTTL + rec.TTL = models.NewTTL(fixedNameServerRecordTTL) } } diff --git a/providers/dnsmadeeasy/types.go b/providers/dnsmadeeasy/types.go index 1a4010d3a2..2f34cad35c 100644 --- a/providers/dnsmadeeasy/types.go +++ b/providers/dnsmadeeasy/types.go @@ -126,7 +126,7 @@ type recordRequestData struct { func toRecordConfig(domain string, record *recordResponseDataEntry) *models.RecordConfig { rc := &models.RecordConfig{ Type: record.Type, - TTL: uint32(record.TTL), + TTL: models.NewTTL(uint32(record.TTL)), Original: record, } @@ -162,7 +162,7 @@ func fromRecordConfig(rc *models.RecordConfig) *recordRequestData { record := &recordRequestData{ Type: rc.Type, - TTL: int(rc.TTL), + TTL: int(rc.TTL.Value()), GtdLocation: "DEFAULT", Name: label, Value: rc.GetTargetCombined(), diff --git a/providers/domainnameshop/convert.go b/providers/domainnameshop/convert.go index 0a2cedf589..9d492c45bd 100644 --- a/providers/domainnameshop/convert.go +++ b/providers/domainnameshop/convert.go @@ -14,7 +14,7 @@ func toRecordConfig(domain string, currentRecord *domainNameShopRecord) *models. t := &models.RecordConfig{ Type: currentRecord.Type, - TTL: fixTTL(uint32(currentRecord.TTL)), + TTL: models.NewTTL(fixTTL(uint32(currentRecord.TTL))), MxPreference: uint16(currentRecord.ActualPriority), SrvPriority: uint16(currentRecord.ActualPriority), SrvWeight: uint16(currentRecord.ActualWeight), @@ -60,7 +60,7 @@ func (api *domainNameShopProvider) fromRecordConfig(domainName string, rc *model dnsR := &domainNameShopRecord{ ID: 0, Host: rc.GetLabel(), - TTL: uint16(fixTTL(rc.TTL)), + TTL: uint16(fixTTL(rc.TTL.Value())), Type: rc.Type, Data: data, Weight: strconv.Itoa(int(rc.SrvWeight)), diff --git a/providers/domainnameshop/dns.go b/providers/domainnameshop/dns.go index 27acee61b0..023b515f51 100644 --- a/providers/domainnameshop/dns.go +++ b/providers/domainnameshop/dns.go @@ -37,7 +37,7 @@ func (api *domainNameShopProvider) GetZoneRecordsCorrections(dc *models.DomainCo // Domainnameshop doesn't allow arbitrary TTLs they must be a multiple of 60. for _, record := range dc.Records { - record.TTL = fixTTL(record.TTL) + record.TTL = models.NewTTL(fixTTL(record.TTL.Value())) } var corrections []*models.Correction diff --git a/providers/exoscale/exoscaleProvider.go b/providers/exoscale/exoscaleProvider.go index ec303aa244..654aa03295 100644 --- a/providers/exoscale/exoscaleProvider.go +++ b/providers/exoscale/exoscaleProvider.go @@ -154,7 +154,7 @@ func (c *exoscaleProvider) GetZoneRecords(domainName string, meta map[string]str Original: record, } if record.TTL != nil { - rc.TTL = uint32(*record.TTL) + rc.TTL = models.NewTTL(uint32(*record.TTL)) } rc.SetLabel(rname, domainName) @@ -269,8 +269,8 @@ func (c *exoscaleProvider) createRecordFunc(rc *models.RecordConfig, domainID st Priority: prio, } - if rc.TTL != 0 { - ttl := int64(rc.TTL) + if rc.TTL.Value() != 0 { + ttl := int64(rc.TTL.Value()) record.TTL = &ttl } @@ -325,8 +325,8 @@ func (c *exoscaleProvider) updateRecordFunc(record *egoscale.DNSDomainRecord, rc record.Name = &name record.Type = &rc.Type record.Content = &target - if rc.TTL != 0 { - ttl := int64(rc.TTL) + if rc.TTL.Value() != 0 { + ttl := int64(rc.TTL.Value()) record.TTL = &ttl } diff --git a/providers/gandiv5/convert.go b/providers/gandiv5/convert.go index 229ac0c3f1..b46ff48130 100644 --- a/providers/gandiv5/convert.go +++ b/providers/gandiv5/convert.go @@ -20,7 +20,7 @@ func nativeToRecords(n livedns.DomainRecord, origin string) (rcs []*models.Recor // We must split them out into individual records, one for each value. for _, value := range n.RrsetValues { rc := &models.RecordConfig{ - TTL: uint32(n.RrsetTTL), + TTL: models.NewTTL(uint32(n.RrsetTTL)), Original: n, } rc.SetLabel(n.RrsetName, origin) @@ -63,7 +63,7 @@ func recordsToNative(rcs []*models.RecordConfig, origin string) []livedns.Domain // Allocate a new ZoneRecord: zr := livedns.DomainRecord{ RrsetType: r.Type, - RrsetTTL: int(r.TTL), + RrsetTTL: int(r.TTL.Value()), RrsetName: label, RrsetValues: []string{r.GetTargetCombined()}, } @@ -71,10 +71,10 @@ func recordsToNative(rcs []*models.RecordConfig, origin string) []livedns.Domain } else { zr.RrsetValues = append(zr.RrsetValues, r.GetTargetCombined()) - if r.TTL != uint32(zr.RrsetTTL) { + if r.TTL.Value() != uint32(zr.RrsetTTL) { printer.Warnf("All TTLs for a rrset (%v) must be the same. Using smaller of %v and %v.\n", key, r.TTL, zr.RrsetTTL) - if r.TTL < uint32(zr.RrsetTTL) { - zr.RrsetTTL = int(r.TTL) + if r.TTL.Value() < uint32(zr.RrsetTTL) { + zr.RrsetTTL = int(r.TTL.Value()) } } diff --git a/providers/gandiv5/gandi_v5Provider.go b/providers/gandiv5/gandi_v5Provider.go index 6a069bd70d..47382e10f6 100644 --- a/providers/gandiv5/gandi_v5Provider.go +++ b/providers/gandiv5/gandi_v5Provider.go @@ -169,13 +169,13 @@ func PrepDesiredRecords(dc *models.DomainConfig) { // Therefore, we change this to a CNAME. rec.Type = "CNAME" } - if rec.TTL < 300 { + if rec.TTL.Value() < 300 { printer.Warnf("Gandi does not support ttls < 300. Setting %s from %d to 300\n", rec.GetLabelFQDN(), rec.TTL) - rec.TTL = 300 + rec.TTL = models.NewTTL(300) } - if rec.TTL > 2592000 { + if rec.TTL.Value() > 2592000 { printer.Warnf("Gandi does not support ttls > 30 days. Setting %s from %d to 2592000\n", rec.GetLabelFQDN(), rec.TTL) - rec.TTL = 2592000 + rec.TTL = models.NewTTL(2592000) } if rec.Type == "TXT" { rec.SetTarget("\"" + rec.GetTargetField() + "\"") // FIXME(tlim): Should do proper quoting. diff --git a/providers/gcloud/gcloudProvider.go b/providers/gcloud/gcloudProvider.go index a7333b47f1..c948684b49 100644 --- a/providers/gcloud/gcloudProvider.go +++ b/providers/gcloud/gcloudProvider.go @@ -261,7 +261,7 @@ func (g *gcloudProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, exis for _, r := range dc.Records { if keyForRec(r) == ck { newRRs.Rrdatas = append(newRRs.Rrdatas, r.GetTargetCombined()) - newRRs.Ttl = int64(r.TTL) + newRRs.Ttl = int64(r.TTL.Value()) } } if len(newRRs.Rrdatas) > 0 { @@ -299,7 +299,7 @@ func (g *gcloudProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, exis func nativeToRecord(set *gdns.ResourceRecordSet, rec, origin string) (*models.RecordConfig, error) { r := &models.RecordConfig{} r.SetLabelFromFQDN(set.Name, origin) - r.TTL = uint32(set.Ttl) + r.TTL = models.NewTTL(uint32(set.Ttl)) rtype := set.Type var err error switch rtype { diff --git a/providers/gcore/convert.go b/providers/gcore/convert.go index 938caf9d73..a737fbe3af 100644 --- a/providers/gcore/convert.go +++ b/providers/gcore/convert.go @@ -20,7 +20,7 @@ func nativeToRecords(n gcoreRRSetExtended, zoneName string) ([]*models.RecordCon // Split G-Core's RRset into individual records for _, value := range n.Records { rc := &models.RecordConfig{ - TTL: uint32(n.TTL), + TTL: models.NewTTL(uint32(n.TTL)), Original: n, } rc.SetLabelFromFQDN(recName, zoneName) @@ -89,17 +89,17 @@ func recordsToNative(rcs []*models.RecordConfig, expectedKey models.RecordKey) * if result == nil { result = &dnssdk.RRSet{ - TTL: int(r.TTL), + TTL: int(r.TTL.Value()), Filters: nil, Records: []dnssdk.ResourceRecord{rr}, } } else { result.Records = append(result.Records, rr) - if int(r.TTL) != result.TTL { + if int(r.TTL.Value()) != result.TTL { printer.Warnf("All TTLs for a rrset (%v) must be the same. Using smaller of %v and %v.\n", key, r.TTL, result.TTL) - if int(r.TTL) < result.TTL { - result.TTL = int(r.TTL) + if int(r.TTL.Value()) < result.TTL { + result.TTL = int(r.TTL.Value()) } } } diff --git a/providers/hedns/hednsProvider.go b/providers/hedns/hednsProvider.go index 13aaa37460..d57c4a1155 100644 --- a/providers/hedns/hednsProvider.go +++ b/providers/hedns/hednsProvider.go @@ -329,7 +329,7 @@ func (c *hednsProvider) GetZoneRecords(domain string, meta map[string]string) (m rc := &models.RecordConfig{ Type: parser.parseStringAttr(element.Find("td > .rrlabel"), "data"), - TTL: parser.parseIntElementUint32(element.Find("td:nth-child(5)")), + TTL: models.NewTTL(parser.parseIntElementUint32(element.Find("td:nth-child(5)"))), Original: Record{ ZoneName: domain, ZoneID: domainID, @@ -576,7 +576,7 @@ func (c *hednsProvider) editZoneRecord(zoneID uint64, recordID uint64, rc *model "menu": {"edit_zone"}, "hosted_dns_zoneid": {strconv.FormatUint(zoneID, 10)}, "hosted_dns_editzone": {"1"}, - "TTL": {strconv.FormatUint(uint64(rc.TTL), 10)}, + "TTL": {strconv.FormatUint(uint64(rc.TTL.Value()), 10)}, "Name": {rc.Name}, } diff --git a/providers/hetzner/types.go b/providers/hetzner/types.go index 80ad3c8e7f..fd5b268c36 100644 --- a/providers/hetzner/types.go +++ b/providers/hetzner/types.go @@ -59,7 +59,7 @@ func fromRecordConfig(in *models.RecordConfig, zone *zone) record { Name: in.GetLabel(), Type: in.Type, Value: in.GetTargetCombined(), - TTL: &in.TTL, + TTL: in.TTL.ValueRef(), ZoneID: zone.ID, } @@ -83,7 +83,7 @@ func fromRecordConfig(in *models.RecordConfig, zone *zone) record { func toRecordConfig(domain string, r *record) (*models.RecordConfig, error) { rc := models.RecordConfig{ Type: r.Type, - TTL: *r.TTL, + TTL: models.NewTTL(*r.TTL), Original: r, } rc.SetLabel(r.Name, domain) diff --git a/providers/hexonet/records.go b/providers/hexonet/records.go index 9102f3990b..83ee607b93 100644 --- a/providers/hexonet/records.go +++ b/providers/hexonet/records.go @@ -136,7 +136,7 @@ func (n *HXClient) GetZoneRecordsCorrections(dc *models.DomainConfig, actual mod func toRecord(r *HXRecord, origin string) *models.RecordConfig { rc := &models.RecordConfig{ Type: r.Type, - TTL: r.TTL, + TTL: models.NewTTL(r.TTL), Original: r, } fqdn := r.Fqdn[:len(r.Fqdn)-1] @@ -245,7 +245,7 @@ func (n *HXClient) createRecordString(rc *models.RecordConfig, domain string) (s Host: rc.GetLabel(), Type: rc.Type, Answer: rc.GetTargetField(), - TTL: rc.TTL, + TTL: rc.TTL.Value(), Priority: uint32(rc.MxPreference), } switch rc.Type { // #rtype_variations diff --git a/providers/hostingde/hostingdeProvider.go b/providers/hostingde/hostingdeProvider.go index be91b2413e..08d3eb3844 100644 --- a/providers/hostingde/hostingdeProvider.go +++ b/providers/hostingde/hostingdeProvider.go @@ -123,11 +123,11 @@ func (hp *hostingdeProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, // TTL must be between (inclusive) 1m and 1y (in fact, a little bit more) for _, r := range dc.Records { - if r.TTL < 60 { - r.TTL = 60 + if r.TTL.Value() < 60 { + r.TTL = models.NewTTL(60) } - if r.TTL > 31556926 { - r.TTL = 31556926 + if r.TTL.Value() > 31556926 { + r.TTL = models.NewTTL(31556926) } } @@ -186,12 +186,17 @@ func (hp *hostingdeProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, defaultSoa = &soaValues{} } + defaultSoaTTL := models.EmptyTTL() + if defaultSoa.TTL != 0 { + defaultSoaTTL = models.NewTTL(defaultSoa.TTL) + } + newSOA := soaValues{ Refresh: firstNonZero(desiredSoa.SoaRefresh, defaultSoa.Refresh, 86400), Retry: firstNonZero(desiredSoa.SoaRetry, defaultSoa.Retry, 7200), Expire: firstNonZero(desiredSoa.SoaExpire, defaultSoa.Expire, 3600000), NegativeTTL: firstNonZero(desiredSoa.SoaMinttl, defaultSoa.NegativeTTL, 900), - TTL: firstNonZero(desiredSoa.TTL, defaultSoa.TTL, 86400), + TTL: firstSet(desiredSoa.TTL, defaultSoaTTL, models.NewTTL(86400)).Value(), } if zone.ZoneConfig.SOAValues != newSOA { @@ -316,6 +321,15 @@ func firstNonZero(items ...uint32) uint32 { return 999 } +func firstSet(items ...models.TTL) models.TTL { + for _, item := range items { + if item.IsSet() { + return item + } + } + return models.EmptyTTL() +} + func (hp *hostingdeProvider) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { // err := dc.Punycode() // if err != nil { diff --git a/providers/hostingde/types.go b/providers/hostingde/types.go index 0a1f924a5b..596a41fdd2 100644 --- a/providers/hostingde/types.go +++ b/providers/hostingde/types.go @@ -148,7 +148,7 @@ func (r record) nativeToRecord(domain string) *models.RecordConfig { rc := &models.RecordConfig{ Type: "", - TTL: r.TTL, + TTL: models.NewTTL(r.TTL), MxPreference: r.Priority, SrvPriority: r.Priority, Original: r, @@ -186,7 +186,7 @@ func recordToNative(rc *models.RecordConfig) *record { Name: rc.NameFQDN, Type: rc.Type, Content: strings.TrimSuffix(rc.GetTargetCombined(), "."), - TTL: rc.TTL, + TTL: rc.TTL.Value(), } switch rc.Type { // #rtype_variations diff --git a/providers/inwx/inwxProvider.go b/providers/inwx/inwxProvider.go index 3c50f7c274..10a8ab1ab3 100644 --- a/providers/inwx/inwxProvider.go +++ b/providers/inwx/inwxProvider.go @@ -167,7 +167,7 @@ func makeNameserverRecordRequest(domain string, rec *models.RecordConfig) *goinw Type: rec.Type, Content: content, Name: rec.GetLabel(), - TTL: int(rec.TTL), + TTL: int(rec.TTL.Value()), } switch rType := rec.Type; rType { @@ -320,7 +320,7 @@ func (api *inwxAPI) GetZoneRecords(domain string, meta map[string]string) (model } rc := &models.RecordConfig{ - TTL: uint32(record.TTL), + TTL: models.NewTTL(uint32(record.TTL)), Original: record, } rc.SetLabelFromFQDN(record.Name, domain) diff --git a/providers/linode/linodeProvider.go b/providers/linode/linodeProvider.go index 1e68dfc999..ef11212c0f 100644 --- a/providers/linode/linodeProvider.go +++ b/providers/linode/linodeProvider.go @@ -29,7 +29,7 @@ Info required in `creds.json`: // Allowed values from the Linode API // https://www.linode.com/docs/api/domains/#domains-list__responses var allowedTTLValues = []uint32{ - 0, // Default, currently 1209600 seconds + 0, // Default, currently 86400 seconds 300, // 5 minutes 3600, // 1 hour 7200, // 2 hours @@ -132,7 +132,7 @@ func (api *linodeProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, ex // The documentation says that it will always round up to the next highest value: 300 -> 300, 301 -> 3600. // https://www.linode.com/docs/api/domains/#domains-list__responses for _, record := range dc.Records { - record.TTL = fixTTL(record.TTL) + record.TTL = models.NewTTL(fixTTL(record.TTL.Value())) } var err error @@ -249,7 +249,7 @@ func (api *linodeProvider) getRecordsForDomain(domainID int, domain string) (mod func toRc(domain string, r *domainRecord) *models.RecordConfig { rc := &models.RecordConfig{ Type: r.Type, - TTL: r.TTLSec, + TTL: models.NewTTL(r.TTLSec), MxPreference: r.Priority, SrvPriority: r.Priority, SrvWeight: r.Weight, @@ -277,7 +277,7 @@ func toReq(dc *models.DomainConfig, rc *models.RecordConfig) (*recordEditRequest Type: rc.Type, Name: rc.GetLabel(), Target: rc.GetTargetField(), - TTL: int(rc.TTL), + TTL: int(rc.TTL.Value()), Priority: 0, Port: int(rc.SrvPort), Weight: int(rc.SrvWeight), diff --git a/providers/loopia/convert.go b/providers/loopia/convert.go index 737820df65..a5fd00be42 100644 --- a/providers/loopia/convert.go +++ b/providers/loopia/convert.go @@ -14,7 +14,7 @@ func nativeToRecord(zr zoneRecord, origin string, subdomain string) (rc *models. record := zr.GetZR() rc = &models.RecordConfig{ - TTL: record.TTL, + TTL: models.NewTTL(record.TTL), Original: record, Type: record.Type, } @@ -45,7 +45,7 @@ func recordToNative(rc *models.RecordConfig, id ...uint32) paramStruct { //rc is the record from dnscontrol to loopia zrec := zRec{} zrec.Type = rc.Type - zrec.TTL = rc.TTL + zrec.TTL = rc.TTL.Value() zrec.Rdata = rc.GetTargetCombined() if rc.Original != nil { diff --git a/providers/loopia/convert_test.go b/providers/loopia/convert_test.go index 5c19bcd072..bca7892c1e 100644 --- a/providers/loopia/convert_test.go +++ b/providers/loopia/convert_test.go @@ -10,7 +10,7 @@ import ( func TestRecordToNative_1(t *testing.T) { rc := &models.RecordConfig{ - TTL: 3600, + TTL: models.NewTTL(3600), } rc.SetLabel("foo", "example.com") rc.SetTarget("1.2.3.4") @@ -37,7 +37,7 @@ func TestNativeToRecord_1(t *testing.T) { if rc.Type != "A" { t.Errorf("nativeToRecord produced unexpected type") - } else if rc.TTL != 300 { + } else if rc.TTL.Value() != 300 { t.Errorf("nativeToRecord produced unexpected TTL") } else if rc.GetTargetCombined() != "1.2.3.4" { t.Errorf("nativeToRecord produced unexpected Rdata") diff --git a/providers/loopia/loopiaProvider.go b/providers/loopia/loopiaProvider.go index 3e0740b3e5..a4227e3362 100644 --- a/providers/loopia/loopiaProvider.go +++ b/providers/loopia/loopiaProvider.go @@ -232,14 +232,14 @@ func PrepDesiredRecords(dc *models.DomainConfig) { // Therefore, we change this to a CNAME. rec.Type = "CNAME" } - if rec.TTL < 300 { + if rec.TTL.Value() < 300 { /* you can submit TTL lower than 300 but the dig results are normalized to 300 */ printer.Warnf("Loopia does not support TTL < 300. Setting %s from %d to 300\n", rec.GetLabelFQDN(), rec.TTL) - rec.TTL = 300 - } else if rec.TTL > 2147483647 { + rec.TTL = models.NewTTL(300) + } else if rec.TTL.Value() > 2147483647 { /* you can submit a TTL higher than 4294967296 but Loopia shortens it to 2147483647. 68 year timeout tho. */ printer.Warnf("Loopia does not support TTL > 68 years. Setting %s from %d to 2147483647\n", rec.GetLabelFQDN(), rec.TTL) - rec.TTL = 2147483647 + rec.TTL = models.NewTTL(2147483647) } // if rec.Type == "NS" && rec.GetLabel() == "@" { // if !strings.HasSuffix(rec.GetTargetField(), ".loopia.se.") { diff --git a/providers/luadns/api.go b/providers/luadns/api.go index 0ca8afabc9..fa20df1ecf 100644 --- a/providers/luadns/api.go +++ b/providers/luadns/api.go @@ -210,7 +210,7 @@ func (l *luadnsProvider) makeRequest(endpoint string, method string, params any) func nativeToRecord(domain string, r *domainRecord) *models.RecordConfig { rc := &models.RecordConfig{ Type: r.Type, - TTL: r.TTL, + TTL: models.NewTTL(r.TTL), Original: r, } rc.SetLabelFromFQDN(r.Name, domain) @@ -242,8 +242,8 @@ func checkNS(dc *models.DomainConfig) { newList := make([]*models.RecordConfig, 0, len(dc.Records)) for _, rec := range dc.Records { // LuaDNS does not support changing the TTL of the default nameservers, so forcefully change the TTL to 86400. - if rec.Type == "NS" && strings.HasSuffix(rec.GetTargetField(), ".luadns.net.") && rec.TTL != 86400 { - rec.TTL = 86400 + if rec.Type == "NS" && strings.HasSuffix(rec.GetTargetField(), ".luadns.net.") && rec.TTL.Value() != 86400 { + rec.TTL = models.NewTTL(86400) } newList = append(newList, rec) } diff --git a/providers/msdns/convert.go b/providers/msdns/convert.go index d27372a15b..4b494d7875 100644 --- a/providers/msdns/convert.go +++ b/providers/msdns/convert.go @@ -62,7 +62,7 @@ func nativeToRecords(nr nativeRecord, origin string) (*models.RecordConfig, erro Original: nr, } rc.SetLabel(nr.HostName, origin) - rc.TTL = uint32(nr.TimeToLive.TotalSeconds) + rc.TTL = models.NewTTL(uint32(nr.TimeToLive.TotalSeconds)) sprops, uprops, err := extractProps(nr.RecordData.CimInstanceProperties) if err != nil { diff --git a/providers/msdns/corrections.go b/providers/msdns/corrections.go index dc9ed5ff50..f2de0f7c69 100644 --- a/providers/msdns/corrections.go +++ b/providers/msdns/corrections.go @@ -106,7 +106,7 @@ func (client *msdnsProvider) modifyOneRecord(dnsserver, zonename string, oldrec, } func (client *msdnsProvider) modifyRecordTTL(dnsserver, zonename string, oldrec, newrec *models.RecordConfig) error { - return client.shell.RecordModifyTTL(dnsserver, zonename, oldrec, newrec.TTL) + return client.shell.RecordModifyTTL(dnsserver, zonename, oldrec, newrec.TTL.Value()) } func (client *msdnsProvider) deleteRec(dnsserver, domainname string, cor diff.Correlation) *models.Correction { diff --git a/providers/msdns/naptr.go b/providers/msdns/naptr.go index e27ed5744d..cdfbe2ad98 100644 --- a/providers/msdns/naptr.go +++ b/providers/msdns/naptr.go @@ -30,7 +30,7 @@ func generatePSCreateNaptr(dnsServerName, domain string, rec *models.RecordConfi fmt.Fprintf(&b, `$Service = %s ; `, escapePS(rec.NaptrService)) fmt.Fprintf(&b, `$Regex = %s ; `, escapePS(rec.NaptrRegexp)) fmt.Fprintf(&b, `$Replacement = %s ; `, escapePS(rec.GetTargetField())) - fmt.Fprintf(&b, `dnscmd %s/recordadd $zoneName $rrName %d naptr $Order $Preference $Flags $Service $Regex $Replacement ; `, computername, rec.TTL) + fmt.Fprintf(&b, `dnscmd %s/recordadd $zoneName $rrName %d naptr $Order $Preference $Flags $Service $Regex $Replacement ; `, computername, rec.TTL.Value()) return b.String() } diff --git a/providers/msdns/powershell.go b/providers/msdns/powershell.go index 2022367bc6..e68333d2f9 100644 --- a/providers/msdns/powershell.go +++ b/providers/msdns/powershell.go @@ -296,7 +296,7 @@ func generatePSCreate(dnsserver, domain string, rec *models.RecordConfig) string } fmt.Fprintf(&b, ` -ZoneName "%s"`, domain) fmt.Fprintf(&b, ` -Name "%s"`, rec.GetLabel()) - fmt.Fprintf(&b, ` -TimeToLive $(New-TimeSpan -Seconds %d)`, rec.TTL) + fmt.Fprintf(&b, ` -TimeToLive $(New-TimeSpan -Seconds %d)`, rec.TTL.Value()) switch rec.Type { case "A": fmt.Fprintf(&b, ` -A -IPv4Address "%s"`, rec.GetTargetIP()) @@ -390,7 +390,7 @@ func (psh *psHandle) RecordModifyTTL(dnsserver, domain string, old *models.Recor func generatePSModifyTTL(dnsserver, domain string, rec *models.RecordConfig, newTTL uint32) string { var b bytes.Buffer - fmt.Fprintf(&b, `echo MODIFY-TTL "%s" "%s" %q ttl=%d->%d`, rec.Name, rec.Type, rec.GetTargetCombined(), rec.TTL, newTTL) + fmt.Fprintf(&b, `echo MODIFY-TTL "%s" "%s" %q ttl=%d->%d`, rec.Name, rec.Type, rec.GetTargetCombined(), rec.TTL.Value(), newTTL) fmt.Fprintf(&b, " ; ") fmt.Fprint(&b, `Get-DnsServerResourceRecord`) diff --git a/providers/namecheap/namecheapProvider.go b/providers/namecheap/namecheapProvider.go index 31af80c94b..130743b139 100644 --- a/providers/namecheap/namecheapProvider.go +++ b/providers/namecheap/namecheapProvider.go @@ -293,7 +293,7 @@ func toRecords(result *nc.DomainDNSGetHostsResult, origin string) ([]*models.Rec for _, dnsHost := range result.Hosts { record := models.RecordConfig{ Type: dnsHost.Type, - TTL: uint32(dnsHost.TTL), + TTL: models.NewTTL(uint32(dnsHost.TTL)), MxPreference: uint16(dnsHost.MXPref), Name: dnsHost.Name, } @@ -332,7 +332,7 @@ func (n *namecheapProvider) generateRecords(dc *models.DomainConfig) error { Type: r.Type, Address: value, MXPref: int(r.MxPreference), - TTL: int(r.TTL), + TTL: int(r.TTL.Value()), } recs = append(recs, rec) id++ diff --git a/providers/namedotcom/records.go b/providers/namedotcom/records.go index 0dba6abb2a..ac9208486c 100644 --- a/providers/namedotcom/records.go +++ b/providers/namedotcom/records.go @@ -91,7 +91,7 @@ func toRecord(r *namecom.Record, origin string) *models.RecordConfig { heapr := r // NB(tlim): Unsure if this is actually needed. rc := &models.RecordConfig{ Type: r.Type, - TTL: r.TTL, + TTL: models.NewTTL(r.TTL), Original: heapr, } if !strings.HasSuffix(r.Fqdn, ".") { @@ -154,7 +154,7 @@ func (n *namedotcomProvider) createRecord(rc *models.RecordConfig, domain string Host: rc.GetLabel(), Type: rc.Type, Answer: rc.GetTargetField(), - TTL: rc.TTL, + TTL: rc.TTL.Value(), Priority: uint32(rc.MxPreference), } switch rc.Type { // #rtype_variations diff --git a/providers/netcup/netcupProvider.go b/providers/netcup/netcupProvider.go index a2d51581be..6a0015ddf7 100644 --- a/providers/netcup/netcupProvider.go +++ b/providers/netcup/netcupProvider.go @@ -77,7 +77,7 @@ func (api *netcupProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, ex // Setting the TTL is not supported for netcup for _, r := range dc.Records { - r.TTL = 0 + r.TTL = models.NewTTL(0) } // Filter out types we can't modify (like NS) diff --git a/providers/netcup/types.go b/providers/netcup/types.go index ae7279fa63..3d96186324 100644 --- a/providers/netcup/types.go +++ b/providers/netcup/types.go @@ -81,7 +81,7 @@ func toRecordConfig(domain string, r *record) *models.RecordConfig { rc := &models.RecordConfig{ Type: r.Type, - TTL: uint32(0), + TTL: models.NewTTL(uint32(0)), MxPreference: uint16(priority), SrvPriority: uint16(priority), SrvWeight: uint16(0), diff --git a/providers/netlify/netlifyProvider.go b/providers/netlify/netlifyProvider.go index 42f474d03b..0757f4d3f2 100644 --- a/providers/netlify/netlifyProvider.go +++ b/providers/netlify/netlifyProvider.go @@ -105,7 +105,7 @@ func (n *netlifyProvider) GetZoneRecords(domain string, meta map[string]string) } rec := &models.RecordConfig{ - TTL: uint32(r.TTL), + TTL: models.NewTTL(uint32(r.TTL)), Original: r, } @@ -262,7 +262,7 @@ func toReq(rc *models.RecordConfig) *dnsRecordCreate { Type: rc.Type, Hostname: name, Value: target, - TTL: int64(rc.TTL), + TTL: int64(rc.TTL.Value()), Priority: priority, Port: int64(rc.SrvPort), Weight: int64(rc.SrvWeight), diff --git a/providers/ns1/ns1Provider.go b/providers/ns1/ns1Provider.go index 5eb30dde47..a1e296501c 100644 --- a/providers/ns1/ns1Provider.go +++ b/providers/ns1/ns1Provider.go @@ -286,7 +286,7 @@ func buildRecord(recs models.Records, domain string, id string) *dns.Record { Domain: r.GetLabelFQDN(), Type: r.Type, ID: id, - TTL: int(r.TTL), + TTL: int(r.TTL.Value()), Zone: domain, Filters: []*filter.Filter{}, // Work through a bug in the NS1 API library that causes 400 Input validation failed (Value None for field '.filters' is not of type array) } @@ -332,7 +332,7 @@ func convert(zr *dns.ZoneRecord, domain string) ([]*models.RecordConfig, error) found := []*models.RecordConfig{} for _, ans := range zr.ShortAns { rec := &models.RecordConfig{ - TTL: uint32(zr.TTL), + TTL: models.NewTTL(uint32(zr.TTL)), Original: zr, } rec.SetLabelFromFQDN(zr.Domain, domain) diff --git a/providers/oracle/oracleProvider.go b/providers/oracle/oracleProvider.go index 8b269b710c..03b391bae7 100644 --- a/providers/oracle/oracleProvider.go +++ b/providers/oracle/oracleProvider.go @@ -173,7 +173,7 @@ func (o *oracleProvider) GetZoneRecords(zone string, meta map[string]string) (mo rc := &models.RecordConfig{ Type: *record.Rtype, - TTL: uint32(*record.Ttl), + TTL: models.NewTTL(uint32(*record.Ttl)), Original: record, } rc.SetLabelFromFQDN(*record.Domain, zone) @@ -219,9 +219,9 @@ func (o *oracleProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, exis continue } - if rec.TTL != 86400 { + if rec.TTL.Value() != 86400 { printer.Warnf("Oracle Cloud forces TTL=86400 for NS records. Ignoring configured TTL of %d for %s\n", rec.TTL, recNS) - rec.TTL = 86400 + rec.TTL = models.NewTTL(86400) } } @@ -328,7 +328,7 @@ func convertToRecordOperation(rec *models.RecordConfig, op dns.RecordOperationOp fqdn := rec.GetLabelFQDN() rtype := rec.Type rdata := rec.GetTargetCombined() - ttl := int(rec.TTL) + ttl := int(rec.TTL.Value()) return dns.RecordOperation{ Domain: &fqdn, diff --git a/providers/ovh/ovhProvider.go b/providers/ovh/ovhProvider.go index a5d74f8ed5..b7402114f8 100644 --- a/providers/ovh/ovhProvider.go +++ b/providers/ovh/ovhProvider.go @@ -221,7 +221,7 @@ func nativeToRecord(r *Record, origin string) (*models.RecordConfig, error) { return nil, nil } rec := &models.RecordConfig{ - TTL: uint32(r.TTL), + TTL: models.NewTTL(uint32(r.TTL)), Original: r, } @@ -238,8 +238,8 @@ func nativeToRecord(r *Record, origin string) (*models.RecordConfig, error) { } // ovh default is 3600 - if rec.TTL == 0 { - rec.TTL = 3600 + if rec.TTL.Value() == 0 { + rec.TTL = models.NewTTL(3600) } return rec, nil diff --git a/providers/ovh/protocol.go b/providers/ovh/protocol.go index 71691bcf95..1e2a7f4b70 100644 --- a/providers/ovh/protocol.go +++ b/providers/ovh/protocol.go @@ -113,7 +113,7 @@ func (c *ovhProvider) createRecordFunc(rc *models.RecordConfig, fqdn string) fun SubDomain: dnsutil.TrimDomainName(rc.GetLabelFQDN(), fqdn), FieldType: rc.Type, Target: rc.GetTargetCombined(), - TTL: rc.TTL, + TTL: rc.TTL.Value(), } if record.SubDomain == "@" { record.SubDomain = "" @@ -131,7 +131,7 @@ func (c *ovhProvider) updateRecordFunc(old *Record, rc *models.RecordConfig, fqd SubDomain: rc.GetLabel(), FieldType: rc.Type, Target: rc.GetTargetCombined(), - TTL: rc.TTL, + TTL: rc.TTL.Value(), Zone: fqdn, ID: old.ID, } diff --git a/providers/packetframe/packetframeProvider.go b/providers/packetframe/packetframeProvider.go index 2c1fff871f..5296543768 100644 --- a/providers/packetframe/packetframeProvider.go +++ b/providers/packetframe/packetframeProvider.go @@ -174,7 +174,7 @@ func (api *packetframeProvider) GetZoneRecordsCorrections(dc *models.DomainConfi func toReq(zoneID string, dc *models.DomainConfig, rc *models.RecordConfig) (*domainRecord, error) { req := &domainRecord{ Type: rc.Type, - TTL: int(rc.TTL), + TTL: int(rc.TTL.Value()), Label: rc.GetLabel(), Zone: zoneID, } @@ -196,7 +196,7 @@ func toReq(zoneID string, dc *models.DomainConfig, rc *models.RecordConfig) (*do func toRc(dc *models.DomainConfig, r *domainRecord) *models.RecordConfig { rc := &models.RecordConfig{ Type: r.Type, - TTL: uint32(r.TTL), + TTL: models.NewTTL(uint32(r.TTL)), Original: r, } diff --git a/providers/porkbun/porkbunProvider.go b/providers/porkbun/porkbunProvider.go index 1825a7550a..d09ddd3c6a 100644 --- a/providers/porkbun/porkbunProvider.go +++ b/providers/porkbun/porkbunProvider.go @@ -83,7 +83,7 @@ func (c *porkbunProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, exi // Make sure TTL larger than the minimum TTL for _, record := range dc.Records { - record.TTL = fixTTL(record.TTL) + record.TTL = models.NewTTL(fixTTL(record.TTL.Value())) } var corrections []*models.Correction @@ -210,7 +210,7 @@ func toRc(domain string, r *domainRecord) *models.RecordConfig { rc := &models.RecordConfig{ Type: r.Type, - TTL: uint32(ttl), + TTL: models.NewTTL(uint32(ttl)), MxPreference: uint16(priority), SrvPriority: uint16(priority), Original: r, @@ -267,7 +267,7 @@ func toReq(rc *models.RecordConfig) (requestParams, error) { "type": rc.Type, "name": rc.GetLabel(), "content": rc.GetTargetField(), - "ttl": strconv.Itoa(int(rc.TTL)), + "ttl": strconv.Itoa(int(rc.TTL.Value())), } // porkbun doesn't use "@", it uses an empty name diff --git a/providers/powerdns/convert.go b/providers/powerdns/convert.go index 307955552f..24b7e5317c 100644 --- a/providers/powerdns/convert.go +++ b/providers/powerdns/convert.go @@ -14,7 +14,7 @@ func toRecordConfig(domain string, r zones.Record, ttl int, name string, rtype s name = strings.TrimSuffix(name, ".") rc := &models.RecordConfig{ - TTL: uint32(ttl), + TTL: models.NewTTL(uint32(ttl)), Original: r, Type: rtype, } diff --git a/providers/powerdns/diff.go b/providers/powerdns/diff.go index 1d246251cb..9a60d80195 100644 --- a/providers/powerdns/diff.go +++ b/providers/powerdns/diff.go @@ -3,11 +3,12 @@ package powerdns import ( "context" "fmt" + "strings" + "github.com/StackExchange/dnscontrol/v4/models" "github.com/StackExchange/dnscontrol/v4/pkg/diff" "github.com/StackExchange/dnscontrol/v4/pkg/diff2" "github.com/mittwald/go-powerdns/apis/zones" - "strings" ) func (dsp *powerdnsProvider) getDiff1DomainCorrections(dc *models.DomainConfig, existing models.Records) ([]*models.Correction, error) { @@ -50,7 +51,7 @@ func (dsp *powerdnsProvider) getDiff1DomainCorrections(dc *models.DomainConfig, return dsp.client.Zones().AddRecordSetToZone(context.Background(), dsp.ServerName, dc.Name, zones.ResourceRecordSet{ Name: labelName, Type: labelType, - TTL: int(ttl), + TTL: int(ttl.Value()), Records: records, ChangeType: zones.ChangeTypeReplace, }) @@ -84,7 +85,7 @@ func (dsp *powerdnsProvider) getDiff2DomainCorrections(dc *models.DomainConfig, case diff2.REPORT: corrections = append(corrections, &models.Correction{Msg: change.MsgsJoined}) case diff2.CREATE, diff2.CHANGE: - labelTTL := int(change.New[0].TTL) + labelTTL := int(change.New[0].TTL.Value()) records := buildRecordList(change) corrections = append(corrections, &models.Correction{ diff --git a/providers/route53/route53Provider.go b/providers/route53/route53Provider.go index 2fe31a1611..517a4e3dc7 100644 --- a/providers/route53/route53Provider.go +++ b/providers/route53/route53Provider.go @@ -423,7 +423,7 @@ func (r *route53Provider) GetZoneRecordsCorrections(dc *models.DomainConfig, exi Value: aws.String(val), } rrset.ResourceRecords = append(rrset.ResourceRecords, rr) - i := int64(rec.TTL) + i := int64(rec.TTL.Value()) rrset.TTL = &i // TODO: make sure that ttls are consistent within a set } // Assemble the change and add it to the list: @@ -540,7 +540,7 @@ func (r *route53Provider) GetZoneRecordsCorrections(dc *models.DomainConfig, exi Value: aws.String(r.GetTargetCombined()), } rrset.ResourceRecords = append(rrset.ResourceRecords, rr) - i := int64(r.TTL) + i := int64(r.TTL.Value()) rrset.TTL = &i } } @@ -626,7 +626,7 @@ func nativeToRecords(set r53Types.ResourceRecordSet, origin string) ([]*models.R if set.AliasTarget != nil { rc := &models.RecordConfig{ Type: "R53_ALIAS", - TTL: 300, + TTL: models.NewTTL(300), R53Alias: map[string]string{ "type": string(set.Type), "zone_id": aws.ToString(set.AliasTarget.HostedZoneId), @@ -680,7 +680,7 @@ func nativeToRecords(set r53Types.ResourceRecordSet, origin string) ([]*models.R } var err error - rc := &models.RecordConfig{TTL: uint32(aws.ToInt64(set.TTL))} + rc := &models.RecordConfig{TTL: models.NewTTL(uint32(aws.ToInt64(set.TTL)))} rc.SetLabelFromFQDN(unescape(set.Name), origin) rc.Original = set switch rtypeString { diff --git a/providers/rwth/convert.go b/providers/rwth/convert.go index cac252f3ab..c08ef4e835 100644 --- a/providers/rwth/convert.go +++ b/providers/rwth/convert.go @@ -22,7 +22,7 @@ func (api *rwthProvider) printRecConfig(rr models.RecordConfig) string { // ttl ttl := "" - if rr.TTL != 172800 && rr.TTL != 0 { + if rr.TTL.Value() != 172800 && rr.TTL.IsSet() { ttl = fmt.Sprint(rr.TTL) } diff --git a/providers/softlayer/softlayerProvider.go b/providers/softlayer/softlayerProvider.go index 3269d626de..9445fd5d93 100644 --- a/providers/softlayer/softlayerProvider.go +++ b/providers/softlayer/softlayerProvider.go @@ -152,7 +152,7 @@ func (s *softlayerProvider) getExistingRecords(domain *datatypes.Dns_Domain) (mo recConfig := &models.RecordConfig{ Type: recType, - TTL: uint32(*record.Ttl), + TTL: models.NewTTL(uint32(*record.Ttl)), Original: record, } recConfig.SetTarget(*record.Data) @@ -196,7 +196,7 @@ func (s *softlayerProvider) getExistingRecords(domain *datatypes.Dns_Domain) (mo } func (s *softlayerProvider) createRecordFunc(desired *models.RecordConfig, domain *datatypes.Dns_Domain) func() error { - var ttl, preference, domainID = verifyMinTTL(int(desired.TTL)), int(desired.MxPreference), *domain.Id + var ttl, preference, domainID = verifyMinTTL(int(desired.TTL.Value())), int(desired.MxPreference), *domain.Id var weight, priority, port = int(desired.SrvWeight), int(desired.SrvPriority), int(desired.SrvPort) var host, data, newType = desired.GetLabel(), desired.GetTargetField(), desired.Type var err error @@ -266,7 +266,7 @@ func (s *softlayerProvider) deleteRecordFunc(resID int) func() error { } func (s *softlayerProvider) updateRecordFunc(existing *datatypes.Dns_Domain_ResourceRecord, desired *models.RecordConfig) func() error { - var ttl, preference = verifyMinTTL(int(desired.TTL)), int(desired.MxPreference) + var ttl, preference = verifyMinTTL(int(desired.TTL.Value())), int(desired.MxPreference) var priority, weight, port = int(desired.SrvPriority), int(desired.SrvWeight), int(desired.SrvPort) return func() error { diff --git a/providers/transip/transipProvider.go b/providers/transip/transipProvider.go index 6f76bcc930..dac1e28de7 100644 --- a/providers/transip/transipProvider.go +++ b/providers/transip/transipProvider.go @@ -357,7 +357,7 @@ func recordToNative(config *models.RecordConfig, useOriginal bool) (domain.DNSEn return domain.DNSEntry{ Name: config.Name, - Expire: int(config.TTL), + Expire: int(config.TTL.Value()), Type: config.Type, Content: getTargetRecordContent(config), }, nil @@ -365,7 +365,7 @@ func recordToNative(config *models.RecordConfig, useOriginal bool) (domain.DNSEn func nativeToRecord(entry domain.DNSEntry, origin string) (*models.RecordConfig, error) { rc := &models.RecordConfig{ - TTL: uint32(entry.Expire), + TTL: models.NewTTL(uint32(entry.Expire)), Type: entry.Type, Original: entry, } diff --git a/providers/vultr/vultrProvider.go b/providers/vultr/vultrProvider.go index c493e41c99..b66f80db6d 100644 --- a/providers/vultr/vultrProvider.go +++ b/providers/vultr/vultrProvider.go @@ -260,7 +260,7 @@ func toRecordConfig(domain string, r govultr.DomainRecord) (*models.RecordConfig origin, data := domain, r.Data rc := &models.RecordConfig{ - TTL: uint32(r.TTL), + TTL: models.NewTTL(uint32(r.TTL)), Original: r, } rc.SetLabel(r.Name, domain) @@ -339,7 +339,7 @@ func toVultrRecord(dc *models.DomainConfig, rc *models.RecordConfig, vultrID str Type: rc.Type, Name: name, Data: data, - TTL: int(rc.TTL), + TTL: int(rc.TTL.Value()), Priority: priority, } switch rtype := rc.Type; rtype { // #rtype_variations