mreg-cli 1.1.0__tar.gz → 1.2.0__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.1.0 → mreg_cli-1.2.0}/.github/workflows/publish.yml +1 -1
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/.github/workflows/test.yml +11 -1
- mreg_cli-1.2.0/.markdownlint.json +9 -0
- mreg_cli-1.1.0/CHANGELOG → mreg_cli-1.2.0/CHANGELOG.md +21 -5
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/PKG-INFO +1 -9
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/README.md +0 -8
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/ci/testsuite +1 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/ci/testsuite-result.json +122 -1
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/_version.py +2 -2
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/api/endpoints.py +7 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/api/models.py +214 -30
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/cli.py +1 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/help.py +51 -2
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/host_submodules/core.py +16 -9
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/errorbuilder.py +17 -15
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/exceptions.py +2 -2
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/prompt.py +3 -5
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/tokenfile.py +7 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/utilities/api.py +5 -3
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli.egg-info/PKG-INFO +1 -9
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli.egg-info/SOURCES.txt +2 -1
- mreg_cli-1.2.0/tests/test_errorbuilder.py +64 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/tests/test_prompt.py +18 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/tests/test_tokenfile.py +34 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/tests/utilities/test_api.py +28 -4
- mreg_cli-1.1.0/tests/test_errorbuilder.py +0 -56
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/.gitignore +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/AUTHORS +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/CONTRIBUTING +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/LICENSE +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/ci/diff.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/ci/docker-compose.yml +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/ci/run_testsuite_and_record.sh +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/data/mreg-cli.conf +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/__about__.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/__init__.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/__main__.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/api/__init__.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/api/abstracts.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/api/fields.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/api/history.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/__init__.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/base.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/dhcp.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/group.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/host.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/host_submodules/__init__.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/host_submodules/a_aaaa.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/host_submodules/bacnet.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/host_submodules/cname.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/host_submodules/rr.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/label.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/logging.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/network.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/permission.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/policy.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/recording.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/registry.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/root.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/commands/zone.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/config.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/help_formatter.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/main.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/outputmanager.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/py.typed +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/tags.txt +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/types.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/utilities/__init__.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/utilities/shared.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli/utilities/validators.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli.egg-info/dependency_links.txt +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli.egg-info/entry_points.txt +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli.egg-info/requires.txt +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/mreg_cli.egg-info/top_level.txt +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/pyproject.toml +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/run.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/setup.cfg +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/tests/__init__.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/tests/api/__init__.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/tests/api/test_fields.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/tests/api/test_models.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/tests/conftest.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/tests/utilities/__init__.py +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/tox.ini +0 -0
- {mreg_cli-1.1.0 → mreg_cli-1.2.0}/uv.lock +0 -0
|
@@ -48,7 +48,17 @@ jobs:
|
|
|
48
48
|
run: uv sync --group ci
|
|
49
49
|
- name: Test building with tox
|
|
50
50
|
run: uv run tox r
|
|
51
|
-
|
|
51
|
+
|
|
52
|
+
lint:
|
|
53
|
+
name: Lint
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
steps:
|
|
56
|
+
- uses: actions/checkout@v4
|
|
57
|
+
- uses: DavidAnson/markdownlint-cli2-action@v18
|
|
58
|
+
with:
|
|
59
|
+
config: '.markdownlint.json'
|
|
60
|
+
globs: '**/*.md'
|
|
61
|
+
|
|
52
62
|
unit:
|
|
53
63
|
name: Unit tests
|
|
54
64
|
runs-on: ubuntu-latest
|
|
@@ -7,7 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
<!-- ## [Unreleased] -->
|
|
9
9
|
|
|
10
|
-
## [1.1.0
|
|
10
|
+
## [1.2.0](https://github.com/unioslo/mreg-cli/releases/tag/1.2.0) - 2024-11-25
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Support for `help versions` which shows versions of the CLI and the server for servers that have implemented the `/meta/version` (and `/meta/libraries` endpoints for superusers).
|
|
15
|
+
- Support for `help whoami` which shows information about the current user as the server sees them. This includes the username, attributes, groups, and permissions. This command is only available if the server has implemented the `/meta/users` endpoint.
|
|
16
|
+
- Support for `help whois <username>` which allows elevated users to see information about the given user. Also requires the `/meta/users` endpoint to be implemented on the server.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- REPL command completion not showing descriptions for commands.
|
|
21
|
+
- `host info` with MAC address argument not working for hosts with multiple IPs associated with the same MAC address.
|
|
22
|
+
- Failed token file write causing the application to crash.
|
|
23
|
+
- Connection strings without domains and with ports are now parsed properly for prompt interpolation.
|
|
24
|
+
|
|
25
|
+
## [1.1.0](https://github.com/unioslo/mreg-cli/releases/tag/1.1.0) - 2024-11-19
|
|
11
26
|
|
|
12
27
|
### Added
|
|
13
28
|
|
|
@@ -21,18 +36,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
21
36
|
|
|
22
37
|
### Fixed
|
|
23
38
|
|
|
24
|
-
-
|
|
39
|
+
- MAC adress validation. Now supports all common formats. See the [Pydantic docs](https://docs.pydantic.dev/latest/api/pydantic_extra_types_mac_address/) for more information.
|
|
40
|
+
- `host add`: Re-introduced support for network/IP ending with `/`.
|
|
25
41
|
- Automatically deduces the correct network to assign an IP from.
|
|
26
|
-
- `host add`: No longer possible to assign
|
|
42
|
+
- `host add`: No longer possible to assign network address (first address of subnet) or broadcast address (last address of subnet) to a host.
|
|
27
43
|
|
|
28
|
-
## [1.0.1] - 2024-10-21
|
|
44
|
+
## [1.0.1](https://github.com/unioslo/mreg-cli/releases/tag/1.0.1) - 2024-10-21
|
|
29
45
|
|
|
30
46
|
### Fixed
|
|
31
47
|
|
|
32
48
|
- Rendering of top level command autocompletion in REPL.
|
|
33
49
|
- `OK:` messages not being displayed outside of `--record` mode
|
|
34
50
|
|
|
35
|
-
## [1.0.0] - 2024-10-12
|
|
51
|
+
## [1.0.0](https://github.com/unioslo/mreg-cli/releases/tag/1.0.0) - 2024-10-12
|
|
36
52
|
|
|
37
53
|
The big Pydantic update. The entire codebase has been rewritten to use Pydantic for request and response validation. This brings with it a huge improvement to the development experience and the robustness of the code.
|
|
38
54
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mreg-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
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>
|
|
@@ -726,15 +726,7 @@ Requires-Dist: platformdirs>=4.3.0
|
|
|
726
726
|
- [DHCP](#dhcp)
|
|
727
727
|
- [subnet](#subnet)
|
|
728
728
|
- [zone](#zone)
|
|
729
|
-
- [history](#history)
|
|
730
729
|
- [other](#other)
|
|
731
|
-
- [Devhelp](#devhelp)
|
|
732
|
-
- [client.py](#clientpy)
|
|
733
|
-
- [commands.py](#commandspy)
|
|
734
|
-
- [util.py](#utilpy)
|
|
735
|
-
- [history.py](#historypy)
|
|
736
|
-
- [config.py](#configpy)
|
|
737
|
-
- [log.py](#logpy)
|
|
738
730
|
|
|
739
731
|
## Setup
|
|
740
732
|
|
|
@@ -22,15 +22,7 @@
|
|
|
22
22
|
- [DHCP](#dhcp)
|
|
23
23
|
- [subnet](#subnet)
|
|
24
24
|
- [zone](#zone)
|
|
25
|
-
- [history](#history)
|
|
26
25
|
- [other](#other)
|
|
27
|
-
- [Devhelp](#devhelp)
|
|
28
|
-
- [client.py](#clientpy)
|
|
29
|
-
- [commands.py](#commandspy)
|
|
30
|
-
- [util.py](#utilpy)
|
|
31
|
-
- [history.py](#historypy)
|
|
32
|
-
- [config.py](#configpy)
|
|
33
|
-
- [log.py](#logpy)
|
|
34
26
|
|
|
35
27
|
## Setup
|
|
36
28
|
|
|
@@ -203,6 +203,7 @@ dhcp assoc foo aa:bb:cc:dd:ee:ff # should fail, the host has two IPs of differen
|
|
|
203
203
|
host aaaa_remove foo 2001:db9::5
|
|
204
204
|
host aaaa_add foo 2001:db8::5 -f
|
|
205
205
|
dhcp assoc foo aa:bb:cc:dd:ee:ff # should work, the host now has two IPs of different types on the same VLAN.
|
|
206
|
+
host info foo # should work and show both IPs
|
|
206
207
|
host remove foo -f
|
|
207
208
|
network remove 10.0.0.0/24 -f
|
|
208
209
|
network remove 2001:db8::/64 -f
|
|
@@ -20022,6 +20022,127 @@
|
|
|
20022
20022
|
],
|
|
20023
20023
|
"time": null
|
|
20024
20024
|
},
|
|
20025
|
+
{
|
|
20026
|
+
"command": "host info foo",
|
|
20027
|
+
"command_filter": null,
|
|
20028
|
+
"command_filter_negate": false,
|
|
20029
|
+
"command_issued": "host info foo # should work and show both IPs",
|
|
20030
|
+
"ok": [],
|
|
20031
|
+
"warning": [],
|
|
20032
|
+
"error": [],
|
|
20033
|
+
"output": [
|
|
20034
|
+
"Name: foo.example.org",
|
|
20035
|
+
"Contact: ",
|
|
20036
|
+
"A_Records IP MAC",
|
|
20037
|
+
" 10.0.0.5 aa:bb:cc:dd:ee:ff",
|
|
20038
|
+
"AAAA_Records IP MAC",
|
|
20039
|
+
" 2001:db8::5 <not set>",
|
|
20040
|
+
"TTL: (Default)",
|
|
20041
|
+
"TXT: v=spf1 -all",
|
|
20042
|
+
"Created: Thu Nov 21 10:34:55 2024",
|
|
20043
|
+
"Updated: Thu Nov 21 10:34:55 2024"
|
|
20044
|
+
],
|
|
20045
|
+
"api_requests": [
|
|
20046
|
+
{
|
|
20047
|
+
"method": "GET",
|
|
20048
|
+
"url": "/api/v1/hosts/foo.example.org",
|
|
20049
|
+
"data": {},
|
|
20050
|
+
"status": 200,
|
|
20051
|
+
"response": {
|
|
20052
|
+
"ipaddresses": [
|
|
20053
|
+
{
|
|
20054
|
+
"macaddress": "aa:bb:cc:dd:ee:ff",
|
|
20055
|
+
"ipaddress": "10.0.0.5",
|
|
20056
|
+
"host": 13
|
|
20057
|
+
},
|
|
20058
|
+
{
|
|
20059
|
+
"macaddress": "",
|
|
20060
|
+
"ipaddress": "2001:db8::5",
|
|
20061
|
+
"host": 13
|
|
20062
|
+
}
|
|
20063
|
+
],
|
|
20064
|
+
"cnames": [],
|
|
20065
|
+
"mxs": [],
|
|
20066
|
+
"txts": [
|
|
20067
|
+
{
|
|
20068
|
+
"txt": "v=spf1 -all",
|
|
20069
|
+
"host": 13
|
|
20070
|
+
}
|
|
20071
|
+
],
|
|
20072
|
+
"ptr_overrides": [],
|
|
20073
|
+
"hinfo": null,
|
|
20074
|
+
"loc": null,
|
|
20075
|
+
"bacnetid": null,
|
|
20076
|
+
"name": "foo.example.org",
|
|
20077
|
+
"contact": "",
|
|
20078
|
+
"ttl": null,
|
|
20079
|
+
"comment": "",
|
|
20080
|
+
"zone": 1
|
|
20081
|
+
}
|
|
20082
|
+
},
|
|
20083
|
+
{
|
|
20084
|
+
"method": "GET",
|
|
20085
|
+
"url": "/api/v1/srvs/?host=13",
|
|
20086
|
+
"data": {},
|
|
20087
|
+
"status": 200,
|
|
20088
|
+
"response": {
|
|
20089
|
+
"count": 0,
|
|
20090
|
+
"next": null,
|
|
20091
|
+
"previous": null,
|
|
20092
|
+
"results": []
|
|
20093
|
+
}
|
|
20094
|
+
},
|
|
20095
|
+
{
|
|
20096
|
+
"method": "GET",
|
|
20097
|
+
"url": "/api/v1/naptrs/?host=13",
|
|
20098
|
+
"data": {},
|
|
20099
|
+
"status": 200,
|
|
20100
|
+
"response": {
|
|
20101
|
+
"count": 0,
|
|
20102
|
+
"next": null,
|
|
20103
|
+
"previous": null,
|
|
20104
|
+
"results": []
|
|
20105
|
+
}
|
|
20106
|
+
},
|
|
20107
|
+
{
|
|
20108
|
+
"method": "GET",
|
|
20109
|
+
"url": "/api/v1/sshfps/?host=13",
|
|
20110
|
+
"data": {},
|
|
20111
|
+
"status": 200,
|
|
20112
|
+
"response": {
|
|
20113
|
+
"count": 0,
|
|
20114
|
+
"next": null,
|
|
20115
|
+
"previous": null,
|
|
20116
|
+
"results": []
|
|
20117
|
+
}
|
|
20118
|
+
},
|
|
20119
|
+
{
|
|
20120
|
+
"method": "GET",
|
|
20121
|
+
"url": "/api/v1/hostpolicy/roles/?hosts=13",
|
|
20122
|
+
"data": {},
|
|
20123
|
+
"status": 200,
|
|
20124
|
+
"response": {
|
|
20125
|
+
"count": 0,
|
|
20126
|
+
"next": null,
|
|
20127
|
+
"previous": null,
|
|
20128
|
+
"results": []
|
|
20129
|
+
}
|
|
20130
|
+
},
|
|
20131
|
+
{
|
|
20132
|
+
"method": "GET",
|
|
20133
|
+
"url": "/api/v1/hostgroups/?hosts=13",
|
|
20134
|
+
"data": {},
|
|
20135
|
+
"status": 200,
|
|
20136
|
+
"response": {
|
|
20137
|
+
"count": 0,
|
|
20138
|
+
"next": null,
|
|
20139
|
+
"previous": null,
|
|
20140
|
+
"results": []
|
|
20141
|
+
}
|
|
20142
|
+
}
|
|
20143
|
+
],
|
|
20144
|
+
"time": null
|
|
20145
|
+
},
|
|
20025
20146
|
{
|
|
20026
20147
|
"command": "host remove foo -f",
|
|
20027
20148
|
"command_filter": null,
|
|
@@ -24928,7 +25049,7 @@
|
|
|
24928
25049
|
"api_requests": [
|
|
24929
25050
|
{
|
|
24930
25051
|
"method": "GET",
|
|
24931
|
-
"url": "/api/v1/hosts/?ipaddresses__ipaddress=10.0.0.20
|
|
25052
|
+
"url": "/api/v1/hosts/?ipaddresses__ipaddress=10.0.0.20",
|
|
24932
25053
|
"data": {},
|
|
24933
25054
|
"status": 200,
|
|
24934
25055
|
"response": {
|
|
@@ -77,6 +77,13 @@ class Endpoint(str, Enum):
|
|
|
77
77
|
|
|
78
78
|
ForwardZoneForHost = f"{ForwardZones}hostname/"
|
|
79
79
|
|
|
80
|
+
TokenIsValid = "/api/token-is-valid/"
|
|
81
|
+
|
|
82
|
+
MetaUser = "/api/meta/user"
|
|
83
|
+
MetaVersion = "/api/meta/version"
|
|
84
|
+
MetaLibraries = "/api/meta/libraries"
|
|
85
|
+
MetaHeartbeat = "/api/meta/heartbeat"
|
|
86
|
+
|
|
80
87
|
def __str__(self):
|
|
81
88
|
"""Prevent direct usage without parameters where needed."""
|
|
82
89
|
if "{}" in self.value:
|
|
@@ -7,16 +7,9 @@ import logging
|
|
|
7
7
|
import re
|
|
8
8
|
from datetime import date, datetime
|
|
9
9
|
from functools import cached_property
|
|
10
|
-
from typing import Any, Callable, ClassVar, Literal, Self, cast, overload
|
|
11
|
-
|
|
12
|
-
from pydantic import
|
|
13
|
-
AliasChoices,
|
|
14
|
-
BaseModel,
|
|
15
|
-
ConfigDict,
|
|
16
|
-
Field,
|
|
17
|
-
computed_field,
|
|
18
|
-
field_validator,
|
|
19
|
-
)
|
|
10
|
+
from typing import Any, Callable, ClassVar, List, Literal, Self, cast, overload
|
|
11
|
+
|
|
12
|
+
from pydantic import AliasChoices, BaseModel, ConfigDict, Field, computed_field, field_validator
|
|
20
13
|
from typing_extensions import Unpack
|
|
21
14
|
|
|
22
15
|
from mreg_cli.api.abstracts import APIMixin, FrozenModel, FrozenModelWithTimestamps
|
|
@@ -38,7 +31,7 @@ from mreg_cli.exceptions import (
|
|
|
38
31
|
InvalidIPv6Address,
|
|
39
32
|
InvalidNetwork,
|
|
40
33
|
IPNetworkWarning,
|
|
41
|
-
|
|
34
|
+
MultipleEntitiesFound,
|
|
42
35
|
PatchError,
|
|
43
36
|
UnexpectedDataError,
|
|
44
37
|
ValidationError,
|
|
@@ -118,6 +111,7 @@ class NetworkOrIP(BaseModel):
|
|
|
118
111
|
:param value:The value to parse.
|
|
119
112
|
:param mode: The mode to validate the input as.
|
|
120
113
|
:returns: The parsed value as an IP address or network.
|
|
114
|
+
:raises IPNetworkWarning: If the value is not an IP address or network.
|
|
121
115
|
"""
|
|
122
116
|
ipnet = cls.validate(value)
|
|
123
117
|
funcmap: dict[IPNetMode, Callable[..., IP_AddressT | IP_NetworkT]] = {
|
|
@@ -2185,7 +2179,7 @@ class CNAME(FrozenModelWithTimestamps, WithHost, WithZone, WithTTL, APIMixin):
|
|
|
2185
2179
|
raise EntityNotFound(f"CNAME record for {name} not found for {target_hostname}.")
|
|
2186
2180
|
|
|
2187
2181
|
if len(results) > 1:
|
|
2188
|
-
raise
|
|
2182
|
+
raise MultipleEntitiesFound(f"Multiple CNAME records found for {host} with {name}!")
|
|
2189
2183
|
|
|
2190
2184
|
return results[0]
|
|
2191
2185
|
|
|
@@ -2684,28 +2678,22 @@ class Host(FrozenModelWithTimestamps, WithTTL, WithHistory, APIMixin):
|
|
|
2684
2678
|
try:
|
|
2685
2679
|
ptr = False
|
|
2686
2680
|
ipaddress.ip_address(identifier)
|
|
2681
|
+
NetworkOrIP.parse(identifier, mode="ip")
|
|
2687
2682
|
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
if not hosts:
|
|
2693
|
-
hosts = Host.get_list_by_field("ptr_overrides__ipaddress", identifier)
|
|
2683
|
+
host = Host.get_by_field("ipaddresses__ipaddress", identifier)
|
|
2684
|
+
if not host:
|
|
2685
|
+
host = Host.get_by_field("ptr_overrides__ipaddress", identifier)
|
|
2694
2686
|
ptr = True
|
|
2695
2687
|
|
|
2696
|
-
if
|
|
2688
|
+
if host:
|
|
2697
2689
|
if ptr and inform_as_ptr:
|
|
2698
|
-
OutputManager().add_line(
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
f"Multiple hosts found with IP address or PTR {identifier}."
|
|
2706
|
-
)
|
|
2707
|
-
|
|
2708
|
-
except ValueError:
|
|
2690
|
+
OutputManager().add_line(f"{identifier} is a PTR override for {host.name}")
|
|
2691
|
+
return host
|
|
2692
|
+
except MultipleEntitiesFound as e:
|
|
2693
|
+
raise MultipleEntitiesFound(
|
|
2694
|
+
f"Multiple hosts found with IP address or PTR {identifier}."
|
|
2695
|
+
) from e
|
|
2696
|
+
except ValueError: # invalid IP
|
|
2709
2697
|
pass
|
|
2710
2698
|
|
|
2711
2699
|
try:
|
|
@@ -3501,3 +3489,199 @@ class HostGroup(FrozenModelWithTimestamps, WithName, WithHistory, APIMixin):
|
|
|
3501
3489
|
|
|
3502
3490
|
for host in self.hosts:
|
|
3503
3491
|
manager.add_formatted_line_with_source("host", host, self.name)
|
|
3492
|
+
|
|
3493
|
+
|
|
3494
|
+
### Meta models
|
|
3495
|
+
|
|
3496
|
+
|
|
3497
|
+
class UserDjangoStatus(BaseModel):
|
|
3498
|
+
"""Model for Django status in the user response."""
|
|
3499
|
+
|
|
3500
|
+
superuser: bool
|
|
3501
|
+
staff: bool
|
|
3502
|
+
active: bool
|
|
3503
|
+
|
|
3504
|
+
|
|
3505
|
+
class UserMregStatus(BaseModel):
|
|
3506
|
+
"""Model for Mreg status in the user response."""
|
|
3507
|
+
|
|
3508
|
+
superuser: bool
|
|
3509
|
+
admin: bool
|
|
3510
|
+
group_admin: bool
|
|
3511
|
+
network_admin: bool
|
|
3512
|
+
hostpolicy_admin: bool
|
|
3513
|
+
dns_wildcard_admin: bool
|
|
3514
|
+
underscore_admin: bool
|
|
3515
|
+
|
|
3516
|
+
|
|
3517
|
+
class UserPermission(BaseModel):
|
|
3518
|
+
"""Model for permissions in the user response."""
|
|
3519
|
+
|
|
3520
|
+
group: str
|
|
3521
|
+
range: str
|
|
3522
|
+
regex: str
|
|
3523
|
+
labels: List[str]
|
|
3524
|
+
|
|
3525
|
+
|
|
3526
|
+
class ServerVersion(BaseModel):
|
|
3527
|
+
"""Model for server version metadata."""
|
|
3528
|
+
|
|
3529
|
+
version: str
|
|
3530
|
+
|
|
3531
|
+
@classmethod
|
|
3532
|
+
def endpoint(cls) -> str:
|
|
3533
|
+
"""Return the endpoint for the class."""
|
|
3534
|
+
return Endpoint.MetaVersion
|
|
3535
|
+
|
|
3536
|
+
@classmethod
|
|
3537
|
+
def fetch(cls, ignore_errors: bool = True) -> "ServerVersion":
|
|
3538
|
+
"""Fetch the server version from the endpoint.
|
|
3539
|
+
|
|
3540
|
+
:param ignore_errors: Whether to ignore errors.
|
|
3541
|
+
:raises ValidationError: If the response data is invalid and ignore_errors is False.
|
|
3542
|
+
:raises requests.RequestException: If the HTTP request fails and ignore_errors is False.
|
|
3543
|
+
:returns: An instance of ServerVersion with the fetched data.
|
|
3544
|
+
"""
|
|
3545
|
+
try:
|
|
3546
|
+
response = get(cls.endpoint())
|
|
3547
|
+
return cls.model_validate(response.json())
|
|
3548
|
+
except Exception as e:
|
|
3549
|
+
if ignore_errors:
|
|
3550
|
+
return cls(version="Unknown")
|
|
3551
|
+
raise e
|
|
3552
|
+
|
|
3553
|
+
def output(self) -> None:
|
|
3554
|
+
"""Output the server version to the console."""
|
|
3555
|
+
manager = OutputManager()
|
|
3556
|
+
manager.add_line(f"mreg-server: {self.version}")
|
|
3557
|
+
|
|
3558
|
+
|
|
3559
|
+
class Library(BaseModel):
|
|
3560
|
+
"""Model for library metadata."""
|
|
3561
|
+
|
|
3562
|
+
name: str
|
|
3563
|
+
version: str
|
|
3564
|
+
|
|
3565
|
+
|
|
3566
|
+
class ServerLibraries(BaseModel):
|
|
3567
|
+
"""Model for server libraries metadata."""
|
|
3568
|
+
|
|
3569
|
+
libraries: List[Library]
|
|
3570
|
+
|
|
3571
|
+
@classmethod
|
|
3572
|
+
def endpoint(cls) -> str:
|
|
3573
|
+
"""Return the endpoint for the class."""
|
|
3574
|
+
return Endpoint.MetaLibraries
|
|
3575
|
+
|
|
3576
|
+
@classmethod
|
|
3577
|
+
def fetch(cls, ignore_errors: bool = True) -> "ServerLibraries":
|
|
3578
|
+
"""Fetch the server libraries from the endpoint.
|
|
3579
|
+
|
|
3580
|
+
:param ignore_errors: Whether to ignore errors.
|
|
3581
|
+
:raises ValidationError: If the response data is invalid and ignore_errors is False.
|
|
3582
|
+
:raises requests.RequestException: If the HTTP request fails and ignore_errors is False.
|
|
3583
|
+
:returns: An instance of ServerLibraries with the fetched data.
|
|
3584
|
+
"""
|
|
3585
|
+
try:
|
|
3586
|
+
response = get(cls.endpoint())
|
|
3587
|
+
libraries: List[Library] = []
|
|
3588
|
+
|
|
3589
|
+
for name, version in response.json().items():
|
|
3590
|
+
libraries.append(Library(name=name, version=version))
|
|
3591
|
+
return cls(libraries=libraries)
|
|
3592
|
+
except Exception as e:
|
|
3593
|
+
if ignore_errors:
|
|
3594
|
+
return cls(libraries=[])
|
|
3595
|
+
raise e
|
|
3596
|
+
|
|
3597
|
+
def output(self, indent: int = 4) -> None:
|
|
3598
|
+
"""Output the server libraries to the console."""
|
|
3599
|
+
manager = OutputManager()
|
|
3600
|
+
if not self.libraries:
|
|
3601
|
+
return
|
|
3602
|
+
|
|
3603
|
+
manager.add_line("Libraries:")
|
|
3604
|
+
for lib in self.libraries:
|
|
3605
|
+
manager.add_line(f"{' ' * indent}{lib.name}: {lib.version}")
|
|
3606
|
+
|
|
3607
|
+
|
|
3608
|
+
class UserInfo(BaseModel):
|
|
3609
|
+
"""Model for the user information."""
|
|
3610
|
+
|
|
3611
|
+
username: str
|
|
3612
|
+
django_status: UserDjangoStatus
|
|
3613
|
+
mreg_status: UserMregStatus
|
|
3614
|
+
groups: List[str]
|
|
3615
|
+
permissions: List[UserPermission]
|
|
3616
|
+
|
|
3617
|
+
@classmethod
|
|
3618
|
+
def endpoint(cls) -> str:
|
|
3619
|
+
"""Return the endpoint for the class."""
|
|
3620
|
+
return Endpoint.MetaUser
|
|
3621
|
+
|
|
3622
|
+
@classmethod
|
|
3623
|
+
def fetch(cls, ignore_errors: bool = True, user: str | None = None) -> "UserInfo":
|
|
3624
|
+
"""Fetch the user information from the endpoint.
|
|
3625
|
+
|
|
3626
|
+
:param ignore_errors: Whether to ignore errors.
|
|
3627
|
+
:param user: The username to fetch information for. If None, fetch information for the
|
|
3628
|
+
current user.
|
|
3629
|
+
|
|
3630
|
+
:raises ValidationError: If the response data is invalid and ignore_errors is False.
|
|
3631
|
+
:raises requests.RequestException: If the HTTP request fails and ignore_errors is False.
|
|
3632
|
+
:returns: An instance of UserInfo with the fetched data.
|
|
3633
|
+
"""
|
|
3634
|
+
try:
|
|
3635
|
+
endpoint = cls.endpoint()
|
|
3636
|
+
if user:
|
|
3637
|
+
endpoint = f"{endpoint}?username={user}"
|
|
3638
|
+
|
|
3639
|
+
response = get(endpoint)
|
|
3640
|
+
return cls.model_validate(response.json())
|
|
3641
|
+
except Exception as e:
|
|
3642
|
+
if ignore_errors:
|
|
3643
|
+
return cls(
|
|
3644
|
+
username="Unknown",
|
|
3645
|
+
django_status=UserDjangoStatus(superuser=False, staff=False, active=False),
|
|
3646
|
+
mreg_status=UserMregStatus(
|
|
3647
|
+
superuser=False,
|
|
3648
|
+
admin=False,
|
|
3649
|
+
group_admin=False,
|
|
3650
|
+
network_admin=False,
|
|
3651
|
+
hostpolicy_admin=False,
|
|
3652
|
+
dns_wildcard_admin=False,
|
|
3653
|
+
underscore_admin=False,
|
|
3654
|
+
),
|
|
3655
|
+
groups=[],
|
|
3656
|
+
permissions=[],
|
|
3657
|
+
)
|
|
3658
|
+
raise e
|
|
3659
|
+
|
|
3660
|
+
def output(self) -> None:
|
|
3661
|
+
"""Output the user information to the console."""
|
|
3662
|
+
outputmanager = OutputManager()
|
|
3663
|
+
outputmanager.add_line(f"Username: {self.username}")
|
|
3664
|
+
outputmanager.add_line("Django status:")
|
|
3665
|
+
outputmanager.add_line(f" Superuser: {self.django_status.superuser}")
|
|
3666
|
+
outputmanager.add_line(f" Staff: {self.django_status.staff}")
|
|
3667
|
+
outputmanager.add_line(f" Active: {self.django_status.active}")
|
|
3668
|
+
outputmanager.add_line("Mreg status:")
|
|
3669
|
+
outputmanager.add_line(f" Superuser: {self.mreg_status.superuser}")
|
|
3670
|
+
outputmanager.add_line(f" Admin: {self.mreg_status.admin}")
|
|
3671
|
+
outputmanager.add_line(f" Group admin: {self.mreg_status.group_admin}")
|
|
3672
|
+
outputmanager.add_line(f" Network admin: {self.mreg_status.network_admin}")
|
|
3673
|
+
outputmanager.add_line(f" Hostpolicy admin: {self.mreg_status.hostpolicy_admin}")
|
|
3674
|
+
outputmanager.add_line(f" DNS wildcard admin: {self.mreg_status.dns_wildcard_admin}")
|
|
3675
|
+
outputmanager.add_line(f" Underscore admin: {self.mreg_status.underscore_admin}")
|
|
3676
|
+
outputmanager.add_line("Groups:")
|
|
3677
|
+
for group in self.groups:
|
|
3678
|
+
outputmanager.add_line(f" {group}")
|
|
3679
|
+
outputmanager.add_line("Permissions:")
|
|
3680
|
+
if not self.permissions:
|
|
3681
|
+
outputmanager.add_line(" None")
|
|
3682
|
+
|
|
3683
|
+
for perm in self.permissions:
|
|
3684
|
+
outputmanager.add_line(f" Group: {perm.group}")
|
|
3685
|
+
outputmanager.add_line(f" Range: {perm.range}")
|
|
3686
|
+
outputmanager.add_line(f" Regex: {perm.regex}")
|
|
3687
|
+
outputmanager.add_line(f" Labels: {', '.join(perm.labels)}")
|
|
@@ -5,9 +5,13 @@ from __future__ import annotations
|
|
|
5
5
|
import argparse
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from mreg_cli.__about__ import __version__ as mreg_cli_version
|
|
9
|
+
from mreg_cli.api.models import ServerLibraries, ServerVersion, UserInfo
|
|
8
10
|
from mreg_cli.commands.base import BaseCommand
|
|
9
11
|
from mreg_cli.commands.registry import CommandRegistry
|
|
10
12
|
from mreg_cli.config import MregCliConfig
|
|
13
|
+
from mreg_cli.outputmanager import OutputManager
|
|
14
|
+
from mreg_cli.types import Flag
|
|
11
15
|
|
|
12
16
|
command_registry = CommandRegistry()
|
|
13
17
|
|
|
@@ -27,7 +31,9 @@ class HelpCommands(BaseCommand):
|
|
|
27
31
|
)
|
|
28
32
|
|
|
29
33
|
|
|
30
|
-
@command_registry.register_command(
|
|
34
|
+
@command_registry.register_command(
|
|
35
|
+
prog="filtering", description="Show help for filtering.", short_desc="Filtering Help"
|
|
36
|
+
)
|
|
31
37
|
def filtering_help(_: argparse.Namespace) -> None:
|
|
32
38
|
"""Show help for filtering."""
|
|
33
39
|
print(
|
|
@@ -71,7 +77,50 @@ TXT: v=spf1 -all
|
|
|
71
77
|
)
|
|
72
78
|
|
|
73
79
|
|
|
74
|
-
@command_registry.register_command(
|
|
80
|
+
@command_registry.register_command(
|
|
81
|
+
prog="configuration", description="Show configuration", short_desc="Show configuration"
|
|
82
|
+
)
|
|
75
83
|
def configuration_help(_: argparse.Namespace) -> None:
|
|
76
84
|
"""Show configuration."""
|
|
77
85
|
MregCliConfig().print_config_table()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@command_registry.register_command(
|
|
89
|
+
prog="versions",
|
|
90
|
+
description="Show versions of client and server as much as possible",
|
|
91
|
+
short_desc="Show versions",
|
|
92
|
+
)
|
|
93
|
+
def versions_help(_: argparse.Namespace) -> None:
|
|
94
|
+
"""Show versions of client and server as much as possible."""
|
|
95
|
+
output_manager = OutputManager()
|
|
96
|
+
output_manager.add_line(f"mreg-cli: {mreg_cli_version}")
|
|
97
|
+
|
|
98
|
+
ServerVersion.fetch().output()
|
|
99
|
+
ServerLibraries.fetch().output()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@command_registry.register_command(
|
|
103
|
+
prog="whoami",
|
|
104
|
+
description="Show information about the the current user",
|
|
105
|
+
short_desc="Show self info",
|
|
106
|
+
)
|
|
107
|
+
def whoami_help(_: argparse.Namespace) -> None:
|
|
108
|
+
"""Show information about the current user."""
|
|
109
|
+
try:
|
|
110
|
+
UserInfo.fetch(ignore_errors=False).output()
|
|
111
|
+
except Exception as e:
|
|
112
|
+
print("Failed to fetch user info:", e)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@command_registry.register_command(
|
|
116
|
+
prog="whois",
|
|
117
|
+
description="Show information about a user",
|
|
118
|
+
short_desc="Show user info",
|
|
119
|
+
flags=[Flag("user", description="The user to show information about")],
|
|
120
|
+
)
|
|
121
|
+
def whois_help(args: argparse.Namespace) -> None:
|
|
122
|
+
"""Show information about a user."""
|
|
123
|
+
try:
|
|
124
|
+
UserInfo.fetch(ignore_errors=False, user=args.user).output()
|
|
125
|
+
except Exception as e:
|
|
126
|
+
print("Failed to fetch user info:", e)
|