mreg-cli 1.7.2__tar.gz → 1.7.3__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.7.2 → mreg_cli-1.7.3}/CHANGELOG.md +10 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/PKG-INFO +1 -1
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/_version.py +3 -3
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/logging.py +14 -12
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/config.py +15 -4
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/log.py +8 -1
- mreg_cli-1.7.3/mreg_cli/utilities/fs.py +86 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli.egg-info/PKG-INFO +1 -1
- mreg_cli-1.7.2/mreg_cli/utilities/fs.py +0 -57
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/.github/workflows/publish.yml +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/.github/workflows/test.yml +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/.gitignore +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/.markdownlint.json +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/AUTHORS +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/CONTRIBUTING +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/LICENSE +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/README.md +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/ci/Dockerfile +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/ci/diff.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/ci/docker-compose.yml +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/ci/run_testsuite_and_record.sh +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/ci/run_testsuite_and_record_V2.sh +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/ci/testsuite +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/ci/testsuite-result.json +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/data/mreg-cli.conf +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/__about__.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/__init__.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/__main__.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/api/__init__.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/api/abstracts.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/api/endpoints.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/api/errors.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/api/fields.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/api/history.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/api/models.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/cache.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/cli.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/__init__.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/base.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/cache.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/dhcp.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/group.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/help.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/host.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/host_submodules/__init__.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/host_submodules/a_aaaa.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/host_submodules/bacnet.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/host_submodules/cname.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/host_submodules/core.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/host_submodules/rr.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/label.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/network.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/permission.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/policy.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/recording.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/registry.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/root.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/commands/zone.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/dirs.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/errorbuilder.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/exceptions.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/help_formatter.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/main.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/outputmanager.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/prompt.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/py.typed +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/tags.txt +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/tokenfile.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/types.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/utilities/__init__.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/utilities/api.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/utilities/shared.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli/utilities/validators.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli.egg-info/SOURCES.txt +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli.egg-info/dependency_links.txt +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli.egg-info/entry_points.txt +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli.egg-info/requires.txt +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/mreg_cli.egg-info/top_level.txt +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/pyproject.toml +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/run.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/setup.cfg +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/tests/__init__.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/tests/api/__init__.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/tests/api/test_fields.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/tests/api/test_models.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/tests/conftest.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/tests/test_cache.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/tests/test_config.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/tests/test_errorbuilder.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/tests/test_exceptions.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/tests/test_outputmanager.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/tests/test_prompt.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/tests/test_tokenfile.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/tests/test_types.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/tests/utilities/__init__.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/tests/utilities/test_api.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/tests/utilities/test_fs.py +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/tox.ini +0 -0
- {mreg_cli-1.7.2 → mreg_cli-1.7.3}/uv.lock +0 -0
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
<!-- ## Unreleased -->
|
|
9
9
|
|
|
10
|
+
## [1.7.3](https://github.com/unioslo/mreg-cli/releases/tag/1.7.3) - 2025-11-18
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- No longer requires a writable log file to start the application. In extreme cases where no writable log file directory can be found, logging will be disabled.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Temporary file creation failing due to non-unique filename in `/tmp` directory. Now creates a unique temporary directory for the temporary file.
|
|
19
|
+
|
|
10
20
|
## [1.7.2](https://github.com/unioslo/mreg-cli/releases/tag/1.7.2) - 2025-11-17
|
|
11
21
|
|
|
12
22
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mreg-cli
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.3
|
|
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>
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '1.7.
|
|
32
|
-
__version_tuple__ = version_tuple = (1, 7,
|
|
31
|
+
__version__ = version = '1.7.3'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 7, 3)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g9939333a3'
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
|
+
from pathlib import Path
|
|
6
7
|
from typing import Any
|
|
7
8
|
|
|
8
9
|
from mreg_cli.commands.base import BaseCommand
|
|
@@ -66,10 +67,9 @@ def start_logging(args: argparse.Namespace) -> None:
|
|
|
66
67
|
status = log.status
|
|
67
68
|
# Abort if already logging with same params
|
|
68
69
|
if status.enabled and status.file == log_file and status.level == level:
|
|
69
|
-
raise InputFailure(f"Logging already enabled: {
|
|
70
|
-
|
|
70
|
+
raise InputFailure(f"Logging already enabled: {status.as_str()}")
|
|
71
71
|
log.start_logging(log_file, level)
|
|
72
|
-
OutputManager().add_line(f"Logging started: {
|
|
72
|
+
OutputManager().add_line(f"Logging started: {status.as_str()}")
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
@command_registry.register_command(
|
|
@@ -81,7 +81,7 @@ def stop_logging(_: argparse.Namespace):
|
|
|
81
81
|
"""Stop logging."""
|
|
82
82
|
log = MregCliLogger()
|
|
83
83
|
if not log.status.enabled:
|
|
84
|
-
raise InputFailure("Logging is
|
|
84
|
+
raise InputFailure("Logging is already disabled.")
|
|
85
85
|
|
|
86
86
|
log.stop_logging()
|
|
87
87
|
OutputManager().add_line("Logging stopped.")
|
|
@@ -110,7 +110,7 @@ def set_logging_level(args: argparse.Namespace) -> None:
|
|
|
110
110
|
raise InputFailure("Logging is not enabled, cannot set level.")
|
|
111
111
|
|
|
112
112
|
# Determine logging params
|
|
113
|
-
log_file = log.status.file or config.log_file
|
|
113
|
+
log_file = log.status.file or config.log_file
|
|
114
114
|
level = LogLevel(args.level)
|
|
115
115
|
log.start_logging(log_file, level)
|
|
116
116
|
|
|
@@ -125,19 +125,21 @@ def logging_status(_: argparse.Namespace):
|
|
|
125
125
|
conf = MregCliConfig()
|
|
126
126
|
|
|
127
127
|
status = MregCliLogger().status
|
|
128
|
+
log_file = status.file or conf.log_file # fallback should never be reachable...
|
|
129
|
+
|
|
128
130
|
if not status.enabled:
|
|
129
131
|
raise InputFailure("Logging is disabled.")
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
elif not log_file:
|
|
133
|
+
OutputManager().add_line(status.as_str())
|
|
134
|
+
return
|
|
133
135
|
|
|
134
136
|
lines_in_logfile = 0
|
|
135
137
|
try:
|
|
136
|
-
with open(
|
|
138
|
+
with open(log_file) as f:
|
|
137
139
|
lines_in_logfile = sum(1 for _ in f)
|
|
138
140
|
except OSError as e:
|
|
139
|
-
raise FileError(f"Unable to read log file {
|
|
141
|
+
raise FileError(f"Unable to read log file {log_file}: {e}") from e
|
|
140
142
|
|
|
141
|
-
filesize = sizeof_fmt(
|
|
143
|
+
filesize = sizeof_fmt(log_file.stat().st_size)
|
|
142
144
|
|
|
143
|
-
OutputManager().add_line(f"{
|
|
145
|
+
OutputManager().add_line(f"{status.as_str()} ({lines_in_logfile} lines, {filesize})")
|
|
@@ -157,7 +157,14 @@ class IniConfigSettingsSource(InitSettingsSource, ConfigFileSourceMixin):
|
|
|
157
157
|
return f"{self.__class__.__name__}()"
|
|
158
158
|
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
def to_path_optional(value: Any) -> Path | None:
|
|
161
|
+
"""Convert a value to a Path object with expanded user and resolved symlinks, or None."""
|
|
162
|
+
if value is None:
|
|
163
|
+
return None
|
|
164
|
+
return to_path(value)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
ResolvedPath = Annotated[Path, AfterValidator(to_path_optional)]
|
|
161
168
|
"""Path type that is user expanded (~) and resolved (absolute+symlinks) after validation."""
|
|
162
169
|
|
|
163
170
|
|
|
@@ -179,7 +186,7 @@ class MregCliConfig(BaseSettings):
|
|
|
179
186
|
token_only: bool = False
|
|
180
187
|
source: ResolvedPath | None = None
|
|
181
188
|
verbose: bool = False
|
|
182
|
-
log_file: ResolvedPath = Field(
|
|
189
|
+
log_file: ResolvedPath | None = Field(
|
|
183
190
|
LOG_FILE_DEFAULT,
|
|
184
191
|
# New and old names both valid
|
|
185
192
|
validation_alias=AliasChoices("logfile", "log_file"),
|
|
@@ -247,9 +254,13 @@ class MregCliConfig(BaseSettings):
|
|
|
247
254
|
return []
|
|
248
255
|
|
|
249
256
|
@field_validator("log_file", mode="after")
|
|
250
|
-
def _ensure_log_file_writable(cls, v: Path) -> Path:
|
|
257
|
+
def _ensure_log_file_writable(cls, v: Path) -> Path | None:
|
|
251
258
|
"""Ensure we have a writable log file."""
|
|
252
|
-
|
|
259
|
+
try:
|
|
260
|
+
return get_writable_file_or_tempfile(v)
|
|
261
|
+
except OSError as e:
|
|
262
|
+
logger.error(str(e))
|
|
263
|
+
return None
|
|
253
264
|
|
|
254
265
|
@override
|
|
255
266
|
@classmethod
|
|
@@ -23,6 +23,10 @@ class LoggingStatus(NamedTuple):
|
|
|
23
23
|
file: Path | None
|
|
24
24
|
level: LogLevel
|
|
25
25
|
|
|
26
|
+
def as_str(self) -> str:
|
|
27
|
+
"""Get a string representation of the logging status."""
|
|
28
|
+
return f"{self.level} > {self.file or 'stderr'}" if self.enabled else "disabled"
|
|
29
|
+
|
|
26
30
|
|
|
27
31
|
class MregCliLogger:
|
|
28
32
|
"""Singleton that manages logging state."""
|
|
@@ -44,7 +48,10 @@ class MregCliLogger:
|
|
|
44
48
|
return LoggingStatus(self._is_logging, self._file, self._level)
|
|
45
49
|
|
|
46
50
|
def start_logging(
|
|
47
|
-
self,
|
|
51
|
+
self,
|
|
52
|
+
logfile: Path | None,
|
|
53
|
+
level: LogLevel | str = LogLevel.INFO,
|
|
54
|
+
fmt: str = LOGGING_FORMAT,
|
|
48
55
|
) -> None:
|
|
49
56
|
"""Start logging to the specified file. Disables logging if already enabled."""
|
|
50
57
|
level = LogLevel(level)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""File system utilities."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import functools
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from mreg_cli.exceptions import InputFailure
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_writable_file_or_tempfile(path: Path) -> Path:
|
|
17
|
+
"""Ensure a writable file path exists, creating it if necessary, or fall back to a temporary file.
|
|
18
|
+
|
|
19
|
+
:param path: The desired file path.
|
|
20
|
+
:returns: The original path if writable, otherwise a temporary file path.
|
|
21
|
+
"""
|
|
22
|
+
try:
|
|
23
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
24
|
+
if path.exists() and path.is_dir():
|
|
25
|
+
raise IsADirectoryError(f"Path {path} is a directory, not a file")
|
|
26
|
+
if path.exists():
|
|
27
|
+
check_writable(path)
|
|
28
|
+
else:
|
|
29
|
+
# Create new file if it doesn't exist (very defensively)
|
|
30
|
+
path.touch(exist_ok=True)
|
|
31
|
+
except OSError as e:
|
|
32
|
+
import time # noqa: PLC0415
|
|
33
|
+
|
|
34
|
+
logger.error(
|
|
35
|
+
"Unable to create file at %s: %s",
|
|
36
|
+
path,
|
|
37
|
+
e,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
filename = path.name or f"mreg-cli-temp-{int(time.time())}"
|
|
41
|
+
# NOTE: Not really a temporary dir, since we never delete it!
|
|
42
|
+
tempdir = get_temp_dir()
|
|
43
|
+
tmpfile = tempdir / filename
|
|
44
|
+
try:
|
|
45
|
+
tmpfile.touch(exist_ok=True)
|
|
46
|
+
check_writable(tmpfile)
|
|
47
|
+
except Exception as tmp_err:
|
|
48
|
+
raise OSError(f"Unable to create temporary file at {tmpfile}: {tmp_err}") from tmp_err
|
|
49
|
+
logger.warning("Using temporary file at %s", tmpfile)
|
|
50
|
+
return tmpfile
|
|
51
|
+
return path
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def check_writable(path: Path) -> None:
|
|
55
|
+
"""Check if the given path is writable.
|
|
56
|
+
|
|
57
|
+
HACKY: Tests write access on file by opening in append mode.
|
|
58
|
+
"""
|
|
59
|
+
# Check write privileges without overwriting existing content
|
|
60
|
+
with path.open("a"):
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@functools.cache
|
|
65
|
+
def get_temp_dir() -> Path:
|
|
66
|
+
"""Get a temporary directory for use by the CLI.
|
|
67
|
+
|
|
68
|
+
Always returns the same directory for the lifetime of the process.
|
|
69
|
+
"""
|
|
70
|
+
import tempfile # noqa: PLC0415
|
|
71
|
+
|
|
72
|
+
temp_dir = tempfile.mkdtemp(prefix="mreg-cli.", suffix="." + str(os.getuid()))
|
|
73
|
+
return Path(temp_dir)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def to_path(value: Any) -> Path:
|
|
77
|
+
"""Convert a value to a Path object with expanded user and resolved symlinks."""
|
|
78
|
+
try:
|
|
79
|
+
p = Path(value)
|
|
80
|
+
try:
|
|
81
|
+
p = p.expanduser()
|
|
82
|
+
except RuntimeError: # no homedir
|
|
83
|
+
pass
|
|
84
|
+
return p.resolve()
|
|
85
|
+
except Exception as e:
|
|
86
|
+
raise InputFailure(f"Invalid path {value}: {e}") from e
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mreg-cli
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.3
|
|
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>
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
"""File system utilities."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import logging
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import Any
|
|
8
|
-
|
|
9
|
-
from mreg_cli.exceptions import InputFailure
|
|
10
|
-
|
|
11
|
-
logger = logging.getLogger(__name__)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def get_writable_file_or_tempfile(path: Path) -> Path:
|
|
15
|
-
"""Ensure a file path exists and is writable, otherwise return a temporary file."""
|
|
16
|
-
try:
|
|
17
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
18
|
-
if path.exists() and path.is_dir():
|
|
19
|
-
raise IsADirectoryError(f"Path {path} is a directory, not a file")
|
|
20
|
-
# Check write privileges without overwriting existing content
|
|
21
|
-
if path.exists():
|
|
22
|
-
# Test write access on existing file by opening in append mode
|
|
23
|
-
with path.open("a"):
|
|
24
|
-
pass
|
|
25
|
-
else:
|
|
26
|
-
# Create new file if it doesn't exist
|
|
27
|
-
path.touch()
|
|
28
|
-
except OSError as e:
|
|
29
|
-
import tempfile # noqa: PLC0415 # only import if we really need it
|
|
30
|
-
import time # noqa: PLC0415
|
|
31
|
-
|
|
32
|
-
# NOTE: we might not be able to write to the temp file either
|
|
33
|
-
# but in that case we are so far outside the normal
|
|
34
|
-
# usage patterns that we should probably just fail
|
|
35
|
-
tmpfile = Path(tempfile.gettempdir()) / path.name or f"mreg-cli-temp-{int(time.time())}"
|
|
36
|
-
logger.error(
|
|
37
|
-
"Unable to create file at %s due to: %s; falling back to temporary file at %s",
|
|
38
|
-
path,
|
|
39
|
-
e,
|
|
40
|
-
tmpfile,
|
|
41
|
-
)
|
|
42
|
-
tmpfile.touch()
|
|
43
|
-
return tmpfile
|
|
44
|
-
return path
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def to_path(value: Any) -> Path:
|
|
48
|
-
"""Convert a value to a Path object with expanded user and resolved symlinks."""
|
|
49
|
-
try:
|
|
50
|
-
p = Path(value)
|
|
51
|
-
try:
|
|
52
|
-
p = p.expanduser()
|
|
53
|
-
except RuntimeError: # no homedir
|
|
54
|
-
pass
|
|
55
|
-
return p.resolve()
|
|
56
|
-
except Exception as e:
|
|
57
|
-
raise InputFailure(f"Invalid path {value}: {e}") from e
|
|
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
|
|
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
|