Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/modules/domain_record.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ NOTE: Domain records are identified by their name, target, and type.
| `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)** |
| `tag` | <center>`str`</center> | <center>Optional</center> | The tag portion of a CAA record. Only valid and required for CAA record requests. **(Updatable)** |
| `target` | <center>`str`</center> | <center>Optional</center> | The target for this Record. |
| `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)** |
| `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)** |
| `type` | <center>`str`</center> | <center>Optional</center> | The type of Record this is in the DNS system. |
| `weight` | <center>`int`</center> | <center>Optional</center> | The relative weight of this Record used in the case of identical priority. **(Updatable)** |

Expand Down
49 changes: 45 additions & 4 deletions plugins/modules/domain_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,11 @@
type=FieldType.integer,
editable=True,
description=[
"The amount of time in seconds that this Domain’s "
"records may be cached by resolvers "
"or other domain servers."
"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."
],
),
"type": SpecField(
Expand Down Expand Up @@ -158,6 +160,21 @@
"weight",
}

# Valid TTL values for domain records
VALID_TTL_VALUES = [
300,
3600,
7200,
14400,
28800,
57600,
86400,
172800,
345600,
604800,
1209600,
2419200,
]
DOCUMENTATION = r"""
"""
EXAMPLES = r"""
Expand Down Expand Up @@ -187,6 +204,21 @@ def __init__(self) -> None:
mutually_exclusive=self.mutually_exclusive,
)

def _normalize_ttl_sec(self, ttl_value: Optional[int]) -> Optional[int]:
"""Normalize TTL value to the nearest valid value."""
if ttl_value is None:
return None

# Find the closest valid TTL value
closest = min(VALID_TTL_VALUES, key=lambda x: abs(x - ttl_value))

if closest != ttl_value:
self.warn(
f"TTL value {ttl_value} is not valid. Using nearest valid value: {closest}"
)

return closest

def _find_record(
self, domain: Domain, name: str, rtype: str, target: str
) -> Optional[DomainRecord]:
Expand Down Expand Up @@ -268,6 +300,10 @@ def _create_record(self) -> Optional[DomainRecord]:
record_name = params.get("name")
record_service = params.get("service")

# Normalize TTL value if provided
if "ttl_sec" in params:
params["ttl_sec"] = self._normalize_ttl_sec(params["ttl_sec"])

try:
self.register_action(
"Created domain record type {0}: name is {1}; service is {2}".format(
Expand All @@ -283,9 +319,14 @@ def _create_record(self) -> Optional[DomainRecord]:
def _update_record(self) -> None:
"""Handles all update functionality for the current Domain record"""

# Get filtered parameters and normalize TTL if present
params = filter_null_values(self.module.params)
if "ttl_sec" in params:
params["ttl_sec"] = self._normalize_ttl_sec(params["ttl_sec"])

handle_updates(
self._record,
filter_null_values(self.module.params),
params,
MUTABLE_FIELDS,
self.register_action,
ignore_keys={"name", "record", "target"},
Expand Down
75 changes: 75 additions & 0 deletions tests/integration/targets/domain_record/tasks/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,81 @@
- record_dupe_update.record.ttl_sec == 300
- record_dupe_update.record.weight == 55

- name: Create domain_record with invalid TTL (should round to nearest valid value)
linode.cloud.domain_record:
domain: '{{ domain_create.domain.domain }}'
name: 'ttl-test'
type: 'A'
target: '127.0.0.3'
ttl_sec: 4500 # Invalid value, should round to 3600
state: present
register: record_ttl_invalid

- name: Assert domain_record with invalid TTL is created with rounded value
assert:
that:
- record_ttl_invalid.record.name == 'ttl-test'
- record_ttl_invalid.record.type == 'A'
- record_ttl_invalid.record.target == '127.0.0.3'
- record_ttl_invalid.record.ttl_sec == 3600 # Rounded from 4500 to 3600

- name: Attempt to update with same invalid TTL (should be idempotent, no change)
linode.cloud.domain_record:
domain: '{{ domain_create.domain.domain }}'
record_id: '{{ record_ttl_invalid.record.id }}'
ttl_sec: 4500 # Same invalid value, should normalize to 3600 (no change)
state: present
register: record_ttl_idempotent

- name: Assert no change occurred (idempotency check)
assert:
that:
- record_ttl_idempotent.changed == false
- record_ttl_idempotent.record.ttl_sec == 3600

- name: Test TTL rounding edge cases - value below minimum
linode.cloud.domain_record:
domain: '{{ domain_create.domain.domain }}'
name: 'ttl-min'
type: 'A'
target: '127.0.0.5'
ttl_sec: 100 # Below minimum 300, should round to 300
state: present
register: record_ttl_min

- name: Assert TTL below minimum rounds to 300
assert:
that:
- record_ttl_min.record.name == 'ttl-min'
- record_ttl_min.record.ttl_sec == 300 # Rounded from 100 to 300

- name: Test TTL rounding edge cases - value above maximum
linode.cloud.domain_record:
domain: '{{ domain_create.domain.domain }}'
name: 'ttl-max'
type: 'A'
target: '127.0.0.6'
ttl_sec: 3000000 # Above maximum 2419200, should round to 2419200
state: present
register: record_ttl_max

- name: Assert TTL above maximum rounds to 2419200
assert:
that:
- record_ttl_max.record.name == 'ttl-max'
- record_ttl_max.record.ttl_sec == 2419200 # Rounded from 3000000 to 2419200

- name: Clean up TTL test records
linode.cloud.domain_record:
domain: '{{ domain_create.domain.domain }}'
record_id: '{{ item }}'
state: absent
loop:
- '{{ record_ttl_invalid.record.id }}'
- '{{ record_ttl_min.record.id }}'
- '{{ record_ttl_max.record.id }}'
register: ttl_cleanup

- name: Create an SRV domain record
linode.cloud.domain_record:
domain: '{{ domain_create.domain.domain }}'
Expand Down