mreg-cli 1.4.0__tar.gz → 1.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/CHANGELOG.md +13 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/PKG-INFO +1 -1
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/ci/testsuite-result.json +22 -22
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/_version.py +2 -2
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/api/models.py +72 -10
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/host_submodules/rr.py +7 -8
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/network.py +4 -1
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli.egg-info/PKG-INFO +1 -1
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/tests/api/test_models.py +82 -1
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/.github/workflows/publish.yml +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/.github/workflows/test.yml +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/.gitignore +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/.markdownlint.json +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/AUTHORS +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/CONTRIBUTING +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/LICENSE +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/README.md +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/ci/Dockerfile +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/ci/diff.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/ci/docker-compose.yml +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/ci/run_testsuite_and_record.sh +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/ci/run_testsuite_and_record_V2.sh +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/ci/testsuite +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/data/mreg-cli.conf +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/__about__.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/__init__.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/__main__.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/api/__init__.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/api/abstracts.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/api/endpoints.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/api/errors.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/api/fields.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/api/history.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/cli.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/__init__.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/base.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/dhcp.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/group.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/help.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/host.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/host_submodules/__init__.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/host_submodules/a_aaaa.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/host_submodules/bacnet.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/host_submodules/cname.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/host_submodules/core.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/label.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/logging.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/permission.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/policy.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/recording.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/registry.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/root.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/commands/zone.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/config.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/errorbuilder.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/exceptions.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/help_formatter.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/main.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/outputmanager.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/prompt.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/py.typed +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/tags.txt +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/tokenfile.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/types.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/utilities/__init__.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/utilities/api.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/utilities/shared.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli/utilities/validators.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli.egg-info/SOURCES.txt +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli.egg-info/dependency_links.txt +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli.egg-info/entry_points.txt +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli.egg-info/requires.txt +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/mreg_cli.egg-info/top_level.txt +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/pyproject.toml +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/run.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/setup.cfg +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/tests/__init__.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/tests/api/__init__.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/tests/api/test_fields.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/tests/conftest.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/tests/test_errorbuilder.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/tests/test_exceptions.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/tests/test_outputmanager.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/tests/test_prompt.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/tests/test_tokenfile.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/tests/test_types.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/tests/utilities/__init__.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/tests/utilities/test_api.py +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/tox.ini +0 -0
- {mreg_cli-1.4.0 → mreg_cli-1.4.2}/uv.lock +0 -0
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
<!-- ## Unreleased -->
|
|
9
9
|
|
|
10
|
+
## [1.4.2](https://github.com/unioslo/mreg-cli/releases/tag/1.4.2) - 2025-08-15
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- `host info` for hosts with A(AAA)-records that point to networks not managed by MREG.
|
|
15
|
+
- `network community_host_add` raising a 404 error for hosts with IPs in networks not managed by MREG.
|
|
16
|
+
|
|
17
|
+
## [1.4.1](https://github.com/unioslo/mreg-cli/releases/tag/1.4.1) - 2025-08-06
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- `ptr add -force` now allows adding PTR records for IPs that are not in a network managed by MREG.
|
|
22
|
+
|
|
10
23
|
## [1.4.0](https://github.com/unioslo/mreg-cli/releases/tag/1.4.0) - 2025-06-20
|
|
11
24
|
|
|
12
25
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mreg-cli
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.2
|
|
4
4
|
Summary: MREG interactive command line client
|
|
5
5
|
Author: Others (see AUTHORS)
|
|
6
6
|
Author-email: Øyvind Kolbu <oyvind.kolbu@usit.uio.no>, Safet A <amedov@usit.uio.no>, Magnus Hirth <mhmorgan42@gmail.com>, Nico <nicolaydmohebi@gmail.com>, Marius Bakke <marius.bakke@usit.uio.no>, Fredrik Larsen <fredrik.larsen@usit.uio.no>, Paal Braathen <paal.braathen@usit.uio.no>, Nils Hiorth <nils@nls.me>, Dmytro Karpenko <dmytrok@usit.uio.no>
|
|
@@ -23044,7 +23044,7 @@
|
|
|
23044
23044
|
"command_issued": "dhcp assoc foo aa:bb:cc:dd:ee:ff # should fail, two IPs of same type",
|
|
23045
23045
|
"ok": [],
|
|
23046
23046
|
"warning": [
|
|
23047
|
-
"Host foo.example.org has multiple IPs, cannot determine which one to use."
|
|
23047
|
+
"Host foo.example.org has multiple IPs, cannot determine which one to use: 10.0.0.5, 10.0.0.6."
|
|
23048
23048
|
],
|
|
23049
23049
|
"error": [],
|
|
23050
23050
|
"output": [],
|
|
@@ -23070,15 +23070,15 @@
|
|
|
23070
23070
|
"ipaddresses": [
|
|
23071
23071
|
{
|
|
23072
23072
|
"macaddress": "",
|
|
23073
|
-
"created_at": "2025-
|
|
23074
|
-
"updated_at": "2025-
|
|
23073
|
+
"created_at": "2025-08-13T10:41:00.041467+02:00",
|
|
23074
|
+
"updated_at": "2025-08-13T10:41:00.041474+02:00",
|
|
23075
23075
|
"ipaddress": "10.0.0.5",
|
|
23076
23076
|
"host": 18
|
|
23077
23077
|
},
|
|
23078
23078
|
{
|
|
23079
23079
|
"macaddress": "",
|
|
23080
|
-
"created_at": "2025-
|
|
23081
|
-
"updated_at": "2025-
|
|
23080
|
+
"created_at": "2025-08-13T10:41:00.185731+02:00",
|
|
23081
|
+
"updated_at": "2025-08-13T10:41:00.185738+02:00",
|
|
23082
23082
|
"ipaddress": "10.0.0.6",
|
|
23083
23083
|
"host": 18
|
|
23084
23084
|
}
|
|
@@ -23087,8 +23087,8 @@
|
|
|
23087
23087
|
"mxs": [],
|
|
23088
23088
|
"txts": [
|
|
23089
23089
|
{
|
|
23090
|
-
"created_at": "2025-
|
|
23091
|
-
"updated_at": "2025-
|
|
23090
|
+
"created_at": "2025-08-13T10:40:58.935125+02:00",
|
|
23091
|
+
"updated_at": "2025-08-13T10:40:58.935130+02:00",
|
|
23092
23092
|
"txt": "v=spf1 -all",
|
|
23093
23093
|
"host": 18
|
|
23094
23094
|
}
|
|
@@ -23103,8 +23103,8 @@
|
|
|
23103
23103
|
"loc": null,
|
|
23104
23104
|
"bacnetid": null,
|
|
23105
23105
|
"communities": [],
|
|
23106
|
-
"created_at": "2025-
|
|
23107
|
-
"updated_at": "2025-
|
|
23106
|
+
"created_at": "2025-08-13T10:40:58.933270+02:00",
|
|
23107
|
+
"updated_at": "2025-08-13T10:40:58.933280+02:00",
|
|
23108
23108
|
"name": "foo.example.org",
|
|
23109
23109
|
"contact": "",
|
|
23110
23110
|
"ttl": null,
|
|
@@ -23513,7 +23513,7 @@
|
|
|
23513
23513
|
"command_issued": "dhcp assoc foo aa:bb:cc:dd:ee:ff # should fail, the host has two IPs of different types on different VLANs.",
|
|
23514
23514
|
"ok": [],
|
|
23515
23515
|
"warning": [
|
|
23516
|
-
"Host foo.example.org has multiple IPs, cannot determine which one to use."
|
|
23516
|
+
"Host foo.example.org has multiple IPs, cannot determine which one to use: 10.0.0.5, 2001:db9::5."
|
|
23517
23517
|
],
|
|
23518
23518
|
"error": [],
|
|
23519
23519
|
"output": [],
|
|
@@ -23539,15 +23539,15 @@
|
|
|
23539
23539
|
"ipaddresses": [
|
|
23540
23540
|
{
|
|
23541
23541
|
"macaddress": "",
|
|
23542
|
-
"created_at": "2025-
|
|
23543
|
-
"updated_at": "2025-
|
|
23542
|
+
"created_at": "2025-08-13T10:41:00.041467+02:00",
|
|
23543
|
+
"updated_at": "2025-08-13T10:41:00.041474+02:00",
|
|
23544
23544
|
"ipaddress": "10.0.0.5",
|
|
23545
23545
|
"host": 18
|
|
23546
23546
|
},
|
|
23547
23547
|
{
|
|
23548
23548
|
"macaddress": "",
|
|
23549
|
-
"created_at": "2025-
|
|
23550
|
-
"updated_at": "2025-
|
|
23549
|
+
"created_at": "2025-08-13T10:41:00.786601+02:00",
|
|
23550
|
+
"updated_at": "2025-08-13T10:41:00.786608+02:00",
|
|
23551
23551
|
"ipaddress": "2001:db9::5",
|
|
23552
23552
|
"host": 18
|
|
23553
23553
|
}
|
|
@@ -23556,8 +23556,8 @@
|
|
|
23556
23556
|
"mxs": [],
|
|
23557
23557
|
"txts": [
|
|
23558
23558
|
{
|
|
23559
|
-
"created_at": "2025-
|
|
23560
|
-
"updated_at": "2025-
|
|
23559
|
+
"created_at": "2025-08-13T10:40:58.935125+02:00",
|
|
23560
|
+
"updated_at": "2025-08-13T10:40:58.935130+02:00",
|
|
23561
23561
|
"txt": "v=spf1 -all",
|
|
23562
23562
|
"host": 18
|
|
23563
23563
|
}
|
|
@@ -23572,8 +23572,8 @@
|
|
|
23572
23572
|
"loc": null,
|
|
23573
23573
|
"bacnetid": null,
|
|
23574
23574
|
"communities": [],
|
|
23575
|
-
"created_at": "2025-
|
|
23576
|
-
"updated_at": "2025-
|
|
23575
|
+
"created_at": "2025-08-13T10:40:58.933270+02:00",
|
|
23576
|
+
"updated_at": "2025-08-13T10:40:58.933280+02:00",
|
|
23577
23577
|
"name": "foo.example.org",
|
|
23578
23578
|
"contact": "",
|
|
23579
23579
|
"ttl": null,
|
|
@@ -23590,8 +23590,8 @@
|
|
|
23590
23590
|
"excluded_ranges": [],
|
|
23591
23591
|
"policy": null,
|
|
23592
23592
|
"communities": [],
|
|
23593
|
-
"created_at": "2025-
|
|
23594
|
-
"updated_at": "2025-
|
|
23593
|
+
"created_at": "2025-08-13T10:40:58.822374+02:00",
|
|
23594
|
+
"updated_at": "2025-08-13T10:40:58.822385+02:00",
|
|
23595
23595
|
"network": "10.0.0.0/24",
|
|
23596
23596
|
"description": "foo",
|
|
23597
23597
|
"vlan": 1234,
|
|
@@ -23611,8 +23611,8 @@
|
|
|
23611
23611
|
"excluded_ranges": [],
|
|
23612
23612
|
"policy": null,
|
|
23613
23613
|
"communities": [],
|
|
23614
|
-
"created_at": "2025-
|
|
23615
|
-
"updated_at": "2025-
|
|
23614
|
+
"created_at": "2025-08-13T10:41:00.673383+02:00",
|
|
23615
|
+
"updated_at": "2025-08-13T10:41:00.673397+02:00",
|
|
23616
23616
|
"network": "2001:db9::/64",
|
|
23617
23617
|
"description": "notfoo_ipv6",
|
|
23618
23618
|
"vlan": 1235,
|
|
@@ -24,6 +24,7 @@ from mreg_cli.api.endpoints import Endpoint
|
|
|
24
24
|
from mreg_cli.api.fields import HostName, MacAddress, NameList
|
|
25
25
|
from mreg_cli.api.history import HistoryItem, HistoryResource
|
|
26
26
|
from mreg_cli.exceptions import (
|
|
27
|
+
APINotOk,
|
|
27
28
|
CreateError,
|
|
28
29
|
DeleteError,
|
|
29
30
|
EntityAlreadyExists,
|
|
@@ -1615,6 +1616,39 @@ class Network(FrozenModelWithTimestamps, APIMixin):
|
|
|
1615
1616
|
"""Return the endpoint for the class."""
|
|
1616
1617
|
return Endpoint.Networks
|
|
1617
1618
|
|
|
1619
|
+
@classmethod
|
|
1620
|
+
def dummy_network_from_ip(cls, ip: IPAddress) -> Self:
|
|
1621
|
+
"""Create a Network object for an unknown network given an IP.
|
|
1622
|
+
|
|
1623
|
+
NOTE: Does not perform any API calls. This is purely to work around
|
|
1624
|
+
the fact that MREG supports creating IP addresses for networks that
|
|
1625
|
+
are not registered in MREG.
|
|
1626
|
+
"""
|
|
1627
|
+
if ip.is_ipv4():
|
|
1628
|
+
network_addr = "0.0.0.0/24"
|
|
1629
|
+
description = "Unknown IPv4 network"
|
|
1630
|
+
else:
|
|
1631
|
+
network_addr = "::/64"
|
|
1632
|
+
description = "Unknown IPv6 network"
|
|
1633
|
+
|
|
1634
|
+
return cls(
|
|
1635
|
+
id=0,
|
|
1636
|
+
excluded_ranges=[],
|
|
1637
|
+
network=network_addr,
|
|
1638
|
+
description=description,
|
|
1639
|
+
vlan=None,
|
|
1640
|
+
dns_delegated=False,
|
|
1641
|
+
category="",
|
|
1642
|
+
location="",
|
|
1643
|
+
frozen=False,
|
|
1644
|
+
reserved=0,
|
|
1645
|
+
policy=None,
|
|
1646
|
+
communities=[],
|
|
1647
|
+
# epoch time
|
|
1648
|
+
created_at=datetime.fromtimestamp(0),
|
|
1649
|
+
updated_at=datetime.fromtimestamp(0),
|
|
1650
|
+
)
|
|
1651
|
+
|
|
1618
1652
|
@classmethod
|
|
1619
1653
|
def get_by_any_means(cls, identifier: str) -> Self | None:
|
|
1620
1654
|
"""Get a network by the given identifier.
|
|
@@ -2412,14 +2446,18 @@ class IPAddress(FrozenModelWithTimestamps, WithHost, APIMixin):
|
|
|
2412
2446
|
"""Return True if the IP address is IPv6."""
|
|
2413
2447
|
return self.ipaddress.version == 6
|
|
2414
2448
|
|
|
2415
|
-
def network(self) -> Network:
|
|
2449
|
+
def network(self) -> Network | None:
|
|
2416
2450
|
"""Return the network of the IP address."""
|
|
2417
|
-
|
|
2418
|
-
|
|
2451
|
+
try:
|
|
2452
|
+
return get_typed(Endpoint.NetworksByIP.with_id(str(self.ipaddress)), Network)
|
|
2453
|
+
except APINotOk:
|
|
2454
|
+
return None
|
|
2419
2455
|
|
|
2420
2456
|
def vlan(self) -> int | None:
|
|
2421
2457
|
"""Return the VLAN of the IP address."""
|
|
2422
|
-
|
|
2458
|
+
if net := self.network():
|
|
2459
|
+
return net.vlan
|
|
2460
|
+
return None
|
|
2423
2461
|
|
|
2424
2462
|
def associate_mac(self, mac: MacAddress, force: bool = False) -> IPAddress:
|
|
2425
2463
|
"""Associate a MAC address with the IP address.
|
|
@@ -3394,14 +3432,32 @@ class Host(FrozenModelWithTimestamps, WithTTL, WithHistory, APIMixin):
|
|
|
3394
3432
|
ipv6s = self.ipv6_addresses()
|
|
3395
3433
|
|
|
3396
3434
|
if len(ipv4s) == 1 and len(ipv6s) == 1:
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3435
|
+
ipv4_address = ipv4s[0]
|
|
3436
|
+
ipv6_address = ipv6s[0]
|
|
3437
|
+
ipv4_network = ipv4_address.network()
|
|
3438
|
+
ipv6_network = ipv6_address.network()
|
|
3439
|
+
|
|
3440
|
+
if ipv4_network and ipv6_network:
|
|
3441
|
+
if ipv4_network.vlan == ipv6_network.vlan:
|
|
3442
|
+
return ipv4_address
|
|
3443
|
+
elif ipv4_network: # only IPv4 is in mreg
|
|
3444
|
+
logger.warning(
|
|
3445
|
+
"Host '%s' has IPv6 address not in MREG: %s",
|
|
3446
|
+
self.name,
|
|
3447
|
+
str(ipv6_address.ipaddress),
|
|
3448
|
+
)
|
|
3449
|
+
return ipv4_address
|
|
3450
|
+
elif ipv6_network: # only IPv6 is in mreg
|
|
3451
|
+
logger.warning(
|
|
3452
|
+
"Host '%s' has IPv4 address not in MREG: %s",
|
|
3453
|
+
self.name,
|
|
3454
|
+
str(ipv4_address.ipaddress),
|
|
3455
|
+
)
|
|
3456
|
+
return ipv6_address
|
|
3402
3457
|
|
|
3458
|
+
ips = ", ".join(str(ip.ipaddress) for ip in self.ipaddresses)
|
|
3403
3459
|
raise EntityOwnershipMismatch(
|
|
3404
|
-
f"Host {self} has multiple IPs, cannot determine which one to use."
|
|
3460
|
+
f"Host {self} has multiple IPs, cannot determine which one to use: {ips}."
|
|
3405
3461
|
)
|
|
3406
3462
|
|
|
3407
3463
|
def has_ptr_override(self, arg_ip: IP_AddressT) -> bool:
|
|
@@ -3554,12 +3610,18 @@ class Host(FrozenModelWithTimestamps, WithTTL, WithHistory, APIMixin):
|
|
|
3554
3610
|
def networks(self) -> dict[Network, list[IPAddress]]:
|
|
3555
3611
|
"""Return a dict of unique networks and a list of associated IP addresses for the host.
|
|
3556
3612
|
|
|
3613
|
+
Does not return networks that are not registered in MREG.
|
|
3614
|
+
|
|
3557
3615
|
:returns: A dictionary of networks and the associated IP addresses.
|
|
3558
3616
|
"""
|
|
3559
3617
|
ret_dict: dict[Network, list[IPAddress]] = {}
|
|
3560
3618
|
|
|
3561
3619
|
for ip in self.ipaddresses:
|
|
3562
3620
|
network = ip.network()
|
|
3621
|
+
if not network:
|
|
3622
|
+
# If network is not in MREG, we create a placeholder network
|
|
3623
|
+
network = Network.dummy_network_from_ip(ip)
|
|
3624
|
+
|
|
3563
3625
|
if network not in ret_dict:
|
|
3564
3626
|
ret_dict[network] = []
|
|
3565
3627
|
|
|
@@ -534,15 +534,14 @@ def ptr_add(args: argparse.Namespace) -> None:
|
|
|
534
534
|
if existing_ptrs:
|
|
535
535
|
raise EntityAlreadyExists(f"{ip} already exists in a PTR record.")
|
|
536
536
|
|
|
537
|
-
if host.zone is None and not args.force:
|
|
538
|
-
raise ForceMissing(f"{host} isn't in a zone controlled by MREG, must force")
|
|
539
|
-
|
|
540
537
|
network = Network.get_by_ip(ip)
|
|
541
|
-
if not
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
538
|
+
if not args.force:
|
|
539
|
+
if host.zone is None:
|
|
540
|
+
raise ForceMissing(f"{host} isn't in a zone controlled by MREG, must force")
|
|
541
|
+
elif not network:
|
|
542
|
+
raise ForceMissing(f"{ip} isn't in a network controlled by MREG, must force")
|
|
543
|
+
elif network and network.is_reserved_ip(ip):
|
|
544
|
+
raise ForceMissing(f"{ip} is reserved, must force")
|
|
546
545
|
|
|
547
546
|
PTR_override.create({"host": host.id, "ipaddress": str(ip)})
|
|
548
547
|
OutputManager().add_ok(f"Added PTR record {ip} to {host.name}.")
|
|
@@ -1223,7 +1223,10 @@ def community_host_add(args: argparse.Namespace) -> None:
|
|
|
1223
1223
|
|
|
1224
1224
|
h = Host.get_by_any_means_or_raise(host)
|
|
1225
1225
|
ipaddr = _check_host_ip(h, ip)
|
|
1226
|
-
|
|
1226
|
+
|
|
1227
|
+
if not (net := ipaddr.network()):
|
|
1228
|
+
raise EntityNotFound(f"{h.name!r} is not in a network controlled by MREG.")
|
|
1229
|
+
|
|
1227
1230
|
com = net.get_community_or_raise(community)
|
|
1228
1231
|
|
|
1229
1232
|
com.add_host(h, ipaddress=ipaddr.ipaddress)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mreg-cli
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.2
|
|
4
4
|
Summary: MREG interactive command line client
|
|
5
5
|
Author: Others (see AUTHORS)
|
|
6
6
|
Author-email: Øyvind Kolbu <oyvind.kolbu@usit.uio.no>, Safet A <amedov@usit.uio.no>, Magnus Hirth <mhmorgan42@gmail.com>, Nico <nicolaydmohebi@gmail.com>, Marius Bakke <marius.bakke@usit.uio.no>, Fredrik Larsen <fredrik.larsen@usit.uio.no>, Paal Braathen <paal.braathen@usit.uio.no>, Nils Hiorth <nils@nls.me>, Dmytro Karpenko <dmytrok@usit.uio.no>
|
|
@@ -6,7 +6,7 @@ from typing import Any, Callable
|
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
8
|
|
|
9
|
-
from mreg_cli.api.models import IPNetMode, Network, NetworkOrIP
|
|
9
|
+
from mreg_cli.api.models import IPAddress, IPNetMode, Network, NetworkOrIP
|
|
10
10
|
from mreg_cli.exceptions import (
|
|
11
11
|
InputFailure,
|
|
12
12
|
InvalidIPAddress,
|
|
@@ -135,3 +135,84 @@ def test_network_ip_network(inp: str, expect: IP_NetworkT) -> None:
|
|
|
135
135
|
assert network.ip_network == expect
|
|
136
136
|
assert network.broadcast_address == expect.broadcast_address
|
|
137
137
|
assert network.network_address == expect.network_address
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@pytest.mark.parametrize(
|
|
141
|
+
"inp",
|
|
142
|
+
[
|
|
143
|
+
"192.168.0.1",
|
|
144
|
+
"0.0.0.0",
|
|
145
|
+
"10.0.0.1",
|
|
146
|
+
],
|
|
147
|
+
)
|
|
148
|
+
def test_network_dummy_network_from_ip_v4(inp: str) -> None:
|
|
149
|
+
"""Test creating a dummy network from an IPv4 address."""
|
|
150
|
+
_test_network_dummy_network(IPv4Address(inp), expected_version=4)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@pytest.mark.parametrize(
|
|
154
|
+
"inp",
|
|
155
|
+
[
|
|
156
|
+
"2001:db8::1",
|
|
157
|
+
"::1",
|
|
158
|
+
"fe80::1",
|
|
159
|
+
],
|
|
160
|
+
)
|
|
161
|
+
def test_network_dummy_network_from_ip_v6(inp: str) -> None:
|
|
162
|
+
"""Test creating a dummy network from an IPv6 address."""
|
|
163
|
+
_test_network_dummy_network(IPv6Address(inp), expected_version=6)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _test_network_dummy_network(inp: IPv4Address | IPv6Address, expected_version: int) -> None:
|
|
167
|
+
"""Helper function to test creating a dummy network from an IP address.""" # noqa: D401
|
|
168
|
+
ip = _get_mreg_ipaddress(inp)
|
|
169
|
+
network = Network.dummy_network_from_ip(ip)
|
|
170
|
+
assert isinstance(network, Network)
|
|
171
|
+
assert network.ip_network.version == expected_version
|
|
172
|
+
|
|
173
|
+
# Ensure all dummy networks for the given IP type are hashed
|
|
174
|
+
# the same and equal to each other, so that all dummy networks
|
|
175
|
+
# of a given IP version are considered the same.
|
|
176
|
+
network2 = Network.dummy_network_from_ip(ip)
|
|
177
|
+
assert network == network2
|
|
178
|
+
assert hash(network) == hash(network2)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _get_mreg_ipaddress(ip: IPv4Address | IPv6Address) -> IPAddress:
|
|
182
|
+
"""Construct an mreg IPAddress object from an IP."""
|
|
183
|
+
return IPAddress(
|
|
184
|
+
id=0,
|
|
185
|
+
host=0,
|
|
186
|
+
created_at=datetime.now(),
|
|
187
|
+
updated_at=datetime.now(),
|
|
188
|
+
ipaddress=ip,
|
|
189
|
+
# no mac
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_network_dummy_network_from_ip_identity() -> None:
|
|
194
|
+
"""Test hashing of dummy networks from identical and differenet IP versions."""
|
|
195
|
+
ipv4_1 = _get_mreg_ipaddress(IPv4Address("192.168.0.1"))
|
|
196
|
+
ipv4_2 = _get_mreg_ipaddress(IPv4Address("192.168.0.2"))
|
|
197
|
+
ipv6_1 = _get_mreg_ipaddress(IPv6Address("2001:db8::1"))
|
|
198
|
+
ipv6_2 = _get_mreg_ipaddress(IPv6Address("2001:db8::2"))
|
|
199
|
+
|
|
200
|
+
network_v4_1 = Network.dummy_network_from_ip(ipv4_1)
|
|
201
|
+
network_v4_2 = Network.dummy_network_from_ip(ipv4_2)
|
|
202
|
+
network_v6_1 = Network.dummy_network_from_ip(ipv6_1)
|
|
203
|
+
network_v6_2 = Network.dummy_network_from_ip(ipv6_2)
|
|
204
|
+
|
|
205
|
+
assert network_v4_1 == network_v4_2
|
|
206
|
+
assert hash(network_v4_1) == hash(network_v4_2)
|
|
207
|
+
|
|
208
|
+
assert network_v6_1 == network_v6_2
|
|
209
|
+
assert hash(network_v6_1) == hash(network_v6_2)
|
|
210
|
+
|
|
211
|
+
assert network_v4_1 != network_v6_1
|
|
212
|
+
assert hash(network_v4_1) != hash(network_v6_1)
|
|
213
|
+
|
|
214
|
+
# Usage in dicts
|
|
215
|
+
d = {network_v4_1: "foo", network_v6_1: "bar"}
|
|
216
|
+
# Use the other two network objects to access the existing entries
|
|
217
|
+
assert d[network_v4_2] == "foo"
|
|
218
|
+
assert d[network_v6_2] == "bar"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|