rndc-python 0.1.1__tar.gz → 0.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.
- {rndc_python-0.1.1 → rndc_python-0.2.0}/PKG-INFO +13 -13
- {rndc_python-0.1.1 → rndc_python-0.2.0}/README.md +11 -11
- {rndc_python-0.1.1 → rndc_python-0.2.0}/pyproject.toml +7 -3
- {rndc_python-0.1.1 → rndc_python-0.2.0}/src/rndc_python/__init__.py +7 -1
- {rndc_python-0.1.1 → rndc_python-0.2.0}/src/rndc_python/cli.py +14 -12
- {rndc_python-0.1.1 → rndc_python-0.2.0}/src/rndc_python/config.py +8 -0
- rndc_python-0.2.0/src/rndc_python/models.py +204 -0
- {rndc_python-0.1.1 → rndc_python-0.2.0}/src/rndc_python/rndc_client.py +6 -5
- {rndc_python-0.1.1 → rndc_python-0.2.0}/src/rndc_python/rndc_config.py +14 -9
- {rndc_python-0.1.1 → rndc_python-0.2.0}/.gitignore +0 -0
- {rndc_python-0.1.1 → rndc_python-0.2.0}/LICENSE +0 -0
- {rndc_python-0.1.1 → rndc_python-0.2.0}/src/rndc_python/enums.py +0 -0
- {rndc_python-0.1.1 → rndc_python-0.2.0}/src/rndc_python/exceptions.py +0 -0
- {rndc_python-0.1.1 → rndc_python-0.2.0}/src/rndc_python/py.typed +0 -0
- {rndc_python-0.1.1 → rndc_python-0.2.0}/src/rndc_python/rndc_protocol.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: rndc-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Python client for ISC BIND's RNDC
|
|
5
5
|
Project-URL: Homepage, https://github.com/davidgroves/rndc-python
|
|
6
6
|
Project-URL: Repository, https://github.com/davidgroves/rndc-python
|
|
@@ -67,10 +67,10 @@ The package includes a CLI tool `rndc-python-cli`:
|
|
|
67
67
|
rndc-python-cli -s 127.0.0.1 -p 953 -a sha256 -k <base64-secret> status
|
|
68
68
|
|
|
69
69
|
# Using environment variables
|
|
70
|
-
export
|
|
71
|
-
export
|
|
72
|
-
export
|
|
73
|
-
export
|
|
70
|
+
export RNDC_HOST=127.0.0.1
|
|
71
|
+
export RNDC_PORT=953
|
|
72
|
+
export RNDC_ALGORITHM=sha256
|
|
73
|
+
export RNDC_SECRET=<base64-secret>
|
|
74
74
|
rndc-python-cli status
|
|
75
75
|
|
|
76
76
|
# Mix of both (CLI options override env vars)
|
|
@@ -81,13 +81,13 @@ rndc-python-cli --port 954 reload
|
|
|
81
81
|
|
|
82
82
|
The client can read its settings from environment variables (or a `.env` file):
|
|
83
83
|
|
|
84
|
-
- `
|
|
85
|
-
- `
|
|
86
|
-
- `
|
|
87
|
-
- `
|
|
88
|
-
- `
|
|
89
|
-
- `
|
|
90
|
-
- `
|
|
84
|
+
- `RNDC_HOST`
|
|
85
|
+
- `RNDC_PORT`
|
|
86
|
+
- `RNDC_ALGORITHM` (e.g. `hmac-sha256`)
|
|
87
|
+
- `RNDC_SECRET`
|
|
88
|
+
- `RNDC_TIMEOUT`
|
|
89
|
+
- `RNDC_MAX_RETRIES`
|
|
90
|
+
- `RNDC_RETRY_DELAY`
|
|
91
91
|
|
|
92
92
|
You can also configure the client directly in Python:
|
|
93
93
|
|
|
@@ -27,10 +27,10 @@ The package includes a CLI tool `rndc-python-cli`:
|
|
|
27
27
|
rndc-python-cli -s 127.0.0.1 -p 953 -a sha256 -k <base64-secret> status
|
|
28
28
|
|
|
29
29
|
# Using environment variables
|
|
30
|
-
export
|
|
31
|
-
export
|
|
32
|
-
export
|
|
33
|
-
export
|
|
30
|
+
export RNDC_HOST=127.0.0.1
|
|
31
|
+
export RNDC_PORT=953
|
|
32
|
+
export RNDC_ALGORITHM=sha256
|
|
33
|
+
export RNDC_SECRET=<base64-secret>
|
|
34
34
|
rndc-python-cli status
|
|
35
35
|
|
|
36
36
|
# Mix of both (CLI options override env vars)
|
|
@@ -41,13 +41,13 @@ rndc-python-cli --port 954 reload
|
|
|
41
41
|
|
|
42
42
|
The client can read its settings from environment variables (or a `.env` file):
|
|
43
43
|
|
|
44
|
-
- `
|
|
45
|
-
- `
|
|
46
|
-
- `
|
|
47
|
-
- `
|
|
48
|
-
- `
|
|
49
|
-
- `
|
|
50
|
-
- `
|
|
44
|
+
- `RNDC_HOST`
|
|
45
|
+
- `RNDC_PORT`
|
|
46
|
+
- `RNDC_ALGORITHM` (e.g. `hmac-sha256`)
|
|
47
|
+
- `RNDC_SECRET`
|
|
48
|
+
- `RNDC_TIMEOUT`
|
|
49
|
+
- `RNDC_MAX_RETRIES`
|
|
50
|
+
- `RNDC_RETRY_DELAY`
|
|
51
51
|
|
|
52
52
|
You can also configure the client directly in Python:
|
|
53
53
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["hatchling>=1.25"]
|
|
2
|
+
requires = ["hatchling>=1.25", "hatch-vcs>=0.4.0"]
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rndc-python"
|
|
7
|
-
|
|
7
|
+
dynamic = ["version"]
|
|
8
8
|
description = "Python client for ISC BIND's RNDC"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -54,6 +54,9 @@ Homepage = "https://github.com/davidgroves/rndc-python"
|
|
|
54
54
|
Repository = "https://github.com/davidgroves/rndc-python"
|
|
55
55
|
Issues = "https://github.com/davidgroves/rndc-python/issues"
|
|
56
56
|
|
|
57
|
+
[tool.hatch.version]
|
|
58
|
+
source = "vcs"
|
|
59
|
+
|
|
57
60
|
[tool.hatch.build.targets.wheel]
|
|
58
61
|
packages = ["src/rndc_python"]
|
|
59
62
|
|
|
@@ -103,7 +106,8 @@ select = [
|
|
|
103
106
|
"B", # flake8-bugbear
|
|
104
107
|
"C4", # flake8-comprehensions
|
|
105
108
|
"UP", # pyupgrade
|
|
106
|
-
"SIM", # flake8-
|
|
109
|
+
"SIM", # flake8-simple
|
|
110
|
+
"BLE001", # blind except Exception — use sparingly with noqa + comment where intentional
|
|
107
111
|
]
|
|
108
112
|
ignore = [
|
|
109
113
|
"E501", # line too long (handled by formatter)
|
|
@@ -4,7 +4,13 @@ rndc-python - A Python client library for ISC BIND's RNDC
|
|
|
4
4
|
This library provides a Python interface to ISC BIND's Remote Name Daemon Control (RNDC).
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
from importlib.metadata import version
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
__version__ = version("rndc-python")
|
|
11
|
+
except Exception: # noqa: BLE001 — zpapi-style fallback when package metadata is unavailable
|
|
12
|
+
__version__ = "0.0.0+unknown"
|
|
13
|
+
|
|
8
14
|
__author__ = "David Groves"
|
|
9
15
|
__email__ = "dave@fibrecat.org"
|
|
10
16
|
|
|
@@ -5,6 +5,7 @@ Usage:
|
|
|
5
5
|
rndc-python-cli [options] <command>
|
|
6
6
|
|
|
7
7
|
Examples:
|
|
8
|
+
rndc-python-cli --version
|
|
8
9
|
rndc-python-cli status
|
|
9
10
|
rndc-python-cli reload
|
|
10
11
|
rndc-python-cli zonestatus example.com
|
|
@@ -17,6 +18,8 @@ import sys
|
|
|
17
18
|
|
|
18
19
|
import click
|
|
19
20
|
|
|
21
|
+
from . import __version__
|
|
22
|
+
from .config import ENV_ALGORITHM, ENV_HOST, ENV_PORT, ENV_SECRET, ENV_TIMEOUT
|
|
20
23
|
from .enums import TSIGAlgorithm
|
|
21
24
|
|
|
22
25
|
# Use lowercase enum names for CLI choices
|
|
@@ -26,37 +29,38 @@ ALGORITHM_CHOICES += [f"hmac-{alg.name.lower()}" for alg in TSIGAlgorithm]
|
|
|
26
29
|
|
|
27
30
|
|
|
28
31
|
@click.command()
|
|
32
|
+
@click.version_option(__version__, "-V", "--version", prog_name="rndc-python-cli")
|
|
29
33
|
@click.option(
|
|
30
34
|
"-s",
|
|
31
35
|
"--host",
|
|
32
|
-
envvar=
|
|
36
|
+
envvar=ENV_HOST,
|
|
33
37
|
help="RNDC server hostname or IP",
|
|
34
38
|
)
|
|
35
39
|
@click.option(
|
|
36
40
|
"-p",
|
|
37
41
|
"--port",
|
|
38
42
|
type=int,
|
|
39
|
-
envvar=
|
|
43
|
+
envvar=ENV_PORT,
|
|
40
44
|
help="RNDC server port",
|
|
41
45
|
)
|
|
42
46
|
@click.option(
|
|
43
47
|
"-a",
|
|
44
48
|
"--algorithm",
|
|
45
49
|
type=click.Choice(ALGORITHM_CHOICES, case_sensitive=False),
|
|
46
|
-
envvar=
|
|
50
|
+
envvar=ENV_ALGORITHM,
|
|
47
51
|
help="TSIG algorithm",
|
|
48
52
|
)
|
|
49
53
|
@click.option(
|
|
50
54
|
"-k",
|
|
51
55
|
"--secret",
|
|
52
|
-
envvar=
|
|
56
|
+
envvar=ENV_SECRET,
|
|
53
57
|
help="Base64-encoded RNDC secret key",
|
|
54
58
|
)
|
|
55
59
|
@click.option(
|
|
56
60
|
"-t",
|
|
57
61
|
"--timeout",
|
|
58
62
|
type=int,
|
|
59
|
-
envvar=
|
|
63
|
+
envvar=ENV_TIMEOUT,
|
|
60
64
|
default=10,
|
|
61
65
|
help="Connection timeout in seconds",
|
|
62
66
|
)
|
|
@@ -79,15 +83,13 @@ def main(
|
|
|
79
83
|
|
|
80
84
|
# Validate required options
|
|
81
85
|
if not host:
|
|
82
|
-
raise click.ClickException("Missing --host or
|
|
86
|
+
raise click.ClickException(f"Missing --host or {ENV_HOST} environment variable")
|
|
83
87
|
if not port:
|
|
84
|
-
raise click.ClickException("Missing --port or
|
|
88
|
+
raise click.ClickException(f"Missing --port or {ENV_PORT} environment variable")
|
|
85
89
|
if not algorithm:
|
|
86
|
-
raise click.ClickException(
|
|
87
|
-
"Missing --algorithm or ZPAPI_RNDC_ALGORITHM environment variable"
|
|
88
|
-
)
|
|
90
|
+
raise click.ClickException(f"Missing --algorithm or {ENV_ALGORITHM} environment variable")
|
|
89
91
|
if not secret:
|
|
90
|
-
raise click.ClickException("Missing --secret or
|
|
92
|
+
raise click.ClickException(f"Missing --secret or {ENV_SECRET} environment variable")
|
|
91
93
|
|
|
92
94
|
# Build client kwargs
|
|
93
95
|
client_kwargs: dict = {
|
|
@@ -126,7 +128,7 @@ def main(
|
|
|
126
128
|
raise click.ClickException(f"Configuration error: {e}") from None
|
|
127
129
|
except ConnectionError as e:
|
|
128
130
|
raise click.ClickException(f"Connection error: {e}") from None
|
|
129
|
-
except Exception as e:
|
|
131
|
+
except Exception as e: # noqa: BLE001 — surface unexpected errors as CLI failures after specific handlers
|
|
130
132
|
raise click.ClickException(str(e)) from None
|
|
131
133
|
|
|
132
134
|
|
|
@@ -9,6 +9,14 @@ import os
|
|
|
9
9
|
|
|
10
10
|
from dotenv import load_dotenv
|
|
11
11
|
|
|
12
|
+
ENV_HOST = "RNDC_HOST"
|
|
13
|
+
ENV_PORT = "RNDC_PORT"
|
|
14
|
+
ENV_ALGORITHM = "RNDC_ALGORITHM"
|
|
15
|
+
ENV_SECRET = "RNDC_SECRET"
|
|
16
|
+
ENV_TIMEOUT = "RNDC_TIMEOUT"
|
|
17
|
+
ENV_MAX_RETRIES = "RNDC_MAX_RETRIES"
|
|
18
|
+
ENV_RETRY_DELAY = "RNDC_RETRY_DELAY"
|
|
19
|
+
|
|
12
20
|
|
|
13
21
|
def _load_env_file() -> None:
|
|
14
22
|
"""Load environment variables from .env file if it exists."""
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data models for RNDC responses.
|
|
3
|
+
|
|
4
|
+
This module contains typed dataclasses for parsing RNDC command responses.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import ClassVar
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Status:
|
|
17
|
+
"""Parsed RNDC status response.
|
|
18
|
+
|
|
19
|
+
Represents the status of a BIND DNS server as returned by the 'status' command.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# Version and system info
|
|
23
|
+
version: str
|
|
24
|
+
"""BIND version string (e.g., 'BIND 9.20.15-2-Debian (Stable Release)')"""
|
|
25
|
+
|
|
26
|
+
running_on: str
|
|
27
|
+
"""System information where BIND is running"""
|
|
28
|
+
|
|
29
|
+
boot_time: datetime
|
|
30
|
+
"""When the server was started"""
|
|
31
|
+
|
|
32
|
+
last_configured: datetime
|
|
33
|
+
"""When the server was last configured/reloaded"""
|
|
34
|
+
|
|
35
|
+
configuration_file: str
|
|
36
|
+
"""Path to the named.conf configuration file"""
|
|
37
|
+
|
|
38
|
+
# Resource info
|
|
39
|
+
cpus_found: int
|
|
40
|
+
"""Number of CPUs detected"""
|
|
41
|
+
|
|
42
|
+
worker_threads: int
|
|
43
|
+
"""Number of worker threads"""
|
|
44
|
+
|
|
45
|
+
# Zone info
|
|
46
|
+
number_of_zones: int
|
|
47
|
+
"""Total number of zones"""
|
|
48
|
+
|
|
49
|
+
automatic_zones: int
|
|
50
|
+
"""Number of automatic zones (built-in zones like localhost)"""
|
|
51
|
+
|
|
52
|
+
# Debug and logging
|
|
53
|
+
debug_level: int
|
|
54
|
+
"""Current debug level (0 = off)"""
|
|
55
|
+
|
|
56
|
+
query_logging: bool
|
|
57
|
+
"""Whether query logging is enabled"""
|
|
58
|
+
|
|
59
|
+
response_logging: bool
|
|
60
|
+
"""Whether response logging is enabled"""
|
|
61
|
+
|
|
62
|
+
memory_profiling_active: bool
|
|
63
|
+
"""Whether memory profiling is active"""
|
|
64
|
+
|
|
65
|
+
# Transfer status
|
|
66
|
+
xfers_running: int
|
|
67
|
+
"""Number of zone transfers currently running"""
|
|
68
|
+
|
|
69
|
+
xfers_deferred: int
|
|
70
|
+
"""Number of zone transfers deferred"""
|
|
71
|
+
|
|
72
|
+
xfers_first_refresh: int
|
|
73
|
+
"""Number of zones waiting for first refresh"""
|
|
74
|
+
|
|
75
|
+
soa_queries_in_progress: int
|
|
76
|
+
"""Number of SOA queries in progress"""
|
|
77
|
+
|
|
78
|
+
# Client connections
|
|
79
|
+
recursive_clients_current: int
|
|
80
|
+
"""Current number of recursive clients"""
|
|
81
|
+
|
|
82
|
+
recursive_clients_soft_limit: int
|
|
83
|
+
"""Soft limit for recursive clients"""
|
|
84
|
+
|
|
85
|
+
recursive_clients_hard_limit: int
|
|
86
|
+
"""Hard limit for recursive clients"""
|
|
87
|
+
|
|
88
|
+
recursive_high_water: int
|
|
89
|
+
"""High water mark for recursive clients"""
|
|
90
|
+
|
|
91
|
+
tcp_clients_current: int
|
|
92
|
+
"""Current number of TCP clients"""
|
|
93
|
+
|
|
94
|
+
tcp_clients_limit: int
|
|
95
|
+
"""Limit for TCP clients"""
|
|
96
|
+
|
|
97
|
+
tcp_high_water: int
|
|
98
|
+
"""High water mark for TCP clients"""
|
|
99
|
+
|
|
100
|
+
# Server state
|
|
101
|
+
server_is_up: bool
|
|
102
|
+
"""Whether the server is up and running"""
|
|
103
|
+
|
|
104
|
+
# Raw text for any unparsed fields
|
|
105
|
+
raw_text: str
|
|
106
|
+
"""The raw status text from RNDC"""
|
|
107
|
+
|
|
108
|
+
# Date format used by BIND
|
|
109
|
+
_DATE_FORMAT: ClassVar[str] = "%a, %d %b %Y %H:%M:%S %Z"
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def from_text(cls, text: str) -> Status:
|
|
113
|
+
"""Parse status text into a Status object.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
text: Raw status text from RNDC status command
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Parsed Status object
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def get_value(pattern: str, default: str = "") -> str:
|
|
123
|
+
"""Extract a value using regex pattern."""
|
|
124
|
+
match = re.search(pattern, text, re.MULTILINE)
|
|
125
|
+
return match.group(1).strip() if match else default
|
|
126
|
+
|
|
127
|
+
def get_int(pattern: str, default: int = 0) -> int:
|
|
128
|
+
"""Extract an integer value using regex pattern."""
|
|
129
|
+
value = get_value(pattern)
|
|
130
|
+
try:
|
|
131
|
+
return int(value) if value else default
|
|
132
|
+
except ValueError:
|
|
133
|
+
return default
|
|
134
|
+
|
|
135
|
+
def parse_datetime(pattern: str) -> datetime:
|
|
136
|
+
"""Parse a datetime value using regex pattern."""
|
|
137
|
+
value = get_value(pattern)
|
|
138
|
+
if not value:
|
|
139
|
+
return datetime.min
|
|
140
|
+
try:
|
|
141
|
+
return datetime.strptime(value, cls._DATE_FORMAT)
|
|
142
|
+
except ValueError:
|
|
143
|
+
return datetime.min
|
|
144
|
+
|
|
145
|
+
def parse_bool_on_off(pattern: str) -> bool:
|
|
146
|
+
"""Parse an ON/OFF boolean value."""
|
|
147
|
+
value = get_value(pattern).upper()
|
|
148
|
+
return value == "ON"
|
|
149
|
+
|
|
150
|
+
def parse_bool_active(pattern: str) -> bool:
|
|
151
|
+
"""Parse an ACTIVE/INACTIVE boolean value."""
|
|
152
|
+
value = get_value(pattern).upper()
|
|
153
|
+
return value == "ACTIVE"
|
|
154
|
+
|
|
155
|
+
# Parse zones with automatic count
|
|
156
|
+
zones_match = re.search(r"number of zones:\s*(\d+)\s*(?:\((\d+)\s*automatic\))?", text)
|
|
157
|
+
number_of_zones = int(zones_match.group(1)) if zones_match else 0
|
|
158
|
+
automatic_zones = int(zones_match.group(2)) if zones_match and zones_match.group(2) else 0
|
|
159
|
+
|
|
160
|
+
# Parse recursive clients: current/soft/hard
|
|
161
|
+
recursive_match = re.search(r"recursive clients:\s*(\d+)/(\d+)/(\d+)", text)
|
|
162
|
+
if recursive_match:
|
|
163
|
+
recursive_current = int(recursive_match.group(1))
|
|
164
|
+
recursive_soft = int(recursive_match.group(2))
|
|
165
|
+
recursive_hard = int(recursive_match.group(3))
|
|
166
|
+
else:
|
|
167
|
+
recursive_current = recursive_soft = recursive_hard = 0
|
|
168
|
+
|
|
169
|
+
# Parse TCP clients: current/limit
|
|
170
|
+
tcp_match = re.search(r"tcp clients:\s*(\d+)/(\d+)", text, re.IGNORECASE)
|
|
171
|
+
if tcp_match:
|
|
172
|
+
tcp_current = int(tcp_match.group(1))
|
|
173
|
+
tcp_limit = int(tcp_match.group(2))
|
|
174
|
+
else:
|
|
175
|
+
tcp_current = tcp_limit = 0
|
|
176
|
+
|
|
177
|
+
return cls(
|
|
178
|
+
version=get_value(r"^version:\s*(.+)$"),
|
|
179
|
+
running_on=get_value(r"^running on [^:]+:\s*(.+)$"),
|
|
180
|
+
boot_time=parse_datetime(r"^boot time:\s*(.+)$"),
|
|
181
|
+
last_configured=parse_datetime(r"^last configured:\s*(.+)$"),
|
|
182
|
+
configuration_file=get_value(r"^configuration file:\s*(.+)$"),
|
|
183
|
+
cpus_found=get_int(r"^CPUs found:\s*(\d+)"),
|
|
184
|
+
worker_threads=get_int(r"^worker threads:\s*(\d+)"),
|
|
185
|
+
number_of_zones=number_of_zones,
|
|
186
|
+
automatic_zones=automatic_zones,
|
|
187
|
+
debug_level=get_int(r"^debug level:\s*(\d+)"),
|
|
188
|
+
query_logging=parse_bool_on_off(r"^query logging is\s*(\w+)"),
|
|
189
|
+
response_logging=parse_bool_on_off(r"^response logging is\s*(\w+)"),
|
|
190
|
+
memory_profiling_active=parse_bool_active(r"^memory profiling is\s*(\w+)"),
|
|
191
|
+
xfers_running=get_int(r"^xfers running:\s*(\d+)"),
|
|
192
|
+
xfers_deferred=get_int(r"^xfers deferred:\s*(\d+)"),
|
|
193
|
+
xfers_first_refresh=get_int(r"^xfers first refresh:\s*(\d+)"),
|
|
194
|
+
soa_queries_in_progress=get_int(r"^soa queries in progress:\s*(\d+)"),
|
|
195
|
+
recursive_clients_current=recursive_current,
|
|
196
|
+
recursive_clients_soft_limit=recursive_soft,
|
|
197
|
+
recursive_clients_hard_limit=recursive_hard,
|
|
198
|
+
recursive_high_water=get_int(r"^recursive high-water:\s*(\d+)"),
|
|
199
|
+
tcp_clients_current=tcp_current,
|
|
200
|
+
tcp_clients_limit=tcp_limit,
|
|
201
|
+
tcp_high_water=get_int(r"^TCP high-water:\s*(\d+)", 0),
|
|
202
|
+
server_is_up="server is up and running" in text.lower(),
|
|
203
|
+
raw_text=text,
|
|
204
|
+
)
|
|
@@ -17,6 +17,7 @@ import dns
|
|
|
17
17
|
import dns.rdataclass
|
|
18
18
|
|
|
19
19
|
from . import rndc_protocol
|
|
20
|
+
from .config import ENV_ALGORITHM, ENV_HOST, ENV_PORT, ENV_SECRET
|
|
20
21
|
from .enums import TSIGAlgorithm
|
|
21
22
|
from .exceptions import (
|
|
22
23
|
RNDCAuthenticationError,
|
|
@@ -54,28 +55,28 @@ class RNDCClient:
|
|
|
54
55
|
elif rndc_config is not None:
|
|
55
56
|
self.host = rndc_config.host
|
|
56
57
|
else:
|
|
57
|
-
raise ValueError("host is required (provide it or set
|
|
58
|
+
raise ValueError(f"host is required (provide it or set {ENV_HOST})")
|
|
58
59
|
|
|
59
60
|
if port is not None:
|
|
60
61
|
self.port = port
|
|
61
62
|
elif rndc_config is not None:
|
|
62
63
|
self.port = rndc_config.port
|
|
63
64
|
else:
|
|
64
|
-
raise ValueError("port is required (provide it or set
|
|
65
|
+
raise ValueError(f"port is required (provide it or set {ENV_PORT})")
|
|
65
66
|
|
|
66
67
|
if algorithm is not None:
|
|
67
68
|
self.algorithm = algorithm
|
|
68
69
|
elif rndc_config is not None:
|
|
69
70
|
self.algorithm = rndc_config.algorithm
|
|
70
71
|
else:
|
|
71
|
-
raise ValueError("algorithm is required (provide it or set
|
|
72
|
+
raise ValueError(f"algorithm is required (provide it or set {ENV_ALGORITHM})")
|
|
72
73
|
|
|
73
74
|
if secret is not None:
|
|
74
75
|
self.secret = base64.b64decode(secret)
|
|
75
76
|
elif rndc_config is not None:
|
|
76
77
|
self.secret = base64.b64decode(rndc_config.secret)
|
|
77
78
|
else:
|
|
78
|
-
raise ValueError("secret is required (provide it or set
|
|
79
|
+
raise ValueError(f"secret is required (provide it or set {ENV_SECRET})")
|
|
79
80
|
|
|
80
81
|
if timeout is not None:
|
|
81
82
|
self.timeout = timeout
|
|
@@ -198,7 +199,7 @@ class RNDCClient:
|
|
|
198
199
|
|
|
199
200
|
try:
|
|
200
201
|
remote_hash = base64.b64decode(b64_hash)
|
|
201
|
-
except Exception:
|
|
202
|
+
except Exception: # noqa: BLE001 — malformed TSIG hash must fail verification, not raise
|
|
202
203
|
return False
|
|
203
204
|
|
|
204
205
|
# Verify hash
|
|
@@ -8,6 +8,13 @@ Assumes .env file contains all required defaults if they aren't set in the envir
|
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
10
|
from .config import (
|
|
11
|
+
ENV_ALGORITHM,
|
|
12
|
+
ENV_HOST,
|
|
13
|
+
ENV_MAX_RETRIES,
|
|
14
|
+
ENV_PORT,
|
|
15
|
+
ENV_RETRY_DELAY,
|
|
16
|
+
ENV_SECRET,
|
|
17
|
+
ENV_TIMEOUT,
|
|
11
18
|
_get_required_env_var,
|
|
12
19
|
_load_env_file,
|
|
13
20
|
_parse_float_env_var,
|
|
@@ -48,15 +55,13 @@ class RNDCConfig:
|
|
|
48
55
|
retry_delay: float | None = None,
|
|
49
56
|
) -> None:
|
|
50
57
|
_load_env_file()
|
|
51
|
-
self.host = host or _get_required_env_var(
|
|
52
|
-
self.port = port or _parse_port(_get_required_env_var(
|
|
53
|
-
self.algorithm = algorithm or _parse_algorithm(
|
|
54
|
-
|
|
55
|
-
)
|
|
56
|
-
self.
|
|
57
|
-
self.
|
|
58
|
-
self.max_retries = max_retries or _parse_int_env_var("ZPAPI_RNDC_MAX_RETRIES", 3)
|
|
59
|
-
self.retry_delay = retry_delay or _parse_float_env_var("ZPAPI_RNDC_RETRY_DELAY", 1.0)
|
|
58
|
+
self.host = host or _get_required_env_var(ENV_HOST)
|
|
59
|
+
self.port = port or _parse_port(_get_required_env_var(ENV_PORT))
|
|
60
|
+
self.algorithm = algorithm or _parse_algorithm(_get_required_env_var(ENV_ALGORITHM))
|
|
61
|
+
self.secret = secret or _get_required_env_var(ENV_SECRET)
|
|
62
|
+
self.timeout = timeout or _parse_timeout(_get_required_env_var(ENV_TIMEOUT))
|
|
63
|
+
self.max_retries = max_retries or _parse_int_env_var(ENV_MAX_RETRIES, 3)
|
|
64
|
+
self.retry_delay = retry_delay or _parse_float_env_var(ENV_RETRY_DELAY, 1.0)
|
|
60
65
|
|
|
61
66
|
def to_dict(self) -> dict[str, Any]:
|
|
62
67
|
return {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|