pglift-cli 1.9.0__tar.gz → 2.0.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.9.0 → pglift_cli-2.0.0}/PKG-INFO +6 -9
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/pyproject.toml +3 -5
- pglift_cli-2.0.0/pytest.ini +10 -0
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/_settings.py +14 -20
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/database.py +23 -18
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/instance.py +93 -64
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/main.py +20 -27
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/model.py +6 -7
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/patroni.py +3 -3
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/pgbackrest/__init__.py +6 -7
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/pgbackrest/repo_path.py +3 -3
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/pgconf.py +30 -19
- pglift_cli-2.0.0/src/pglift_cli/pghba.py +93 -0
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/postgres.py +9 -8
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/prometheus.py +14 -4
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/role.py +40 -40
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/util.py +101 -77
- pglift_cli-2.0.0/tests/expect/.gitignore +5 -0
- pglift_cli-2.0.0/tests/expect/test-base.t +33 -0
- pglift_cli-2.0.0/tests/expect/test-cli-walkthrough.t +1135 -0
- pglift_cli-2.0.0/tests/expect/test-help.t +999 -0
- pglift_cli-2.0.0/tests/expect/test-port-validation.t +153 -0
- pglift_cli-2.0.0/tests/expect/test-prometheus.t +103 -0
- pglift_cli-2.0.0/tests/expect/test-standby-pgbackrest.t +371 -0
- pglift_cli-2.0.0/tests/expect/test-upgrade.t +74 -0
- pglift_cli-2.0.0/tests/unit/__init__.py +15 -0
- pglift_cli-2.0.0/tests/unit/conftest.py +29 -0
- pglift_cli-2.0.0/tests/unit/test__site.py +45 -0
- pglift_cli-2.0.0/tests/unit/test_audit.py +58 -0
- pglift_cli-2.0.0/tests/unit/test_cli.py +581 -0
- pglift_cli-2.0.0/tests/unit/test_main.py +87 -0
- pglift_cli-2.0.0/tests/unit/test_model.py +357 -0
- pglift_cli-2.0.0/tests/unit/test_pm.py +62 -0
- pglift_cli-2.0.0/tests/unit/test_util.py +372 -0
- pglift_cli-1.9.0/src/pglift_cli/pghba.py +0 -57
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/.gitignore +0 -0
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/README.md +0 -0
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/hatch.toml +0 -0
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/__init__.py +0 -0
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/__main__.py +0 -0
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/_site.py +0 -0
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/base.py +0 -0
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/console.py +0 -0
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/hookspecs.py +0 -0
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/pm.py +0 -0
- {pglift_cli-1.9.0 → pglift_cli-2.0.0}/src/pglift_cli/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pglift_cli
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.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/
|
|
@@ -15,7 +15,6 @@ Classifier: Intended Audience :: System Administrators
|
|
|
15
15
|
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
17
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -23,18 +22,17 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
23
22
|
Classifier: Topic :: Database
|
|
24
23
|
Classifier: Topic :: System :: Systems Administration
|
|
25
24
|
Classifier: Typing :: Typed
|
|
26
|
-
Requires-Python: <4,>=3.
|
|
25
|
+
Requires-Python: <4,>=3.10
|
|
27
26
|
Requires-Dist: click!=8.1.0,!=8.1.4,>=8.0.0
|
|
28
27
|
Requires-Dist: filelock!=3.12.1,>=3.9.0
|
|
29
28
|
Requires-Dist: pluggy
|
|
30
29
|
Requires-Dist: psycopg>=3.1
|
|
31
|
-
Requires-Dist: pydantic
|
|
30
|
+
Requires-Dist: pydantic>=2.10.2
|
|
32
31
|
Requires-Dist: pyyaml>=6.0.1
|
|
33
32
|
Requires-Dist: rich!=13.9.0,>=11.0.0
|
|
34
33
|
Provides-Extra: dev
|
|
35
34
|
Requires-Dist: anyio; extra == 'dev'
|
|
36
|
-
Requires-Dist: mypy
|
|
37
|
-
Requires-Dist: mypy>=1.10.0; (python_version >= '3.10') and extra == 'dev'
|
|
35
|
+
Requires-Dist: mypy>=1.10.0; extra == 'dev'
|
|
38
36
|
Requires-Dist: patroni[etcd]>=2.1.5; extra == 'dev'
|
|
39
37
|
Requires-Dist: port-for; extra == 'dev'
|
|
40
38
|
Requires-Dist: prysk[pytest-plugin]>=0.14.0; extra == 'dev'
|
|
@@ -51,8 +49,7 @@ Requires-Dist: pytest-cov; extra == 'test'
|
|
|
51
49
|
Requires-Dist: pytest>=8; extra == 'test'
|
|
52
50
|
Requires-Dist: trustme; extra == 'test'
|
|
53
51
|
Provides-Extra: typing
|
|
54
|
-
Requires-Dist: mypy
|
|
55
|
-
Requires-Dist: mypy>=1.10.0; (python_version >= '3.10') and extra == 'typing'
|
|
52
|
+
Requires-Dist: mypy>=1.10.0; extra == 'typing'
|
|
56
53
|
Requires-Dist: types-pyyaml>=6.0.12.10; extra == 'typing'
|
|
57
54
|
Description-Content-Type: text/markdown
|
|
58
55
|
|
|
@@ -10,7 +10,7 @@ build-backend = "hatchling.build"
|
|
|
10
10
|
name = "pglift_cli"
|
|
11
11
|
description = "Command-line interface for pglift"
|
|
12
12
|
readme = "README.md"
|
|
13
|
-
requires-python = ">=3.
|
|
13
|
+
requires-python = ">=3.10, <4"
|
|
14
14
|
license = { text = "GPLv3" }
|
|
15
15
|
authors = [{ name = "Dalibo SCOP", email = "contact@dalibo.com" }]
|
|
16
16
|
keywords = [
|
|
@@ -28,7 +28,6 @@ classifiers = [
|
|
|
28
28
|
"Topic :: Database",
|
|
29
29
|
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
|
30
30
|
"Programming Language :: Python :: 3",
|
|
31
|
-
"Programming Language :: Python :: 3.9",
|
|
32
31
|
"Programming Language :: Python :: 3.10",
|
|
33
32
|
"Programming Language :: Python :: 3.11",
|
|
34
33
|
"Programming Language :: Python :: 3.12",
|
|
@@ -44,7 +43,7 @@ dependencies = [
|
|
|
44
43
|
"filelock >= 3.9.0, != 3.12.1",
|
|
45
44
|
"pluggy",
|
|
46
45
|
"psycopg >= 3.1",
|
|
47
|
-
"pydantic >= 2.
|
|
46
|
+
"pydantic >= 2.10.2",
|
|
48
47
|
"rich >= 11.0.0, != 13.9.0",
|
|
49
48
|
]
|
|
50
49
|
|
|
@@ -59,8 +58,7 @@ test = [
|
|
|
59
58
|
"trustme",
|
|
60
59
|
]
|
|
61
60
|
typing = [
|
|
62
|
-
"mypy >= 1.10.0
|
|
63
|
-
"mypy >= 1.10.0, != 1.11.*, != 1.12.* ; python_version < '3.10'",
|
|
61
|
+
"mypy >= 1.10.0",
|
|
64
62
|
"types-PyYAML >= 6.0.12.10",
|
|
65
63
|
]
|
|
66
64
|
dev = [
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Dalibo
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
[pytest]
|
|
6
|
+
addopts = --strict-config --strict-markers
|
|
7
|
+
filterwarnings =
|
|
8
|
+
error
|
|
9
|
+
ignore:cannot guess 'postgresql.versions' setting as 'bindir' is unset:RuntimeWarning
|
|
10
|
+
testpaths = tests/unit
|
|
@@ -2,23 +2,15 @@
|
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
4
|
|
|
5
|
-
import warnings
|
|
6
5
|
from pathlib import Path
|
|
7
|
-
from typing import Annotated, Any
|
|
6
|
+
from typing import Annotated, Any
|
|
8
7
|
|
|
9
|
-
from pydantic import
|
|
8
|
+
from pydantic import BeforeValidator, Field
|
|
10
9
|
|
|
11
10
|
from pglift.settings import Settings as BaseSettings
|
|
12
11
|
from pglift.settings import SiteSettings as BaseSiteSettings
|
|
13
12
|
from pglift.settings.base import BaseModel, LogPath, RunPath
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def deprecated(value: Any, info: ValidationInfo) -> Any:
|
|
17
|
-
if value is not None:
|
|
18
|
-
warnings.warn(
|
|
19
|
-
f"{info.field_name!r} setting is deprecated", FutureWarning, stacklevel=2
|
|
20
|
-
)
|
|
21
|
-
return value
|
|
13
|
+
from pglift.types import LogLevel
|
|
22
14
|
|
|
23
15
|
|
|
24
16
|
class AuditSettings(BaseModel):
|
|
@@ -38,21 +30,23 @@ class AuditSettings(BaseModel):
|
|
|
38
30
|
] = "%Y-%m-%d %H:%M:%S"
|
|
39
31
|
|
|
40
32
|
|
|
33
|
+
def _upper(value: Any) -> Any:
|
|
34
|
+
if isinstance(value, str):
|
|
35
|
+
return value.upper()
|
|
36
|
+
return value
|
|
37
|
+
|
|
38
|
+
|
|
41
39
|
class CLISettings(BaseModel):
|
|
42
40
|
"""Settings for pglift's command-line interface."""
|
|
43
41
|
|
|
44
|
-
logpath: Annotated[
|
|
45
|
-
Annotated[Optional[Path], LogPath],
|
|
46
|
-
Field(
|
|
47
|
-
description="Directory where temporary debug files from command executions will be stored (DEPRECATED).",
|
|
48
|
-
),
|
|
49
|
-
AfterValidator(deprecated),
|
|
50
|
-
] = None
|
|
51
|
-
|
|
52
42
|
log_format: Annotated[
|
|
53
43
|
str, Field(description="Format for log messages when written to a file")
|
|
54
44
|
] = "%(asctime)s %(levelname)-8s %(name)s - %(message)s"
|
|
55
45
|
|
|
46
|
+
log_level: Annotated[
|
|
47
|
+
LogLevel | None, Field(description="Log level"), BeforeValidator(_upper)
|
|
48
|
+
] = None
|
|
49
|
+
|
|
56
50
|
date_format: Annotated[
|
|
57
51
|
str, Field(description="Date format in log messages when written to a file")
|
|
58
52
|
] = "%Y-%m-%d %H:%M:%S"
|
|
@@ -62,7 +56,7 @@ class CLISettings(BaseModel):
|
|
|
62
56
|
] = Path(".pglift.lock")
|
|
63
57
|
|
|
64
58
|
audit: Annotated[
|
|
65
|
-
|
|
59
|
+
AuditSettings | None,
|
|
66
60
|
Field(description="Settings for change operations auditing"),
|
|
67
61
|
] = None
|
|
68
62
|
|
|
@@ -14,8 +14,8 @@ import psycopg
|
|
|
14
14
|
from attrs import asdict
|
|
15
15
|
from pydantic.v1.utils import deep_update
|
|
16
16
|
|
|
17
|
-
from pglift import databases, postgresql, privileges, task
|
|
18
|
-
from pglift.models import
|
|
17
|
+
from pglift import databases, diff, postgresql, privileges, task
|
|
18
|
+
from pglift.models import PostgreSQLInstance, interface
|
|
19
19
|
|
|
20
20
|
from . import model
|
|
21
21
|
from .util import (
|
|
@@ -25,6 +25,7 @@ from .util import (
|
|
|
25
25
|
OutputFormat,
|
|
26
26
|
async_command,
|
|
27
27
|
audit,
|
|
28
|
+
diff_options,
|
|
28
29
|
dry_run_option,
|
|
29
30
|
instance_identifier_option,
|
|
30
31
|
manifest_option,
|
|
@@ -33,6 +34,7 @@ from .util import (
|
|
|
33
34
|
pass_postgresql_instance,
|
|
34
35
|
print_argspec,
|
|
35
36
|
print_json_for,
|
|
37
|
+
print_result_diff,
|
|
36
38
|
print_schema,
|
|
37
39
|
print_table_for,
|
|
38
40
|
)
|
|
@@ -67,7 +69,7 @@ def cli(**kwargs: Any) -> None:
|
|
|
67
69
|
@click.pass_obj
|
|
68
70
|
@async_command
|
|
69
71
|
async def create(
|
|
70
|
-
obj: Obj, instance:
|
|
72
|
+
obj: Obj, instance: PostgreSQLInstance, database: interface.Database
|
|
71
73
|
) -> None:
|
|
72
74
|
"""Create a database in a PostgreSQL instance"""
|
|
73
75
|
with obj.lock, audit():
|
|
@@ -78,14 +80,14 @@ async def create(
|
|
|
78
80
|
await databases.apply(instance, database)
|
|
79
81
|
|
|
80
82
|
|
|
81
|
-
@cli.command("alter")
|
|
83
|
+
@cli.command("alter")
|
|
82
84
|
@model.as_parameters(interface.Database, "update")
|
|
83
85
|
@click.argument("dbname")
|
|
84
86
|
@pass_postgresql_instance
|
|
85
|
-
@click.pass_obj
|
|
87
|
+
@click.pass_obj # type: ignore[arg-type]
|
|
86
88
|
@async_command
|
|
87
89
|
async def alter(
|
|
88
|
-
obj: Obj, instance:
|
|
90
|
+
obj: Obj, instance: PostgreSQLInstance, dbname: str, **changes: Any
|
|
89
91
|
) -> None:
|
|
90
92
|
"""Alter a database in a PostgreSQL instance"""
|
|
91
93
|
with obj.lock, audit():
|
|
@@ -99,27 +101,32 @@ async def alter(
|
|
|
99
101
|
@cli.command("apply", hidden=True)
|
|
100
102
|
@manifest_option
|
|
101
103
|
@output_format_option
|
|
104
|
+
@diff_options["unified"]
|
|
105
|
+
@diff_options["ansible"]
|
|
102
106
|
@dry_run_option
|
|
103
107
|
@pass_postgresql_instance
|
|
104
108
|
@click.pass_obj
|
|
105
109
|
@async_command
|
|
106
110
|
async def apply(
|
|
107
111
|
obj: Obj,
|
|
108
|
-
instance:
|
|
112
|
+
instance: PostgreSQLInstance,
|
|
109
113
|
data: ManifestData,
|
|
110
114
|
output_format: OutputFormat | None,
|
|
111
115
|
dry_run: bool,
|
|
116
|
+
diff_format: diff.Format | None,
|
|
112
117
|
) -> None:
|
|
113
118
|
"""Apply manifest as a database"""
|
|
114
119
|
database = interface.Database.model_validate(data)
|
|
115
120
|
if dry_run:
|
|
116
121
|
ret = interface.ApplyResult(change_state=None)
|
|
117
122
|
else:
|
|
118
|
-
with obj.lock, audit():
|
|
123
|
+
with obj.lock, audit(dry_run=dry_run), diff.enabled(diff_format):
|
|
119
124
|
async with postgresql.running(instance):
|
|
120
125
|
ret = await databases.apply(instance, database)
|
|
121
126
|
if output_format == "json":
|
|
122
127
|
print_json_for(ret)
|
|
128
|
+
else:
|
|
129
|
+
print_result_diff(ret)
|
|
123
130
|
|
|
124
131
|
|
|
125
132
|
@cli.command("get")
|
|
@@ -128,7 +135,7 @@ async def apply(
|
|
|
128
135
|
@pass_postgresql_instance
|
|
129
136
|
@async_command
|
|
130
137
|
async def get(
|
|
131
|
-
instance:
|
|
138
|
+
instance: PostgreSQLInstance, name: str, output_format: OutputFormat | None
|
|
132
139
|
) -> None:
|
|
133
140
|
"""Get the description of a database"""
|
|
134
141
|
async with postgresql.running(instance):
|
|
@@ -152,7 +159,7 @@ async def get(
|
|
|
152
159
|
@pass_postgresql_instance
|
|
153
160
|
@async_command
|
|
154
161
|
async def ls(
|
|
155
|
-
instance:
|
|
162
|
+
instance: PostgreSQLInstance,
|
|
156
163
|
dbname: Sequence[str],
|
|
157
164
|
exclude_dbnames: Sequence[str],
|
|
158
165
|
output_format: OutputFormat | None,
|
|
@@ -179,7 +186,7 @@ async def ls(
|
|
|
179
186
|
@async_command
|
|
180
187
|
async def drop(
|
|
181
188
|
obj: Obj,
|
|
182
|
-
instance:
|
|
189
|
+
instance: PostgreSQLInstance,
|
|
183
190
|
databasedropped: interface.DatabaseDropped,
|
|
184
191
|
) -> None:
|
|
185
192
|
"""Drop a database"""
|
|
@@ -196,7 +203,7 @@ async def drop(
|
|
|
196
203
|
@pass_postgresql_instance
|
|
197
204
|
@async_command
|
|
198
205
|
async def list_privileges(
|
|
199
|
-
instance:
|
|
206
|
+
instance: PostgreSQLInstance,
|
|
200
207
|
name: str,
|
|
201
208
|
roles: Sequence[str],
|
|
202
209
|
defaults: bool,
|
|
@@ -233,7 +240,7 @@ async def list_privileges(
|
|
|
233
240
|
@pass_postgresql_instance
|
|
234
241
|
@async_command
|
|
235
242
|
async def run(
|
|
236
|
-
instance:
|
|
243
|
+
instance: PostgreSQLInstance,
|
|
237
244
|
sql_command: str,
|
|
238
245
|
dbnames: Sequence[str],
|
|
239
246
|
exclude_dbnames: Sequence[str],
|
|
@@ -268,9 +275,7 @@ async def run(
|
|
|
268
275
|
@click.argument("dbname")
|
|
269
276
|
@pass_postgresql_instance
|
|
270
277
|
@async_command
|
|
271
|
-
async def dump(
|
|
272
|
-
instance: system.PostgreSQLInstance, dbname: str, output: Path | None
|
|
273
|
-
) -> None:
|
|
278
|
+
async def dump(instance: PostgreSQLInstance, dbname: str, output: Path | None) -> None:
|
|
274
279
|
"""Dump a database"""
|
|
275
280
|
async with postgresql.running(instance):
|
|
276
281
|
await databases.dump(instance, dbname, output)
|
|
@@ -282,7 +287,7 @@ async def dump(
|
|
|
282
287
|
@pass_postgresql_instance
|
|
283
288
|
@async_command
|
|
284
289
|
async def dumps(
|
|
285
|
-
instance:
|
|
290
|
+
instance: PostgreSQLInstance,
|
|
286
291
|
dbname: Sequence[str],
|
|
287
292
|
output_format: OutputFormat | None,
|
|
288
293
|
) -> None:
|
|
@@ -304,7 +309,7 @@ async def dumps(
|
|
|
304
309
|
@pass_postgresql_instance
|
|
305
310
|
@async_command
|
|
306
311
|
async def restore(
|
|
307
|
-
instance:
|
|
312
|
+
instance: PostgreSQLInstance, dump_id: str, targetdbname: str | None
|
|
308
313
|
) -> None:
|
|
309
314
|
"""Restore a database dump
|
|
310
315
|
|