Skip to content

Commit 2ad3786

Browse files
authored
domain_record TTLs are not rounded to closest valid values when diffing (#755)
1 parent d29f7d8 commit 2ad3786

File tree

3 files changed

+121
-5
lines changed

3 files changed

+121
-5
lines changed

docs/modules/domain_record.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ NOTE: Domain records are identified by their name, target, and type.
7474
| `service` | <center>`str`</center> | <center>Optional</center> | An underscore (_) is prepended and a period (.) is appended automatically to the submitted value for this property. Only valid and required for SRV record requests. The name of the service. **(Updatable)** |
7575
| `tag` | <center>`str`</center> | <center>Optional</center> | The tag portion of a CAA record. Only valid and required for CAA record requests. **(Updatable)** |
7676
| `target` | <center>`str`</center> | <center>Optional</center> | The target for this Record. |
77-
| `ttl_sec` | <center>`int`</center> | <center>Optional</center> | The amount of time in seconds that this Domains records may be cached by resolvers or other domain servers. **(Updatable)** |
77+
| `ttl_sec` | <center>`int`</center> | <center>Optional</center> | 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 300, 3600, 7200, 14400, 28800, 57600, 86400, 172800, 345600, 604800, 1209600, and 2419200 - any other value will be rounded to the nearest valid value. **(Updatable)** |
7878
| `type` | <center>`str`</center> | <center>Optional</center> | The type of Record this is in the DNS system. |
7979
| `weight` | <center>`int`</center> | <center>Optional</center> | The relative weight of this Record used in the case of identical priority. **(Updatable)** |
8080

plugins/modules/domain_record.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,11 @@
110110
type=FieldType.integer,
111111
editable=True,
112112
description=[
113-
"The amount of time in seconds that this Domain’s "
114-
"records may be cached by resolvers "
115-
"or other domain servers."
113+
"Time to Live - the amount of time in seconds that this Domain's "
114+
"records may be cached by resolvers or other domain servers. "
115+
"Valid values are 300, 3600, 7200, 14400, 28800, 57600, 86400, "
116+
"172800, 345600, 604800, 1209600, and 2419200 - any other value "
117+
"will be rounded to the nearest valid value."
116118
],
117119
),
118120
"type": SpecField(
@@ -158,6 +160,21 @@
158160
"weight",
159161
}
160162

163+
# Valid TTL values for domain records
164+
VALID_TTL_VALUES = [
165+
300,
166+
3600,
167+
7200,
168+
14400,
169+
28800,
170+
57600,
171+
86400,
172+
172800,
173+
345600,
174+
604800,
175+
1209600,
176+
2419200,
177+
]
161178
DOCUMENTATION = r"""
162179
"""
163180
EXAMPLES = r"""
@@ -187,6 +204,21 @@ def __init__(self) -> None:
187204
mutually_exclusive=self.mutually_exclusive,
188205
)
189206

207+
def _normalize_ttl_sec(self, ttl_value: Optional[int]) -> Optional[int]:
208+
"""Normalize TTL value to the nearest valid value."""
209+
if ttl_value is None:
210+
return None
211+
212+
# Find the closest valid TTL value
213+
closest = min(VALID_TTL_VALUES, key=lambda x: abs(x - ttl_value))
214+
215+
if closest != ttl_value:
216+
self.warn(
217+
f"TTL value {ttl_value} is not valid. Using nearest valid value: {closest}"
218+
)
219+
220+
return closest
221+
190222
def _find_record(
191223
self, domain: Domain, name: str, rtype: str, target: str
192224
) -> Optional[DomainRecord]:
@@ -268,6 +300,10 @@ def _create_record(self) -> Optional[DomainRecord]:
268300
record_name = params.get("name")
269301
record_service = params.get("service")
270302

303+
# Normalize TTL value if provided
304+
if "ttl_sec" in params:
305+
params["ttl_sec"] = self._normalize_ttl_sec(params["ttl_sec"])
306+
271307
try:
272308
self.register_action(
273309
"Created domain record type {0}: name is {1}; service is {2}".format(
@@ -283,9 +319,14 @@ def _create_record(self) -> Optional[DomainRecord]:
283319
def _update_record(self) -> None:
284320
"""Handles all update functionality for the current Domain record"""
285321

322+
# Get filtered parameters and normalize TTL if present
323+
params = filter_null_values(self.module.params)
324+
if "ttl_sec" in params:
325+
params["ttl_sec"] = self._normalize_ttl_sec(params["ttl_sec"])
326+
286327
handle_updates(
287328
self._record,
288-
filter_null_values(self.module.params),
329+
params,
289330
MUTABLE_FIELDS,
290331
self.register_action,
291332
ignore_keys={"name", "record", "target"},

tests/integration/targets/domain_record/tasks/main.yaml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,81 @@
160160
- record_dupe_update.record.ttl_sec == 300
161161
- record_dupe_update.record.weight == 55
162162

163+
- name: Create domain_record with invalid TTL (should round to nearest valid value)
164+
linode.cloud.domain_record:
165+
domain: '{{ domain_create.domain.domain }}'
166+
name: 'ttl-test'
167+
type: 'A'
168+
target: '127.0.0.3'
169+
ttl_sec: 4500 # Invalid value, should round to 3600
170+
state: present
171+
register: record_ttl_invalid
172+
173+
- name: Assert domain_record with invalid TTL is created with rounded value
174+
assert:
175+
that:
176+
- record_ttl_invalid.record.name == 'ttl-test'
177+
- record_ttl_invalid.record.type == 'A'
178+
- record_ttl_invalid.record.target == '127.0.0.3'
179+
- record_ttl_invalid.record.ttl_sec == 3600 # Rounded from 4500 to 3600
180+
181+
- name: Attempt to update with same invalid TTL (should be idempotent, no change)
182+
linode.cloud.domain_record:
183+
domain: '{{ domain_create.domain.domain }}'
184+
record_id: '{{ record_ttl_invalid.record.id }}'
185+
ttl_sec: 4500 # Same invalid value, should normalize to 3600 (no change)
186+
state: present
187+
register: record_ttl_idempotent
188+
189+
- name: Assert no change occurred (idempotency check)
190+
assert:
191+
that:
192+
- record_ttl_idempotent.changed == false
193+
- record_ttl_idempotent.record.ttl_sec == 3600
194+
195+
- name: Test TTL rounding edge cases - value below minimum
196+
linode.cloud.domain_record:
197+
domain: '{{ domain_create.domain.domain }}'
198+
name: 'ttl-min'
199+
type: 'A'
200+
target: '127.0.0.5'
201+
ttl_sec: 100 # Below minimum 300, should round to 300
202+
state: present
203+
register: record_ttl_min
204+
205+
- name: Assert TTL below minimum rounds to 300
206+
assert:
207+
that:
208+
- record_ttl_min.record.name == 'ttl-min'
209+
- record_ttl_min.record.ttl_sec == 300 # Rounded from 100 to 300
210+
211+
- name: Test TTL rounding edge cases - value above maximum
212+
linode.cloud.domain_record:
213+
domain: '{{ domain_create.domain.domain }}'
214+
name: 'ttl-max'
215+
type: 'A'
216+
target: '127.0.0.6'
217+
ttl_sec: 3000000 # Above maximum 2419200, should round to 2419200
218+
state: present
219+
register: record_ttl_max
220+
221+
- name: Assert TTL above maximum rounds to 2419200
222+
assert:
223+
that:
224+
- record_ttl_max.record.name == 'ttl-max'
225+
- record_ttl_max.record.ttl_sec == 2419200 # Rounded from 3000000 to 2419200
226+
227+
- name: Clean up TTL test records
228+
linode.cloud.domain_record:
229+
domain: '{{ domain_create.domain.domain }}'
230+
record_id: '{{ item }}'
231+
state: absent
232+
loop:
233+
- '{{ record_ttl_invalid.record.id }}'
234+
- '{{ record_ttl_min.record.id }}'
235+
- '{{ record_ttl_max.record.id }}'
236+
register: ttl_cleanup
237+
163238
- name: Create an SRV domain record
164239
linode.cloud.domain_record:
165240
domain: '{{ domain_create.domain.domain }}'

0 commit comments

Comments
 (0)