Skip to content

Commit 8e87101

Browse files
committed
Refactor host remove override check, add CNAME+MX confirmation
1 parent 3a9acf5 commit 8e87101

File tree

3 files changed

+104
-70
lines changed

3 files changed

+104
-70
lines changed

ci/testsuite-result.json

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26410,7 +26410,7 @@
2641026410
"command_issued": "host remove bar # should fail, because it has a cname record, must force and override with 'cname'",
2641126411
"ok": [],
2641226412
"warning": [
26413-
"bar.example.org requires force and override for deletion:\n 1 cnames\n - fubar.example.org\n multiple ipaddresses on the same VLAN\nUse `-force -override cname` to override."
26413+
"bar.example.org requires force and override for deletion:\n 1 CNAME records\n - fubar.example.org\n multiple ipaddresses on the same VLAN\nUse `-force -override cname` to override."
2641426414
],
2641526415
"error": [],
2641626416
"output": [],
@@ -26424,8 +26424,8 @@
2642426424
"excluded_ranges": [],
2642526425
"policy": null,
2642626426
"communities": [],
26427-
"created_at": "2025-12-03T13:44:43.360753+01:00",
26428-
"updated_at": "2025-12-03T13:44:43.360763+01:00",
26427+
"created_at": "2026-03-27T11:43:07.055968+01:00",
26428+
"updated_at": "2026-03-27T11:43:07.055977+01:00",
2642926429
"network": "10.0.0.0/24",
2643026430
"description": "lorem ipsum",
2643126431
"vlan": null,
@@ -26446,8 +26446,8 @@
2644626446
"excluded_ranges": [],
2644726447
"policy": null,
2644826448
"communities": [],
26449-
"created_at": "2025-12-03T13:44:43.360753+01:00",
26450-
"updated_at": "2025-12-03T13:44:43.360763+01:00",
26449+
"created_at": "2026-03-27T11:43:07.055968+01:00",
26450+
"updated_at": "2026-03-27T11:43:07.055977+01:00",
2645126451
"network": "10.0.0.0/24",
2645226452
"description": "lorem ipsum",
2645326453
"vlan": null,
@@ -27189,7 +27189,7 @@
2718927189
"command_issued": "host remove baz # Should fail, because it has an MX record, must force and override with 'mx'",
2719027190
"ok": [],
2719127191
"warning": [
27192-
"baz.example.org requires force and override for deletion:\n multiple ipaddresses on the same VLAN\n 1 MX records\n - mail.example.org (priority: 10)\nUse `-force -override mx` to override."
27192+
"baz.example.org requires force and override for deletion:\n 1 MX records\n - mail.example.org (priority: 10)\n multiple ipaddresses on the same VLAN\nUse `-force -override mx` to override."
2719327193
],
2719427194
"error": [],
2719527195
"output": [],
@@ -27203,8 +27203,8 @@
2720327203
"excluded_ranges": [],
2720427204
"policy": null,
2720527205
"communities": [],
27206-
"created_at": "2025-12-03T13:44:43.360753+01:00",
27207-
"updated_at": "2025-12-03T13:44:43.360763+01:00",
27206+
"created_at": "2026-03-27T11:43:07.055968+01:00",
27207+
"updated_at": "2026-03-27T11:43:07.055977+01:00",
2720827208
"network": "10.0.0.0/24",
2720927209
"description": "lorem ipsum",
2721027210
"vlan": null,
@@ -27225,8 +27225,8 @@
2722527225
"excluded_ranges": [],
2722627226
"policy": null,
2722727227
"communities": [],
27228-
"created_at": "2025-12-03T13:44:43.439716+01:00",
27229-
"updated_at": "2025-12-03T13:44:43.439728+01:00",
27228+
"created_at": "2026-03-27T11:43:07.091223+01:00",
27229+
"updated_at": "2026-03-27T11:43:07.091237+01:00",
2723027230
"network": "2001:db8::/64",
2723127231
"description": "dolor sit amet",
2723227232
"vlan": null,
@@ -30576,6 +30576,7 @@
3057630576
"command_filter_negate": false,
3057730577
"command_issued": "host remove foo -force -override mx",
3057830578
"ok": [
30579+
"deleted MX record mail.example.org (priority: 10) when removing foo.example.org",
3057930580
"removed foo.example.org"
3058030581
],
3058130582
"warning": [],
@@ -32287,7 +32288,7 @@
3228732288
"command_issued": "host remove foo # Should fail, because it has a CNAME record, must force and override with 'cname'",
3228832289
"ok": [],
3228932290
"warning": [
32290-
"foo.example.org requires force and override for deletion:\n 1 cnames\n - fubar.example.org\nUse `-force -override cname` to override."
32291+
"foo.example.org requires force and override for deletion:\n 1 CNAME records\n - fubar.example.org\nUse `-force -override cname` to override."
3229132292
],
3229232293
"error": [],
3229332294
"output": [],
@@ -32300,6 +32301,7 @@
3230032301
"command_filter_negate": false,
3230132302
"command_issued": "host remove foo -force -override cname",
3230232303
"ok": [
32304+
"deleted CNAME record fubar.example.org when removing foo.example.org",
3230332305
"removed foo.example.org"
3230432306
],
3230532307
"warning": [],
@@ -33345,7 +33347,7 @@
3334533347
"command_issued": "host remove foo # Should fail, because it has multiple records, must force and override with everything.",
3334633348
"ok": [],
3334733349
"warning": [
33348-
"foo.example.org requires force and override for deletion:\n 1 cnames\n - fubar.example.org\n 1 MX records\n - mail.example.org (priority: 10)\n 1 NAPTR records\n - wonk\n 1 SRV records\n - _sip._tcp.example.org\n 1 PTR records\n - 10.0.0.11\nUse `-force -override cname,mx,naptr,ptr,srv` to override."
33350+
"foo.example.org requires force and override for deletion:\n 1 CNAME records\n - fubar.example.org\n 1 MX records\n - mail.example.org (priority: 10)\n 1 SRV records\n - _sip._tcp.example.org\n 1 PTR records\n - 10.0.0.11\n 1 NAPTR records\n - wonk\nUse `-force -override cname,mx,naptr,ptr,srv` to override."
3334933351
],
3335033352
"error": [],
3335133353
"output": [],
@@ -33358,9 +33360,11 @@
3335833360
"command_filter_negate": false,
3335933361
"command_issued": "host remove foo -force -override mx,ptr,naptr,srv,cname",
3336033362
"ok": [
33361-
"deleted NAPTR record wonk when removing foo.example.org",
33363+
"deleted CNAME record fubar.example.org when removing foo.example.org",
33364+
"deleted MX record mail.example.org (priority: 10) when removing foo.example.org",
3336233365
"deleted SRV record _sip._tcp.example.org when removing foo.example.org",
3336333366
"deleted PTR record 10.0.0.11 when removing foo.example.org",
33367+
"deleted NAPTR record wonk when removing foo.example.org",
3336433368
"removed foo.example.org"
3336533369
],
3336633370
"warning": [],

mreg_cli/commands/host_submodules/core.py

Lines changed: 45 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
import argparse
1717
import re
1818
from enum import Enum
19-
from typing import Self, assert_never
19+
from typing import Any, Generic, NamedTuple, Self, TypeVar
2020

2121
from mreg_api.models import (
22+
CNAME,
23+
MX,
2224
NAPTR,
2325
ForwardZone,
2426
Host,
@@ -29,6 +31,7 @@
2931
PTR_override,
3032
Srv,
3133
)
34+
from mreg_api.models.abstracts import FrozenModel
3235
from mreg_api.models.fields import HostName, MacAddress
3336
from typing_extensions import override
3437

@@ -274,21 +277,34 @@ def parse_overrides(cls, overrides: str) -> list[Override]:
274277
]
275278

276279

277-
def get_record_identifier(record: NAPTR | PTR_override | Srv) -> str:
280+
def get_record_identifier(record: CNAME | MX | NAPTR | PTR_override | Srv) -> str:
278281
"""Get a human readable identifier for a record.
279282
280283
:param record: The record to get the identifier for.
281284
:returns: A human readable identifier for the record.
282285
"""
283286
match record:
284-
case NAPTR():
285-
return record.replacement
287+
case CNAME() | Srv():
288+
return record.name
286289
case PTR_override():
287290
return str(record.ipaddress)
288-
case Srv():
289-
return record.name
291+
case NAPTR():
292+
return record.replacement
293+
case MX():
294+
return f"{record.mx} (priority: {record.priority})"
290295
case _:
291-
assert_never(record)
296+
return repr(record) # pyright: ignore[reportUnreachable] # for safety
297+
298+
299+
T = TypeVar("T")
300+
301+
302+
class OverrideRequired(NamedTuple, Generic[T]):
303+
"""Check for overrides required for a specific host record type."""
304+
305+
override: Override
306+
name: str
307+
items: list[T]
292308

293309

294310
@command_registry.register_command(
@@ -340,15 +356,25 @@ def forced(override_required: Override | None = None) -> bool:
340356
# And the fallback is "no".
341357
return False
342358

359+
override_checks: list[OverrideRequired[Any]] = [
360+
OverrideRequired(Override.CNAME, "CNAME", host.cnames),
361+
OverrideRequired(Override.MX, "MX", host.mxs),
362+
OverrideRequired(Override.SRV, "SRV", host.srvs),
363+
OverrideRequired(Override.PTR, "PTR", host.ptr_overrides),
364+
OverrideRequired(Override.NAPTR, "NAPTR", host.naptrs),
365+
]
366+
367+
# Determine required overrides and build warning message
368+
# if force and override requirements are not met
343369
warnings: list[str] = []
344370
overrides_required: set[Override] = set()
345-
346-
# Require force if host has any cnames.
347-
if host.cnames and not forced(Override.CNAME):
348-
overrides_required.add(Override.CNAME)
349-
warnings.append(f" {len(host.cnames)} cnames")
350-
for cname in host.cnames:
351-
warnings.append(f" - {cname.name}")
371+
for check in override_checks:
372+
if check.items and not forced(check.override):
373+
overrides_required.add(check.override)
374+
warnings.append(f" {len(check.items)} {check.name} records")
375+
for item in check.items:
376+
value = get_record_identifier(item)
377+
warnings.append(f" - {value}")
352378

353379
# Require force if host has multiple A/AAAA records and they are not in the same VLAN.
354380
if len(host.ipaddresses) > 1:
@@ -365,39 +391,6 @@ def forced(override_required: Override | None = None) -> bool:
365391
ip_strings.sort()
366392
warnings.append(f" - {', '.join(ip_strings)} (vlan: {vlan_id})")
367393

368-
if host.mxs and not forced(Override.MX):
369-
overrides_required.add(Override.MX)
370-
warnings.append(f" {len(host.mxs)} MX records")
371-
for mx in host.mxs:
372-
warnings.append(f" - {mx.mx} (priority: {mx.priority})")
373-
374-
# Require force if host has any NAPTR records. Delete the NAPTR records if
375-
# force
376-
naptrs = host.naptrs
377-
if len(naptrs) > 0:
378-
if not forced(Override.NAPTR):
379-
overrides_required.add(Override.NAPTR)
380-
warnings.append(f" {len(naptrs)} NAPTR records")
381-
for naptr in naptrs:
382-
warnings.append(f" - {naptr.replacement}")
383-
384-
# Require force if host has any SRV records. Delete the SRV records if force
385-
srvs = host.srvs
386-
if len(srvs) > 0:
387-
if not forced(Override.SRV):
388-
overrides_required.add(Override.SRV)
389-
warnings.append(f" {len(srvs)} SRV records")
390-
for srv in srvs:
391-
warnings.append(f" - {srv.name}")
392-
393-
# Require force if host has any PTR records. Delete the PTR records if force
394-
if len(host.ptr_overrides) > 0:
395-
if not forced(Override.PTR):
396-
overrides_required.add(Override.PTR)
397-
warnings.append(f" {len(host.ptr_overrides)} PTR records")
398-
for ptr in host.ptr_overrides:
399-
warnings.append(f" - {ptr.ipaddress}")
400-
401394
# Warn user and raise exception if any force requirements was found
402395
if warnings:
403396
# Build the override command suggestion
@@ -428,16 +421,13 @@ def forced(override_required: Override | None = None) -> bool:
428421
if not host.delete():
429422
raise DeleteError(f"failed to remove {host.name}")
430423

431-
for record_name, record in [
432-
("NAPTR", host.naptrs),
433-
("SRV", host.srvs),
434-
("PTR", host.ptr_overrides),
435-
]:
436-
for rec in record:
424+
# Print messages for associated records that were deleted
425+
for check in override_checks:
426+
for item in check.items:
437427
OutputManager().add_ok(
438428
(
439-
f"deleted {record_name} record "
440-
f"{get_record_identifier(rec)} when removing {host.name}"
429+
f"deleted {check.name} record "
430+
f"{get_record_identifier(item)} when removing {host.name}"
441431
)
442432
)
443433
OutputManager().add_ok(f"removed {host.name}")

tests/commands/test_host.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import pytest
77
from inline_snapshot import snapshot
8-
from mreg_api.models import NAPTR, PTR_override, Srv
8+
from mreg_api.models import CNAME, MX, NAPTR, SSHFP, PTR_override, Srv
99

1010
from mreg_cli.commands.host_submodules.core import Override, get_record_identifier
1111
from mreg_cli.exceptions import InputFailure
@@ -117,8 +117,48 @@ def test_override_from_string() -> None:
117117
),
118118
"srv.example.com",
119119
),
120+
pytest.param(
121+
CNAME(
122+
host=123,
123+
created_at=datetime.datetime(2026, 1, 1),
124+
updated_at=datetime.datetime(2026, 1, 1),
125+
id=1,
126+
name="alias.example.com", # pyright: ignore[reportArgumentType]
127+
),
128+
"alias.example.com",
129+
),
130+
pytest.param(
131+
MX(
132+
host=123,
133+
created_at=datetime.datetime(2026, 1, 1),
134+
updated_at=datetime.datetime(2026, 1, 1),
135+
id=1,
136+
mx="mail.example.com",
137+
priority=10,
138+
),
139+
"mail.example.com (priority: 10)",
140+
),
120141
],
121142
)
122-
def test_get_record_identifier(record: NAPTR | PTR_override | Srv, expected: str) -> None:
143+
def test_get_record_identifier(
144+
record: NAPTR | PTR_override | Srv | CNAME | MX, expected: str
145+
) -> None:
123146
"""Test get_record_identifier with different record types."""
124147
assert get_record_identifier(record) == expected
148+
149+
150+
def test_get_record_identifier_unknown() -> None:
151+
"""Test get_record_identifier repr() fallback with an unhandled record type."""
152+
record = SSHFP(
153+
host=123,
154+
created_at=datetime.datetime(2026, 1, 1),
155+
updated_at=datetime.datetime(2026, 1, 1),
156+
id=1,
157+
algorithm=1,
158+
hash_type=2,
159+
fingerprint="abc123",
160+
ttl=3600,
161+
)
162+
assert get_record_identifier(record) == snapshot( # pyright: ignore[reportArgumentType]
163+
"SSHFP(host=123, created_at=datetime.datetime(2026, 1, 1, 0, 0), updated_at=datetime.datetime(2026, 1, 1, 0, 0), id=1, algorithm=1, hash_type=2, fingerprint='abc123', ttl=3600)"
164+
)

0 commit comments

Comments
 (0)