mreg-cli 1.2.0__tar.gz → 1.2.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.2.0 → mreg_cli-1.2.2}/.github/workflows/test.yml +4 -8
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/CHANGELOG.md +18 -1
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/CONTRIBUTING +4 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/PKG-INFO +1 -1
- mreg_cli-1.2.2/ci/Dockerfile +13 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/ci/diff.py +2 -2
- mreg_cli-1.2.2/ci/run_testsuite_and_record_V2.sh +88 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/ci/testsuite-result.json +7 -66
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/_version.py +2 -2
- mreg_cli-1.2.2/mreg_cli/api/fields.py +172 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/api/models.py +300 -220
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/cli.py +13 -4
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/dhcp.py +11 -17
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/group.py +3 -3
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/help.py +15 -6
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/host_submodules/a_aaaa.py +6 -5
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/host_submodules/bacnet.py +3 -3
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/host_submodules/cname.py +27 -20
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/host_submodules/core.py +29 -26
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/host_submodules/rr.py +36 -31
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/network.py +3 -4
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/permission.py +2 -2
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/policy.py +4 -4
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/config.py +5 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/errorbuilder.py +23 -23
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/exceptions.py +65 -14
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/main.py +2 -2
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/outputmanager.py +4 -5
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/types.py +16 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/utilities/api.py +10 -2
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli.egg-info/PKG-INFO +1 -1
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli.egg-info/SOURCES.txt +3 -0
- mreg_cli-1.2.2/tests/api/test_fields.py +269 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/tests/api/test_models.py +26 -2
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/tests/conftest.py +10 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/tests/test_errorbuilder.py +13 -2
- mreg_cli-1.2.2/tests/test_exceptions.py +175 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/uv.lock +1 -1
- mreg_cli-1.2.0/mreg_cli/api/fields.py +0 -122
- mreg_cli-1.2.0/tests/api/test_fields.py +0 -144
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/.github/workflows/publish.yml +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/.gitignore +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/.markdownlint.json +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/AUTHORS +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/LICENSE +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/README.md +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/ci/docker-compose.yml +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/ci/run_testsuite_and_record.sh +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/ci/testsuite +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/data/mreg-cli.conf +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/__about__.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/__init__.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/__main__.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/api/__init__.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/api/abstracts.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/api/endpoints.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/api/history.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/__init__.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/base.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/host.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/host_submodules/__init__.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/label.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/logging.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/recording.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/registry.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/root.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/commands/zone.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/help_formatter.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/prompt.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/py.typed +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/tags.txt +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/tokenfile.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/utilities/__init__.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/utilities/shared.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli/utilities/validators.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli.egg-info/dependency_links.txt +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli.egg-info/entry_points.txt +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli.egg-info/requires.txt +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/mreg_cli.egg-info/top_level.txt +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/pyproject.toml +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/run.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/setup.cfg +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/tests/__init__.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/tests/api/__init__.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/tests/test_prompt.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/tests/test_tokenfile.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/tests/utilities/__init__.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/tests/utilities/test_api.py +0 -0
- {mreg_cli-1.2.0 → mreg_cli-1.2.2}/tox.ini +0 -0
|
@@ -14,20 +14,15 @@ jobs:
|
|
|
14
14
|
python-version:
|
|
15
15
|
- '3.11'
|
|
16
16
|
- '3.12'
|
|
17
|
+
- '3.13'
|
|
17
18
|
env:
|
|
18
19
|
# Disable colors and formatting in Rich console output
|
|
19
20
|
TERM: dumb
|
|
20
21
|
steps:
|
|
21
22
|
- name: Checkout
|
|
22
23
|
uses: actions/checkout@v4
|
|
23
|
-
- name: Install uv
|
|
24
|
-
uses: astral-sh/setup-uv@v2
|
|
25
|
-
- name: Set up Python ${{ matrix.python-version }}
|
|
26
|
-
run: uv python install ${{ matrix.python-version }}
|
|
27
|
-
- name: Install mreg-cli
|
|
28
|
-
run: uv sync
|
|
29
24
|
- name: Test and compare api calls
|
|
30
|
-
run:
|
|
25
|
+
run: ci/run_testsuite_and_record_V2.sh ${{ matrix.python-version }}
|
|
31
26
|
|
|
32
27
|
tox:
|
|
33
28
|
name: tox
|
|
@@ -68,6 +63,7 @@ jobs:
|
|
|
68
63
|
python-version:
|
|
69
64
|
- "3.11"
|
|
70
65
|
- "3.12"
|
|
66
|
+
- '3.13'
|
|
71
67
|
steps:
|
|
72
68
|
- uses: actions/checkout@v4
|
|
73
69
|
- name: Install uv
|
|
@@ -77,4 +73,4 @@ jobs:
|
|
|
77
73
|
- name: Install dependencies
|
|
78
74
|
run: uv sync
|
|
79
75
|
- name: Run unittest
|
|
80
|
-
run: uv run pytest
|
|
76
|
+
run: uv run pytest
|
|
@@ -5,7 +5,24 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
<!-- ##
|
|
8
|
+
<!-- ## Unreleased -->
|
|
9
|
+
|
|
10
|
+
## [1.2.2](https://github.com/unioslo/mreg-cli/releases/tag/1.2.2) - 2024-12-09
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- `dhcp assoc` and `host add` commands displaying unique constraint error instead of a proper error message when a MAC is already associated with an IP.
|
|
15
|
+
- Validation errors causing the application to crash instead of displaying a proper error message.
|
|
16
|
+
|
|
17
|
+
## [1.2.1](https://github.com/unioslo/mreg-cli/releases/tag/1.2.1) - 2024-12-02
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Host lookup with IPv6 addresses raising `ValidationError` when no host can be found with the given address.
|
|
22
|
+
|
|
23
|
+
### Removed
|
|
24
|
+
|
|
25
|
+
- Support for fetching hosts that have a valid IP/MAC address as their hostname.
|
|
9
26
|
|
|
10
27
|
## [1.2.0](https://github.com/unioslo/mreg-cli/releases/tag/1.2.0) - 2024-11-25
|
|
11
28
|
|
|
@@ -9,6 +9,10 @@ If you would like to contribute to this project, please follow these steps:
|
|
|
9
9
|
5. Push to the branch (`git push origin feature/branch-name`)
|
|
10
10
|
6. Create a Pull Request
|
|
11
11
|
|
|
12
|
+
## Testing
|
|
13
|
+
|
|
14
|
+
To run tests locally, just run the script `ci/run_testsuite_and_record_V2.sh` from anywhere. The only prerequisite is a working installation of docker or podman.
|
|
15
|
+
|
|
12
16
|
## Publishing
|
|
13
17
|
|
|
14
18
|
Publishing new versions is automatically handled by GitHub actions for versions tagged with `x.y.z[.prerelease]` (`1.2.3`, `1.2.3.rc1`, etc.). If you are a maintainer, you can create a new release by following these steps:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mreg-cli
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.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>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# This container image is only used temporarily for testing during a CI process.
|
|
2
|
+
FROM ubuntu:latest
|
|
3
|
+
ARG python_version=3.12
|
|
4
|
+
|
|
5
|
+
RUN apt-get -qq update
|
|
6
|
+
RUN apt-get -y install curl git >/dev/null
|
|
7
|
+
RUN curl -LsSf https://astral.sh/uv/0.5.6/install.sh | sh
|
|
8
|
+
RUN /root/.local/bin/uv self update -q
|
|
9
|
+
RUN /root/.local/bin/uv python install ${python_version}
|
|
10
|
+
COPY . /build
|
|
11
|
+
RUN cd /build; /root/.local/bin/uv sync -q
|
|
12
|
+
|
|
13
|
+
CMD cd /build/ci; /root/.local/bin/uv run bash -c 'echo "test" | mreg-cli -u test -d example.org --url http://127.0.0.1:8000 --source testsuite --record new_testsuite_log.json --record-without-timestamps -v ERROR >/dev/null'
|
|
@@ -290,8 +290,8 @@ class CommandDiffer:
|
|
|
290
290
|
new_testsuite_results.append(expected.original)
|
|
291
291
|
self.diff_unresolved += 1
|
|
292
292
|
else:
|
|
293
|
-
# No diff, keep
|
|
294
|
-
new_testsuite_results.append(
|
|
293
|
+
# No diff, keep old line
|
|
294
|
+
new_testsuite_results.append(expected.original)
|
|
295
295
|
|
|
296
296
|
# Only write back changes if we are in review mode and there are changes
|
|
297
297
|
if self.review and self.diff_resolved > 0:
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# exit immediately on error
|
|
4
|
+
set -e
|
|
5
|
+
|
|
6
|
+
# check if we have docker or podman
|
|
7
|
+
if command -v docker >/dev/null 2>&1 && ! docker 2>&1 | grep -q podman; then
|
|
8
|
+
echo "Using Docker"
|
|
9
|
+
GH_PARAMS="--ansi=never"
|
|
10
|
+
elif command -v podman >/dev/null 2>&1; then
|
|
11
|
+
echo "Using Podman"
|
|
12
|
+
GH_PARAMS="--no-ansi"
|
|
13
|
+
PODMAN=1
|
|
14
|
+
alias docker=podman
|
|
15
|
+
else
|
|
16
|
+
echo "Found neither docker nor podman."
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# clean up on exit, even if something fails
|
|
21
|
+
function cleanup {
|
|
22
|
+
set +e
|
|
23
|
+
if [[ -n "$GITHUB_ACTIONS" ]]; then
|
|
24
|
+
docker compose $GH_PARAMS down
|
|
25
|
+
else
|
|
26
|
+
docker compose down
|
|
27
|
+
fi
|
|
28
|
+
docker ps -a | grep "mreg-" | awk '{print $1}' | xargs -r docker stop
|
|
29
|
+
docker ps -a | grep "mreg-" | awk '{print $1}' | xargs -r docker rm
|
|
30
|
+
docker images | grep "mreg-" | awk '{print $1}' | xargs -r docker rmi
|
|
31
|
+
rm -f new_testsuite_log.json
|
|
32
|
+
echo "cleanup done."
|
|
33
|
+
}
|
|
34
|
+
trap cleanup EXIT
|
|
35
|
+
|
|
36
|
+
# chdir to where this script is
|
|
37
|
+
cd `dirname $0`
|
|
38
|
+
|
|
39
|
+
# get Python version from argument
|
|
40
|
+
PYTHON_VERSION=3.12
|
|
41
|
+
if [ ! -z "$1" ]; then
|
|
42
|
+
PYTHON_VERSION=$1
|
|
43
|
+
fi
|
|
44
|
+
echo "Python version $PYTHON_VERSION"
|
|
45
|
+
|
|
46
|
+
# build a container image for mreg-cli
|
|
47
|
+
docker build -f Dockerfile -t mreg-cli --build-arg python_version=$PYTHON_VERSION ..
|
|
48
|
+
|
|
49
|
+
# start mreg+postgres in containers
|
|
50
|
+
if [[ -n "$GITHUB_ACTIONS" ]]; then
|
|
51
|
+
if [[ "$PODMAN" == "1" ]]; then
|
|
52
|
+
docker compose $GH_PARAMS pull >/dev/null 2>&1
|
|
53
|
+
else
|
|
54
|
+
docker compose $GH_PARAMS pull --quiet
|
|
55
|
+
fi
|
|
56
|
+
docker compose $GH_PARAMS up -d
|
|
57
|
+
else
|
|
58
|
+
docker compose up -d
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# give mreg some time to create the database schema and start up
|
|
62
|
+
sleep 5s
|
|
63
|
+
|
|
64
|
+
# create a superuser
|
|
65
|
+
if docker exec -t mreg uv version >/dev/null; then
|
|
66
|
+
docker exec -t mreg uv run /app/manage.py create_mreg_superuser --username test --password test
|
|
67
|
+
else
|
|
68
|
+
docker exec -t mreg /app/manage.py create_mreg_superuser --username test --password test
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
# test connectivity
|
|
72
|
+
#docker run --rm --tty --network host --entrypoint curl mreg-cli --head http://127.0.0.1:8000/admin/login/
|
|
73
|
+
|
|
74
|
+
# start the mreg-cli container, which will automatically run the test suite
|
|
75
|
+
echo "Running the tests..."
|
|
76
|
+
docker run --name mreg-cli --network host --tty mreg-cli
|
|
77
|
+
docker commit mreg-cli finished-mreg-tests # because inside is the file new_testsuite_log.json which we want to look at
|
|
78
|
+
|
|
79
|
+
# show a detailed diff (and review if running locally)
|
|
80
|
+
if [[ -n "$GITHUB_ACTIONS" ]]; then
|
|
81
|
+
docker run --rm --tty --entrypoint bash finished-mreg-tests -c 'cd /build/ci; /root/.local/bin/uv run diff.py testsuite-result.json new_testsuite_log.json'
|
|
82
|
+
exit $?
|
|
83
|
+
else
|
|
84
|
+
docker run -it --name finished-mreg-tests --entrypoint bash finished-mreg-tests -c 'cd /build/ci; /root/.local/bin/uv run diff.py testsuite-result.json new_testsuite_log.json --review'
|
|
85
|
+
EXITCODE=$?
|
|
86
|
+
docker cp finished-mreg-tests:/build/ci/testsuite-result.json .
|
|
87
|
+
exit $EXITCODE
|
|
88
|
+
fi
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
"command_issued": "zone create example.org hostmaster@example.org ns1.example.org # should require force because ns1 is unknown",
|
|
7
7
|
"ok": [],
|
|
8
8
|
"warning": [
|
|
9
|
-
"Host ns1.example.org not found.",
|
|
10
9
|
"ns1.example.org is not in mreg, must force"
|
|
11
10
|
],
|
|
12
11
|
"error": [],
|
|
@@ -41,9 +40,7 @@
|
|
|
41
40
|
"ok": [
|
|
42
41
|
"Created zone example.org"
|
|
43
42
|
],
|
|
44
|
-
"warning": [
|
|
45
|
-
"Host ns1.example.org not found."
|
|
46
|
-
],
|
|
43
|
+
"warning": [],
|
|
47
44
|
"error": [],
|
|
48
45
|
"output": [],
|
|
49
46
|
"api_requests": [
|
|
@@ -210,10 +207,9 @@
|
|
|
210
207
|
"command": "zone set_ns example.org ns2.example.org",
|
|
211
208
|
"command_filter": null,
|
|
212
209
|
"command_filter_negate": false,
|
|
213
|
-
"command_issued": "zone set_ns example.org ns2.example.org
|
|
210
|
+
"command_issued": "zone set_ns example.org ns2.example.org # requires force because ns2 is unknown",
|
|
214
211
|
"ok": [],
|
|
215
212
|
"warning": [
|
|
216
|
-
"Host ns2.example.org not found.",
|
|
217
213
|
"ns2.example.org is not in mreg, must force"
|
|
218
214
|
],
|
|
219
215
|
"error": [],
|
|
@@ -271,9 +267,7 @@
|
|
|
271
267
|
"ok": [
|
|
272
268
|
"Updated nameservers for example.org"
|
|
273
269
|
],
|
|
274
|
-
"warning": [
|
|
275
|
-
"Host ns2.example.org not found."
|
|
276
|
-
],
|
|
270
|
+
"warning": [],
|
|
277
271
|
"error": [],
|
|
278
272
|
"output": [],
|
|
279
273
|
"api_requests": [
|
|
@@ -15283,9 +15277,7 @@
|
|
|
15283
15277
|
"ok": [
|
|
15284
15278
|
"Created zone delegation wut.example.org"
|
|
15285
15279
|
],
|
|
15286
|
-
"warning": [
|
|
15287
|
-
"Host ns2.example.org not found."
|
|
15288
|
-
],
|
|
15280
|
+
"warning": [],
|
|
15289
15281
|
"error": [],
|
|
15290
15282
|
"output": [],
|
|
15291
15283
|
"api_requests": [
|
|
@@ -17449,7 +17441,7 @@
|
|
|
17449
17441
|
"command_filter_negate": false,
|
|
17450
17442
|
"command_issued": "policy host_remove fruit foo",
|
|
17451
17443
|
"ok": [
|
|
17452
|
-
"Removed host foo.example.org from role 'fruit'"
|
|
17444
|
+
"Removed host 'foo.example.org' from role 'fruit'"
|
|
17453
17445
|
],
|
|
17454
17446
|
"warning": [],
|
|
17455
17447
|
"error": [],
|
|
@@ -23738,57 +23730,6 @@
|
|
|
23738
23730
|
"comment": "This is the comment",
|
|
23739
23731
|
"zone": 1
|
|
23740
23732
|
}
|
|
23741
|
-
},
|
|
23742
|
-
{
|
|
23743
|
-
"method": "GET",
|
|
23744
|
-
"url": "/api/v1/hosts/?id=14",
|
|
23745
|
-
"data": {},
|
|
23746
|
-
"status": 200,
|
|
23747
|
-
"response": {
|
|
23748
|
-
"count": 1,
|
|
23749
|
-
"next": null,
|
|
23750
|
-
"previous": null,
|
|
23751
|
-
"results": [
|
|
23752
|
-
{
|
|
23753
|
-
"ipaddresses": [
|
|
23754
|
-
{
|
|
23755
|
-
"macaddress": "",
|
|
23756
|
-
"ipaddress": "10.0.0.14",
|
|
23757
|
-
"host": 14
|
|
23758
|
-
},
|
|
23759
|
-
{
|
|
23760
|
-
"macaddress": "11:22:33:44:55:66",
|
|
23761
|
-
"ipaddress": "10.0.0.15",
|
|
23762
|
-
"host": 14
|
|
23763
|
-
}
|
|
23764
|
-
],
|
|
23765
|
-
"cnames": [
|
|
23766
|
-
{
|
|
23767
|
-
"name": "fubar.example.org",
|
|
23768
|
-
"ttl": null,
|
|
23769
|
-
"zone": 1,
|
|
23770
|
-
"host": 14
|
|
23771
|
-
}
|
|
23772
|
-
],
|
|
23773
|
-
"mxs": [],
|
|
23774
|
-
"txts": [
|
|
23775
|
-
{
|
|
23776
|
-
"txt": "v=spf1 -all",
|
|
23777
|
-
"host": 14
|
|
23778
|
-
}
|
|
23779
|
-
],
|
|
23780
|
-
"ptr_overrides": [],
|
|
23781
|
-
"hinfo": null,
|
|
23782
|
-
"loc": null,
|
|
23783
|
-
"bacnetid": null,
|
|
23784
|
-
"name": "bar.example.org",
|
|
23785
|
-
"contact": "me@example.org",
|
|
23786
|
-
"ttl": null,
|
|
23787
|
-
"comment": "This is the comment",
|
|
23788
|
-
"zone": 1
|
|
23789
|
-
}
|
|
23790
|
-
]
|
|
23791
|
-
}
|
|
23792
23733
|
}
|
|
23793
23734
|
],
|
|
23794
23735
|
"time": null
|
|
@@ -27544,7 +27485,7 @@
|
|
|
27544
27485
|
"command_issued": "host add -ip 10.0.0.0/24 -macaddress aa:bb:cc:cc:bb:aa directfail",
|
|
27545
27486
|
"ok": [],
|
|
27546
27487
|
"warning": [
|
|
27547
|
-
"MAC address aa:bb:cc:cc:bb:aa is already associated with 10.0.0.
|
|
27488
|
+
"MAC address aa:bb:cc:cc:bb:aa is already associated with IP address 10.0.0.184, must force."
|
|
27548
27489
|
],
|
|
27549
27490
|
"error": [],
|
|
27550
27491
|
"output": [],
|
|
@@ -27561,7 +27502,7 @@
|
|
|
27561
27502
|
"results": [
|
|
27562
27503
|
{
|
|
27563
27504
|
"macaddress": "aa:bb:cc:cc:bb:aa",
|
|
27564
|
-
"ipaddress": "10.0.0.
|
|
27505
|
+
"ipaddress": "10.0.0.184",
|
|
27565
27506
|
"host": 18
|
|
27566
27507
|
}
|
|
27567
27508
|
]
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Custom field types for Pydantic models.
|
|
2
|
+
|
|
3
|
+
The types validate to basic types like str, int, etc., but with additional
|
|
4
|
+
validation added to them. The types are used in Pydantic models for consistent
|
|
5
|
+
validation of common fields such as hostnames, MAC addresses, etc.
|
|
6
|
+
|
|
7
|
+
Warning:
|
|
8
|
+
Values constructed from these types should NOT be checked at runtime with isinstance()!
|
|
9
|
+
Pydantic will always coerce these types to their schema types (str, int, etc.).
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
import re
|
|
17
|
+
from typing import Annotated, Any
|
|
18
|
+
|
|
19
|
+
from pydantic import AfterValidator, BeforeValidator, GetCoreSchemaHandler
|
|
20
|
+
from pydantic_core import core_schema
|
|
21
|
+
from pydantic_extra_types.mac_address import MacAddress as PydanticMacAddress
|
|
22
|
+
|
|
23
|
+
from mreg_cli.config import MregCliConfig
|
|
24
|
+
from mreg_cli.exceptions import InputFailure
|
|
25
|
+
from mreg_cli.types import get_type_adapter
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class HostName(str):
|
|
31
|
+
"""Hostname string type."""
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def parse(cls, obj: Any) -> HostName | None:
|
|
35
|
+
"""Parse a hostname from a string. Returns None if the hostname is invalid.
|
|
36
|
+
|
|
37
|
+
:param obj: The object to parse.
|
|
38
|
+
:returns: The hostname as a string or None if it is invalid.
|
|
39
|
+
"""
|
|
40
|
+
try:
|
|
41
|
+
return cls.parse_or_raise(obj)
|
|
42
|
+
except InputFailure:
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def parse_or_raise(cls, obj: Any) -> HostName:
|
|
47
|
+
"""Parse a hostname from a string. Returns the hostname as a string.
|
|
48
|
+
|
|
49
|
+
:param obj: The object to parse.
|
|
50
|
+
:returns: The hostname as a string.
|
|
51
|
+
:raises ValueError: If the object is not a valid hostname.
|
|
52
|
+
"""
|
|
53
|
+
try:
|
|
54
|
+
adapter = get_type_adapter(cls)
|
|
55
|
+
return cls(adapter.validate_python(obj))
|
|
56
|
+
except ValueError as e:
|
|
57
|
+
raise InputFailure(f"Invalid hostname '{obj}'") from e
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def validate_hostname(value: str) -> str:
|
|
61
|
+
"""Validate the hostname."""
|
|
62
|
+
value = value.lower()
|
|
63
|
+
|
|
64
|
+
if re.search(r"^(\*\.)?([a-z0-9_][a-z0-9\-]*\.?)+$", value) is None:
|
|
65
|
+
raise InputFailure(f"Invalid input for hostname: {value}")
|
|
66
|
+
|
|
67
|
+
# Assume user is happy with domain, but strip the dot.
|
|
68
|
+
if value.endswith("."):
|
|
69
|
+
return value[:-1]
|
|
70
|
+
|
|
71
|
+
# If a dot in name, assume long name.
|
|
72
|
+
if "." in value:
|
|
73
|
+
return value
|
|
74
|
+
|
|
75
|
+
config = MregCliConfig()
|
|
76
|
+
default_domain = config.get("domain")
|
|
77
|
+
# Append domain name if in config and it does not end with it
|
|
78
|
+
if default_domain and not value.endswith(default_domain):
|
|
79
|
+
return f"{value}.{default_domain}"
|
|
80
|
+
return value
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def __get_pydantic_core_schema__(
|
|
84
|
+
cls, source: type[Any], handler: GetCoreSchemaHandler
|
|
85
|
+
) -> core_schema.CoreSchema:
|
|
86
|
+
"""Return a Pydantic CoreSchema with the hostname validation.
|
|
87
|
+
|
|
88
|
+
:param source: The source type to be converted.
|
|
89
|
+
:param handler: The handler to get the CoreSchema.
|
|
90
|
+
:returns: A Pydantic CoreSchema with the hostname validation.
|
|
91
|
+
|
|
92
|
+
"""
|
|
93
|
+
return core_schema.with_info_before_validator_function(
|
|
94
|
+
cls._validate,
|
|
95
|
+
core_schema.str_schema(),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def _validate(cls, __input_value: str, _: Any) -> str:
|
|
100
|
+
"""Validate a hostname from the provided str value.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
__input_value: The str value to be validated.
|
|
104
|
+
_: The source type to be converted.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
str: The parsed hostname.
|
|
108
|
+
|
|
109
|
+
"""
|
|
110
|
+
return cls.validate_hostname(__input_value)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class MacAddress(PydanticMacAddress):
|
|
114
|
+
"""MAC address string type used in Pydantic models."""
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def parse(cls, obj: Any) -> MacAddress | None:
|
|
118
|
+
"""Parse a MAC address from a string. Returns None if the MAC address is invalid.
|
|
119
|
+
|
|
120
|
+
:param obj: The object to parse.
|
|
121
|
+
:returns: The MAC address as a string or None if it is invalid.
|
|
122
|
+
"""
|
|
123
|
+
try:
|
|
124
|
+
return cls.parse_or_raise(obj)
|
|
125
|
+
except InputFailure:
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
def parse_or_raise(cls, obj: Any) -> MacAddress:
|
|
130
|
+
"""Parse a MAC address from a string. Returns the MAC address as a string.
|
|
131
|
+
|
|
132
|
+
:param obj: The object to parse.
|
|
133
|
+
:returns: The MAC address as a string.
|
|
134
|
+
:raises ValueError: If the object is not a valid MAC address.
|
|
135
|
+
"""
|
|
136
|
+
try:
|
|
137
|
+
adapter = get_type_adapter(cls)
|
|
138
|
+
return cls(adapter.validate_python(obj))
|
|
139
|
+
except ValueError as e:
|
|
140
|
+
raise InputFailure(f"Invalid MAC address '{obj}'") from e
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _extract_name(value: Any) -> str:
|
|
144
|
+
"""Extract the "name" value from a dictionary.
|
|
145
|
+
|
|
146
|
+
:param v: Dictionary containing the name.
|
|
147
|
+
:returns: Extracted name as a string.
|
|
148
|
+
"""
|
|
149
|
+
if isinstance(value, dict):
|
|
150
|
+
try:
|
|
151
|
+
return str(value["name"]) # pyright: ignore[reportUnknownArgumentType]
|
|
152
|
+
except KeyError:
|
|
153
|
+
logger.error("No 'name' key in %s", value) # pyright: ignore[reportUnknownArgumentType]
|
|
154
|
+
return ""
|
|
155
|
+
return value
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _remove_falsy_list_items(value: Any) -> Any:
|
|
159
|
+
"""Remove falsy items from a list.
|
|
160
|
+
|
|
161
|
+
For use in validators only.
|
|
162
|
+
"""
|
|
163
|
+
if isinstance(value, list):
|
|
164
|
+
return [i for i in value if i] # pyright: ignore[reportUnknownVariableType]
|
|
165
|
+
return value
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
NameList = Annotated[
|
|
169
|
+
list[Annotated[str, BeforeValidator(_extract_name)]],
|
|
170
|
+
AfterValidator(_remove_falsy_list_items),
|
|
171
|
+
]
|
|
172
|
+
"""List of names extracted from a list of dicts."""
|