Repository: StackExchange/dnscontrol (Go)

Issue:
LINODE: TTL does not support "Default" (0)
**Describe the bug**
I set up a `dnsconfig.js` matching the records for a domain I manage through Linode. All of the records in Linode have their TTL column set to "Default". I do not configure an explicit TTL in `dnsconfig.js` for any record, leaving it at the `dnscontrol` default.

When I run `dnscontrol preview`, every record is marked as a modification such as:
```
#1: MODIFY A example.com: (1.2.3.4 ttl=0) -> (1.2.3.4 ttl=300), Linode ID: NNN: {"type":"A","name":"","target":"1.2.3.4","ttl_sec":300}
```

**To Reproduce**
1. Configure a domain name to use Linode for DNS records.
2. Add a DNS A record to Linode with some label and IP, and leave the TTL as "Default".
3. Write a `dnsconfig.js` with the same A record label and IP, and no configured TTL.
4. `dnscontrol preview`

**Expected behavior**
No modifications should be proposed.

**Versions**
 - DNSControl version: 4.1.1 (Homebrew)
 - DNS Provider: `LINODE`

**Additional context**
The `LINODE` provider documentation has a [caveat](https://docs.dnscontrol.org/service-providers/providers/linode#caveats) that the TTLs must be from a specific list. [The file that it links to](https://github.com/linode/manager/blob/master/src/domains/components/SelectDNSSeconds.js) does not exist in the Linode source tree anymore. When I searched for an equivalent list, I found this one that explicitly includes the option `{ label: 'Default', value: 0 }`.

https://github.com/linode/manager/blob/07be31a25399c8583a55a3727b8a38e0a6e25cd7/packages/manager/src/features/Domains/DomainRecordDrawer.tsx#L320-L336


Discussion:
The `dnscontrol` source code has the permalink for the file referenced in the docs:
https://github.com/StackExchange/dnscontrol/blob/d00804b4f51c5b95137b50b15c747493de1475d5/providers/linode/linodeProvider.go#L126-L133

From that file, it appears that Linode did support a `ttl_sec` value of 0 back then, which it mapped to `ONE_DAY`: https://github.com/linode/manager/blob/edd99dc4e1be5ab8190f243c3dbf8b830716255e/src/domains/components/SelectDNSSeconds.js#L51
[Linode's own documentation](https://www.linode.com/docs/products/networking/dns-manager/guides/troubleshooting-dns/#set-the-time-to-live-or-ttl) confirms that the default TTL is still one day.
CC @koesie10 

Interesting bug!

That raises an interesting question.  Should allowedTTLValues (https://github.com/StackExchange/dnscontrol/blob/d00804b4f51c5b95137b50b15c747493de1475d5/providers/linode/linodeProvider.go#L29) be changed to include `0` or should the fixTTL function turn 0 into 86400?  (I think the first option)




I personally agree that 0 should be allowed as a setting, because that is what the Linode API is returning when "Default" is set via their UI. If `fixTTL` turned 0 into 86400, that would still result in a `MODIFY` (it would be a functional no-op, but not a technical one).

The question though is whether `dnscontrol` has a good way to represent "Default" in its own UX. Do any other providers have a similar concept? How do they handle a TTL of 0?
I agree that `0` should be added to the `allowedTTLValues`. I believe this should work and does not require any further modifications.

I have created [a PR ](https://github.com/StackExchange/dnscontrol/pull/2442) for this, which also updates the docs. I'm just finding conflicting information about what `0` actually means:
- [The DNS manager documentation](https://www.linode.com/docs/products/networking/dns-manager/guides/troubleshooting-dns/#set-the-time-to-live-or-ttl) says the default is 1 day
- [The API documentation](https://www.linode.com/docs/api/domains/#domains-list__responses) says "A value of 0 is equivalent to the default value of 1209600", which would be 14 days

I've taken the value from the API documentation since that's what the provider is using, but I'm not sure if that actually matches with what Linode does.
@koesie10 I think you were looking at the wrong part of that documentation.
<html><body>
<!--StartFragment-->

data | array of objects
-- | --
expire_sec | integerThe amount of time in seconds that may pass before this Domain is no longer authoritative.Valid values are 0, 300, 3600, 7200, 14400, 28800, 57600, 86400, 172800, 345600, 604800, 1209600, and 2419200.Any other value is rounded up to the nearest valid value.A value of 0 is equivalent to the default value of 1209600.
-- | --
ttl_sec | integer“Time to Live” - the amount of time in seconds that this Domain’s records may be cached by resolvers or other domain servers.Valid values are 0, 300, 3600, 7200, 14400, 28800, 57600, 86400, 172800, 345600, 604800, 1209600, and 2419200.Any other value is rounded up to the nearest valid value.A value of 0 is equivalent to the default value of 86400.
-- | --


<!--EndFragment-->
</body>
</html>

AFAICT the default is still 1 day for `ttl_sec`, not 14 days.
Fixed the comment as part of #2444.



Patch:
diff --git a/documentation/providers/linode.md b/documentation/providers/linode.md
index 784bcf0d24..de05eebbf2 100644
--- a/documentation/providers/linode.md
+++ b/documentation/providers/linode.md
@@ -38,8 +38,9 @@ D("example.tld", REG_NONE, DnsProvider(DSP_LINODE),
 
 ## Caveats
 Linode does not allow all TTLs, but only a specific subset of TTLs. The following TTLs are supported
-([source](https://github.com/linode/manager/blob/master/src/domains/components/SelectDNSSeconds.js)):
+([source](https://www.linode.com/docs/api/domains/#domains-list__responses)):
 
+- 0 (Default, currently equivalent to 1209600, or 14 days)
 - 300
 - 3600
 - 7200
diff --git a/providers/linode/linodeProvider.go b/providers/linode/linodeProvider.go
index bc4451b3e3..1e68dfc999 100644
--- a/providers/linode/linodeProvider.go
+++ b/providers/linode/linodeProvider.go
@@ -26,7 +26,10 @@ 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
 	300,     // 5 minutes
 	3600,    // 1 hour
 	7200,    // 2 hours
@@ -126,8 +129,8 @@ func (api *linodeProvider) GetZoneRecords(domain string, meta map[string]string)
 func (api *linodeProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, existingRecords models.Records) ([]*models.Correction, error) {
 	// Linode doesn't allow selecting an arbitrary TTL, only a set of predefined values
 	// We need to make sure we don't change it every time if it is as close as it's going to get
-	// By experimentation, Linode always rounds up. 300 -> 300, 301 -> 3600.
-	// https://github.com/linode/manager/blob/edd99dc4e1be5ab8190f243c3dbf8b830716255e/src/domains/components/SelectDNSSeconds.js#L19
+	// 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)
 	}


Test patch:
diff --git a/integrationTest/integration_test.go b/integrationTest/integration_test.go
index a7e32aa375..84ea112892 100644
--- a/integrationTest/integration_test.go
+++ b/integrationTest/integration_test.go
@@ -969,12 +969,14 @@ func makeTests(t *testing.T) []*TestGroup {
 		// weirdest edge-case we've ever seen.
 
 		testgroup("Attl",
+			not("LINODE"), // Linode does not support arbitrary TTLs: both are rounded up to 3600.
 			tc("Create Arc", ttl(a("testa", "1.1.1.1"), 333)),
 			tc("Change TTL", ttl(a("testa", "1.1.1.1"), 999)),
 		),
 
 		testgroup("TTL",
 			not("NETCUP"), // NETCUP does not support TTLs.
+			not("LINODE"), // Linode does not support arbitrary TTLs: 666 and 1000 are both rounded up to 3600.
 			tc("Start", ttl(a("@", "8.8.8.8"), 666), a("www", "1.2.3.4"), a("www", "5.6.7.8")),
 			tc("Change a ttl", ttl(a("@", "8.8.8.8"), 1000), a("www", "1.2.3.4"), a("www", "5.6.7.8")),
 			tc("Change single target from set", ttl(a("@", "8.8.8.8"), 1000), a("www", "2.2.2.2"), a("www", "5.6.7.8")),
@@ -1223,7 +1225,7 @@ func makeTests(t *testing.T) []*TestGroup {
 			tc("Internationalized CNAME Target", cname("a", "ööö.com.")),
 		),
 		testgroup("IDNAs in CNAME targets",
-			not("LINODE", "CLOUDFLAREAPI"),
+			not("CLOUDFLAREAPI"),
 			// LINODE: hostname validation does not allow the target domain TLD
 			tc("IDN CNAME AND Target", cname("öoö", "ööö.企业.")),
 		),
diff --git a/providers/linode/linodeProvider_test.go b/providers/linode/linodeProvider_test.go
index 9dfff68341..c4dd82363a 100644
--- a/providers/linode/linodeProvider_test.go
+++ b/providers/linode/linodeProvider_test.go
@@ -6,6 +6,8 @@ func TestFixTTL(t *testing.T) {
 	for i, test := range []struct {
 		given, expected uint32
 	}{
+		{0, 0},
+		{1, 300},
 		{299, 300},
 		{300, 300},
 		{301, 3600},