pglift-cli 1.7.0__tar.gz → 1.9.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.
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/PKG-INFO +9 -7
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/pyproject.toml +6 -7
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/_site.py +2 -3
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/base.py +1 -0
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/instance.py +17 -6
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/main.py +5 -5
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/pgconf.py +6 -6
- pglift_cli-1.9.0/src/pglift_cli/pghba.py +57 -0
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/role.py +21 -3
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/util.py +25 -5
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/.gitignore +0 -0
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/README.md +0 -0
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/hatch.toml +0 -0
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/__init__.py +0 -0
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/__main__.py +0 -0
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/_settings.py +0 -0
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/console.py +0 -0
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/database.py +0 -0
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/hookspecs.py +0 -0
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/model.py +0 -0
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/patroni.py +0 -0
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/pgbackrest/__init__.py +0 -0
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/pgbackrest/repo_path.py +0 -0
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/pm.py +0 -0
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/postgres.py +0 -0
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/prometheus.py +0 -0
- {pglift_cli-1.7.0 → pglift_cli-1.9.0}/src/pglift_cli/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pglift_cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.0
|
|
4
4
|
Summary: Command-line interface for pglift
|
|
5
5
|
Project-URL: Documentation, https://pglift.readthedocs.io/
|
|
6
6
|
Project-URL: Source, https://gitlab.com/dalibo/pglift/
|
|
@@ -18,6 +18,8 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.9
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
23
|
Classifier: Topic :: Database
|
|
22
24
|
Classifier: Topic :: System :: Systems Administration
|
|
23
25
|
Classifier: Typing :: Typed
|
|
@@ -26,18 +28,18 @@ Requires-Dist: click!=8.1.0,!=8.1.4,>=8.0.0
|
|
|
26
28
|
Requires-Dist: filelock!=3.12.1,>=3.9.0
|
|
27
29
|
Requires-Dist: pluggy
|
|
28
30
|
Requires-Dist: psycopg>=3.1
|
|
29
|
-
Requires-Dist: pydantic
|
|
31
|
+
Requires-Dist: pydantic!=2.10.0,!=2.10.1,>=2.5.0
|
|
30
32
|
Requires-Dist: pyyaml>=6.0.1
|
|
31
|
-
Requires-Dist: rich
|
|
33
|
+
Requires-Dist: rich!=13.9.0,>=11.0.0
|
|
32
34
|
Provides-Extra: dev
|
|
33
35
|
Requires-Dist: anyio; extra == 'dev'
|
|
34
|
-
Requires-Dist: mypy!=1.11.*,>=1.10.0; (python_version < '3.10') and extra == 'dev'
|
|
36
|
+
Requires-Dist: mypy!=1.11.*,!=1.12.*,>=1.10.0; (python_version < '3.10') and extra == 'dev'
|
|
35
37
|
Requires-Dist: mypy>=1.10.0; (python_version >= '3.10') and extra == 'dev'
|
|
36
38
|
Requires-Dist: patroni[etcd]>=2.1.5; extra == 'dev'
|
|
37
39
|
Requires-Dist: port-for; extra == 'dev'
|
|
38
40
|
Requires-Dist: prysk[pytest-plugin]>=0.14.0; extra == 'dev'
|
|
39
|
-
Requires-Dist: pytest; extra == 'dev'
|
|
40
41
|
Requires-Dist: pytest-cov; extra == 'dev'
|
|
42
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
41
43
|
Requires-Dist: trustme; extra == 'dev'
|
|
42
44
|
Requires-Dist: types-pyyaml>=6.0.12.10; extra == 'dev'
|
|
43
45
|
Provides-Extra: test
|
|
@@ -45,11 +47,11 @@ Requires-Dist: anyio; extra == 'test'
|
|
|
45
47
|
Requires-Dist: patroni[etcd]>=2.1.5; extra == 'test'
|
|
46
48
|
Requires-Dist: port-for; extra == 'test'
|
|
47
49
|
Requires-Dist: prysk[pytest-plugin]>=0.14.0; extra == 'test'
|
|
48
|
-
Requires-Dist: pytest; extra == 'test'
|
|
49
50
|
Requires-Dist: pytest-cov; extra == 'test'
|
|
51
|
+
Requires-Dist: pytest>=8; extra == 'test'
|
|
50
52
|
Requires-Dist: trustme; extra == 'test'
|
|
51
53
|
Provides-Extra: typing
|
|
52
|
-
Requires-Dist: mypy!=1.11.*,>=1.10.0; (python_version < '3.10') and extra == 'typing'
|
|
54
|
+
Requires-Dist: mypy!=1.11.*,!=1.12.*,>=1.10.0; (python_version < '3.10') and extra == 'typing'
|
|
53
55
|
Requires-Dist: mypy>=1.10.0; (python_version >= '3.10') and extra == 'typing'
|
|
54
56
|
Requires-Dist: types-pyyaml>=6.0.12.10; extra == 'typing'
|
|
55
57
|
Description-Content-Type: text/markdown
|
|
@@ -31,6 +31,8 @@ classifiers = [
|
|
|
31
31
|
"Programming Language :: Python :: 3.9",
|
|
32
32
|
"Programming Language :: Python :: 3.10",
|
|
33
33
|
"Programming Language :: Python :: 3.11",
|
|
34
|
+
"Programming Language :: Python :: 3.12",
|
|
35
|
+
"Programming Language :: Python :: 3.13",
|
|
34
36
|
"Programming Language :: Python :: 3 :: Only",
|
|
35
37
|
"Typing :: Typed",
|
|
36
38
|
]
|
|
@@ -42,8 +44,8 @@ dependencies = [
|
|
|
42
44
|
"filelock >= 3.9.0, != 3.12.1",
|
|
43
45
|
"pluggy",
|
|
44
46
|
"psycopg >= 3.1",
|
|
45
|
-
"pydantic >= 2.5.0",
|
|
46
|
-
"rich >= 11.0.0",
|
|
47
|
+
"pydantic >= 2.5.0, != 2.10.0, != 2.10.1",
|
|
48
|
+
"rich >= 11.0.0, != 13.9.0",
|
|
47
49
|
]
|
|
48
50
|
|
|
49
51
|
[project.optional-dependencies]
|
|
@@ -52,13 +54,13 @@ test = [
|
|
|
52
54
|
"patroni[etcd] >= 2.1.5",
|
|
53
55
|
"port-for",
|
|
54
56
|
"prysk[pytest-plugin] >= 0.14.0",
|
|
55
|
-
"pytest",
|
|
57
|
+
"pytest >= 8",
|
|
56
58
|
"pytest-cov",
|
|
57
59
|
"trustme",
|
|
58
60
|
]
|
|
59
61
|
typing = [
|
|
60
62
|
"mypy >= 1.10.0 ; python_version >= '3.10'",
|
|
61
|
-
"mypy >= 1.10.0, != 1.11.* ; python_version < '3.10'",
|
|
63
|
+
"mypy >= 1.10.0, != 1.11.*, != 1.12.* ; python_version < '3.10'",
|
|
62
64
|
"types-PyYAML >= 6.0.12.10",
|
|
63
65
|
]
|
|
64
66
|
dev = [
|
|
@@ -75,6 +77,3 @@ Tracker = "https://gitlab.com/dalibo/pglift/-/issues/"
|
|
|
75
77
|
"pglift_cli.pgbackrest" = "pglift_cli.pgbackrest"
|
|
76
78
|
"pglift_cli.pgbackrest.repo_path" = "pglift_cli.pgbackrest.repo_path"
|
|
77
79
|
"pglift_cli.prometheus" = "pglift_cli.prometheus"
|
|
78
|
-
|
|
79
|
-
[project.scripts]
|
|
80
|
-
pglift = "pglift_cli.main:cli"
|
|
@@ -20,7 +20,8 @@ from pglift import exceptions, plugin_manager
|
|
|
20
20
|
from pglift.models import interface
|
|
21
21
|
|
|
22
22
|
from . import hookspecs
|
|
23
|
-
from ._settings import Settings
|
|
23
|
+
from ._settings import Settings
|
|
24
|
+
from ._settings import SiteSettings as SiteSettings
|
|
24
25
|
from .pm import PluginManager
|
|
25
26
|
|
|
26
27
|
|
|
@@ -30,8 +31,6 @@ def _settings() -> Settings:
|
|
|
30
31
|
return SiteSettings()
|
|
31
32
|
except (exceptions.SettingsError, pydantic.ValidationError) as e:
|
|
32
33
|
raise click.ClickException(f"invalid site settings\n{e}") from e
|
|
33
|
-
except exceptions.UnsupportedError as e:
|
|
34
|
-
raise click.ClickException(f"unsupported operation: {e}") from None
|
|
35
34
|
|
|
36
35
|
|
|
37
36
|
_default_settings = cache(Settings)
|
|
@@ -14,6 +14,7 @@ from pydantic.v1.utils import deep_update
|
|
|
14
14
|
|
|
15
15
|
from pglift import (
|
|
16
16
|
async_hooks,
|
|
17
|
+
exceptions,
|
|
17
18
|
hooks,
|
|
18
19
|
hookspecs,
|
|
19
20
|
instances,
|
|
@@ -127,7 +128,7 @@ async def alter(obj: Obj, instance: system.Instance, **changes: Any) -> None:
|
|
|
127
128
|
values = deep_update(values, changes)
|
|
128
129
|
# No need for 'settings' in validation_context() as a 'version' key
|
|
129
130
|
# must be present in 'values' when altering.
|
|
130
|
-
with validation_context(operation="update"):
|
|
131
|
+
with validation_context(operation="update", instance=manifest):
|
|
131
132
|
altered = _site.INSTANCE_MODEL.model_validate(values)
|
|
132
133
|
await instances.apply(
|
|
133
134
|
_site.SETTINGS, altered, _is_running=status == Status.running
|
|
@@ -149,10 +150,18 @@ async def apply(
|
|
|
149
150
|
version = default_postgresql_version(_site.SETTINGS.postgresql)
|
|
150
151
|
elif not isinstance(version, str):
|
|
151
152
|
version = str(version)
|
|
152
|
-
op: Operation =
|
|
153
|
-
|
|
154
|
-
)
|
|
155
|
-
|
|
153
|
+
op: Operation = "create"
|
|
154
|
+
actual: interface.Instance | None = None
|
|
155
|
+
if data.get("state") == "absent":
|
|
156
|
+
op = "update"
|
|
157
|
+
else:
|
|
158
|
+
try:
|
|
159
|
+
actual = await instances.get((name, version), settings=_site.SETTINGS)
|
|
160
|
+
except exceptions.InstanceNotFound:
|
|
161
|
+
pass
|
|
162
|
+
else:
|
|
163
|
+
op = "update"
|
|
164
|
+
with validation_context(operation=op, settings=_site.SETTINGS, instance=actual):
|
|
156
165
|
instance = _site.INSTANCE_MODEL.model_validate(data)
|
|
157
166
|
if dry_run:
|
|
158
167
|
ret = interface.InstanceApplyResult(change_state=None)
|
|
@@ -196,7 +205,9 @@ async def get(instance: system.Instance, output_format: OutputFormat | None) ->
|
|
|
196
205
|
}
|
|
197
206
|
if not instance.postgresql.standby:
|
|
198
207
|
exclude.add("standby")
|
|
199
|
-
print_table_for(
|
|
208
|
+
print_table_for(
|
|
209
|
+
[i], partial(model_dump, exclude=exclude, mode="pretty"), box=None
|
|
210
|
+
)
|
|
200
211
|
|
|
201
212
|
|
|
202
213
|
@cli.command("list")
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
7
|
import logging
|
|
8
|
-
import pathlib
|
|
9
8
|
import sys
|
|
10
9
|
import warnings
|
|
11
10
|
from functools import partial
|
|
12
11
|
from importlib.metadata import version
|
|
12
|
+
from pathlib import Path
|
|
13
13
|
from typing import Literal
|
|
14
14
|
|
|
15
15
|
import click
|
|
@@ -94,7 +94,7 @@ def log_level(
|
|
|
94
94
|
@click.option(
|
|
95
95
|
"-l",
|
|
96
96
|
"--log-file",
|
|
97
|
-
type=click.Path(dir_okay=False, resolve_path=True, path_type=
|
|
97
|
+
type=click.Path(dir_okay=False, resolve_path=True, path_type=Path),
|
|
98
98
|
metavar="LOGFILE",
|
|
99
99
|
help="Write logs to LOGFILE, instead of stderr.",
|
|
100
100
|
)
|
|
@@ -133,7 +133,7 @@ def log_level(
|
|
|
133
133
|
def cli(
|
|
134
134
|
context: click.Context,
|
|
135
135
|
log_level: int | None,
|
|
136
|
-
log_file:
|
|
136
|
+
log_file: Path | None,
|
|
137
137
|
debug: bool,
|
|
138
138
|
interactive: bool,
|
|
139
139
|
) -> None:
|
|
@@ -243,7 +243,7 @@ def site_settings(
|
|
|
243
243
|
@click.option(
|
|
244
244
|
"--settings",
|
|
245
245
|
"settings_file",
|
|
246
|
-
type=click.Path(exists=True, path_type=
|
|
246
|
+
type=click.Path(exists=True, path_type=Path),
|
|
247
247
|
help="Custom settings file.",
|
|
248
248
|
)
|
|
249
249
|
@click.argument(
|
|
@@ -256,7 +256,7 @@ async def site_configure(
|
|
|
256
256
|
context: click.Context,
|
|
257
257
|
obj: Obj,
|
|
258
258
|
action: Literal["install", "uninstall", "check"],
|
|
259
|
-
settings_file:
|
|
259
|
+
settings_file: Path | None,
|
|
260
260
|
) -> None:
|
|
261
261
|
"""Manage installation of extra data files for pglift.
|
|
262
262
|
|
|
@@ -105,10 +105,10 @@ async def set_(obj: Obj, instance: system.Instance, parameters: dict[str, Any])
|
|
|
105
105
|
status = await postgresql.status(pg_instance)
|
|
106
106
|
manifest = await instances._get(instance, status)
|
|
107
107
|
manifest.settings.update(parameters)
|
|
108
|
-
|
|
108
|
+
r = await instances.configure(
|
|
109
109
|
pg_instance, manifest, _is_running=status == Status.running
|
|
110
110
|
)
|
|
111
|
-
show_configuration_changes(changes, parameters.keys())
|
|
111
|
+
show_configuration_changes(r.changes, parameters.keys())
|
|
112
112
|
|
|
113
113
|
|
|
114
114
|
@cli.command("remove")
|
|
@@ -129,10 +129,10 @@ async def remove(obj: Obj, instance: system.Instance, parameters: tuple[str]) ->
|
|
|
129
129
|
raise click.ClickException(
|
|
130
130
|
f"{p!r} not found in managed configuration"
|
|
131
131
|
) from None
|
|
132
|
-
|
|
132
|
+
r = await instances.configure(
|
|
133
133
|
pg_instance, manifest, _is_running=status == Status.running
|
|
134
134
|
)
|
|
135
|
-
show_configuration_changes(changes, parameters)
|
|
135
|
+
show_configuration_changes(r.changes, parameters)
|
|
136
136
|
|
|
137
137
|
|
|
138
138
|
@cli.command("edit")
|
|
@@ -156,7 +156,7 @@ async def edit(obj: Obj, instance: system.Instance) -> None:
|
|
|
156
156
|
manifest = await instances._get(instance, status)
|
|
157
157
|
manifest.settings.clear()
|
|
158
158
|
manifest.settings.update(values)
|
|
159
|
-
|
|
159
|
+
r = await instances.configure(
|
|
160
160
|
pg_instance, manifest, _is_running=status == Status.running
|
|
161
161
|
)
|
|
162
|
-
show_configuration_changes(changes)
|
|
162
|
+
show_configuration_changes(r.changes)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2024 Dalibo
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
from pglift import hba
|
|
12
|
+
from pglift.models import interface, system
|
|
13
|
+
|
|
14
|
+
from . import model
|
|
15
|
+
from .util import (
|
|
16
|
+
Group,
|
|
17
|
+
Obj,
|
|
18
|
+
audit,
|
|
19
|
+
instance_identifier_option,
|
|
20
|
+
pass_postgresql_instance,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@click.group(cls=Group)
|
|
25
|
+
@instance_identifier_option
|
|
26
|
+
def cli(**kwargs: Any) -> None:
|
|
27
|
+
"""Manage entries in the pg_hba.conf file of a PostgreSQL instance."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@cli.command("add")
|
|
31
|
+
@model.as_parameters(interface.HbaRecord, "create")
|
|
32
|
+
@pass_postgresql_instance
|
|
33
|
+
@click.pass_obj
|
|
34
|
+
def add(
|
|
35
|
+
obj: Obj, instance: system.PostgreSQLInstance, hbarecord: interface.HbaRecord
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Add a record in pg_hba.conf.
|
|
38
|
+
|
|
39
|
+
If no --connection-* option is specified, a 'local' record is added.
|
|
40
|
+
"""
|
|
41
|
+
with obj.lock, audit():
|
|
42
|
+
hba.add(instance, hbarecord)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@cli.command("remove")
|
|
46
|
+
@model.as_parameters(interface.HbaRecord, "create")
|
|
47
|
+
@pass_postgresql_instance
|
|
48
|
+
@click.pass_obj
|
|
49
|
+
def remove(
|
|
50
|
+
obj: Obj, instance: system.PostgreSQLInstance, hbarecord: interface.HbaRecord
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Remove a record from pg_hba.conf.
|
|
53
|
+
|
|
54
|
+
If no --connection-* option is specified, a 'local' record is removed.
|
|
55
|
+
"""
|
|
56
|
+
with obj.lock, audit():
|
|
57
|
+
hba.remove(instance, hbarecord)
|
|
@@ -125,7 +125,12 @@ async def apply(
|
|
|
125
125
|
) -> None:
|
|
126
126
|
"""Apply manifest as a role"""
|
|
127
127
|
op: Operation = (
|
|
128
|
-
"update"
|
|
128
|
+
"update"
|
|
129
|
+
if (
|
|
130
|
+
data.get("state") == "absent"
|
|
131
|
+
or await roles.exists(instance, name=data["name"])
|
|
132
|
+
)
|
|
133
|
+
else "create"
|
|
129
134
|
)
|
|
130
135
|
with validation_context(operation=op, settings=_site.SETTINGS):
|
|
131
136
|
role = _site.ROLE_MODEL.model_validate(data)
|
|
@@ -152,7 +157,14 @@ async def ls(
|
|
|
152
157
|
if output_format == "json":
|
|
153
158
|
print_json_for([model_dump(r) for r in rls])
|
|
154
159
|
else:
|
|
155
|
-
print_table_for(
|
|
160
|
+
print_table_for(
|
|
161
|
+
rls,
|
|
162
|
+
partial(
|
|
163
|
+
model_dump,
|
|
164
|
+
mode="pretty",
|
|
165
|
+
exclude={"in_roles", "hba_records", "validity"},
|
|
166
|
+
),
|
|
167
|
+
)
|
|
156
168
|
|
|
157
169
|
|
|
158
170
|
@cli.command("get")
|
|
@@ -170,7 +182,13 @@ async def get(
|
|
|
170
182
|
print_json_for(model_dump(r))
|
|
171
183
|
else:
|
|
172
184
|
print_table_for(
|
|
173
|
-
[r],
|
|
185
|
+
[r],
|
|
186
|
+
partial(
|
|
187
|
+
model_dump,
|
|
188
|
+
mode="pretty",
|
|
189
|
+
exclude={"in_roles", "hba_records", "validity"},
|
|
190
|
+
),
|
|
191
|
+
box=None,
|
|
174
192
|
)
|
|
175
193
|
|
|
176
194
|
|
|
@@ -10,7 +10,6 @@ import json
|
|
|
10
10
|
import logging
|
|
11
11
|
import logging.handlers
|
|
12
12
|
import os
|
|
13
|
-
import pathlib
|
|
14
13
|
import shlex
|
|
15
14
|
import sys
|
|
16
15
|
import tempfile
|
|
@@ -20,6 +19,7 @@ from collections.abc import Coroutine, Iterable, Iterator, Sequence
|
|
|
20
19
|
from contextlib import contextmanager
|
|
21
20
|
from datetime import timedelta
|
|
22
21
|
from functools import cache, cached_property, partial, singledispatch, wraps
|
|
22
|
+
from pathlib import Path
|
|
23
23
|
from typing import IO, Any, Callable, Literal, TypedDict, TypeVar
|
|
24
24
|
|
|
25
25
|
import click
|
|
@@ -442,10 +442,19 @@ class ManifestData(TypedDict, total=False):
|
|
|
442
442
|
OutputFormat = Literal["json"]
|
|
443
443
|
|
|
444
444
|
|
|
445
|
+
def set_output_format(
|
|
446
|
+
ctx: click.Context, param: click.Parameter, value: OutputFormat
|
|
447
|
+
) -> OutputFormat:
|
|
448
|
+
assert ctx.obj.output_format is None
|
|
449
|
+
ctx.obj.output_format = value
|
|
450
|
+
return value
|
|
451
|
+
|
|
452
|
+
|
|
445
453
|
output_format_option = click.option(
|
|
446
454
|
"-o",
|
|
447
455
|
"--output-format",
|
|
448
456
|
type=click.Choice(typing.get_args(OutputFormat), case_sensitive=False),
|
|
457
|
+
callback=set_output_format,
|
|
449
458
|
help="Specify the output format.",
|
|
450
459
|
)
|
|
451
460
|
|
|
@@ -537,7 +546,7 @@ def audit(
|
|
|
537
546
|
|
|
538
547
|
|
|
539
548
|
@contextmanager
|
|
540
|
-
def command_logging(logdir:
|
|
549
|
+
def command_logging(logdir: Path | None) -> Iterator[None]:
|
|
541
550
|
logdir_created = False
|
|
542
551
|
logfilename = f"{time.time()}.log"
|
|
543
552
|
if logdir is not None: # pragma: nocover (DEPRECATED)
|
|
@@ -549,13 +558,13 @@ def command_logging(logdir: pathlib.Path | None) -> Iterator[None]:
|
|
|
549
558
|
logdir_created = True
|
|
550
559
|
except OSError:
|
|
551
560
|
# Might be, e.g. PermissionError, if log file path is not writable.
|
|
552
|
-
logfile =
|
|
561
|
+
logfile = Path(
|
|
553
562
|
tempfile.NamedTemporaryFile(
|
|
554
563
|
prefix="pglift-", suffix="-" + logfilename
|
|
555
564
|
).name
|
|
556
565
|
)
|
|
557
566
|
else:
|
|
558
|
-
logfile =
|
|
567
|
+
logfile = Path(
|
|
559
568
|
tempfile.NamedTemporaryFile(prefix="pglift-", suffix="-" + logfilename).name
|
|
560
569
|
)
|
|
561
570
|
handler = logging.FileHandler(logfile)
|
|
@@ -614,8 +623,11 @@ class Obj:
|
|
|
614
623
|
# instance_identifier_option decorator's callback.
|
|
615
624
|
_instance: str | system.PostgreSQLInstance
|
|
616
625
|
|
|
617
|
-
def __init__(
|
|
626
|
+
def __init__(
|
|
627
|
+
self, *, debug: bool = False, output_format: OutputFormat | None = None
|
|
628
|
+
) -> None:
|
|
618
629
|
self.debug = debug
|
|
630
|
+
self.output_format = output_format
|
|
619
631
|
|
|
620
632
|
@cached_property
|
|
621
633
|
def lock(self) -> filelock.FileLock:
|
|
@@ -660,6 +672,8 @@ class Command(click.Command):
|
|
|
660
672
|
raise click.Abort from None
|
|
661
673
|
except pydantic.ValidationError as e:
|
|
662
674
|
logger.debug("a validation error occurred", exc_info=obj.debug)
|
|
675
|
+
if context.obj.output_format == "json":
|
|
676
|
+
console.print_json(e.json(include_url=False, include_context=False))
|
|
663
677
|
raise click.ClickException(str(e)) from None
|
|
664
678
|
except exceptions.Error as e:
|
|
665
679
|
logger.debug("an internal error occurred", exc_info=obj.debug)
|
|
@@ -680,6 +694,10 @@ class Command(click.Command):
|
|
|
680
694
|
raise click.ClickException(str(e).strip()) from None
|
|
681
695
|
|
|
682
696
|
|
|
697
|
+
def is_root() -> bool:
|
|
698
|
+
return os.getuid() == 0
|
|
699
|
+
|
|
700
|
+
|
|
683
701
|
class Group(click.Group):
|
|
684
702
|
command_class = Command
|
|
685
703
|
group_class = type
|
|
@@ -690,6 +708,8 @@ class Group(click.Group):
|
|
|
690
708
|
super().add_command(command, name)
|
|
691
709
|
|
|
692
710
|
def invoke(self, ctx: click.Context) -> Any:
|
|
711
|
+
if is_root():
|
|
712
|
+
raise click.ClickException("pglift cannot be used as root")
|
|
693
713
|
if not install.check(_site.SETTINGS):
|
|
694
714
|
raise click.ClickException(
|
|
695
715
|
"broken installation; did you run 'site-configure install'?",
|
|
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
|