pglift-cli 2.0.0__tar.gz → 2.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.
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/PKG-INFO +2 -2
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/pyproject.toml +4 -1
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/database.py +37 -17
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/instance.py +47 -9
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/model.py +2 -22
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/pgbackrest/__init__.py +4 -4
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/role.py +24 -15
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/util.py +22 -14
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/tests/expect/test-cli-walkthrough.t +119 -7
- pglift_cli-2.2.0/tests/expect/test-demote.t +231 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/tests/expect/test-help.t +97 -36
- pglift_cli-2.2.0/tests/expect/test-transactions.t +70 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/tests/unit/conftest.py +1 -1
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/tests/unit/test__site.py +9 -13
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/tests/unit/test_cli.py +21 -18
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/tests/unit/test_main.py +9 -11
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/tests/unit/test_model.py +1 -11
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/tests/unit/test_util.py +2 -2
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/.gitignore +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/README.md +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/hatch.toml +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/pytest.ini +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/__init__.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/__main__.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/_settings.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/_site.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/base.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/console.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/hookspecs.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/main.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/patroni.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/pgbackrest/repo_path.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/pgconf.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/pghba.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/pm.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/postgres.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/prometheus.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/src/pglift_cli/py.typed +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/tests/expect/.gitignore +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/tests/expect/test-base.t +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/tests/expect/test-port-validation.t +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/tests/expect/test-prometheus.t +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/tests/expect/test-standby-pgbackrest.t +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/tests/expect/test-upgrade.t +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/tests/unit/__init__.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/tests/unit/test_audit.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.2.0}/tests/unit/test_pm.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pglift_cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.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/
|
|
@@ -23,7 +23,7 @@ Classifier: Topic :: Database
|
|
|
23
23
|
Classifier: Topic :: System :: Systems Administration
|
|
24
24
|
Classifier: Typing :: Typed
|
|
25
25
|
Requires-Python: <4,>=3.10
|
|
26
|
-
Requires-Dist: click
|
|
26
|
+
Requires-Dist: click>=8.2.0
|
|
27
27
|
Requires-Dist: filelock!=3.12.1,>=3.9.0
|
|
28
28
|
Requires-Dist: pluggy
|
|
29
29
|
Requires-Dist: psycopg>=3.1
|
|
@@ -39,7 +39,7 @@ dynamic = ["version"]
|
|
|
39
39
|
|
|
40
40
|
dependencies = [
|
|
41
41
|
"PyYAML >= 6.0.1",
|
|
42
|
-
"click >= 8.
|
|
42
|
+
"click >= 8.2.0",
|
|
43
43
|
"filelock >= 3.9.0, != 3.12.1",
|
|
44
44
|
"pluggy",
|
|
45
45
|
"psycopg >= 3.1",
|
|
@@ -75,3 +75,6 @@ Tracker = "https://gitlab.com/dalibo/pglift/-/issues/"
|
|
|
75
75
|
"pglift_cli.pgbackrest" = "pglift_cli.pgbackrest"
|
|
76
76
|
"pglift_cli.pgbackrest.repo_path" = "pglift_cli.pgbackrest.repo_path"
|
|
77
77
|
"pglift_cli.prometheus" = "pglift_cli.prometheus"
|
|
78
|
+
|
|
79
|
+
[project.scripts]
|
|
80
|
+
pglift = "pglift_cli.main:cli"
|
|
@@ -37,6 +37,7 @@ from .util import (
|
|
|
37
37
|
print_result_diff,
|
|
38
38
|
print_schema,
|
|
39
39
|
print_table_for,
|
|
40
|
+
system_configure,
|
|
40
41
|
)
|
|
41
42
|
|
|
42
43
|
|
|
@@ -65,14 +66,18 @@ def cli(**kwargs: Any) -> None:
|
|
|
65
66
|
|
|
66
67
|
@cli.command("create")
|
|
67
68
|
@model.as_parameters(interface.Database, "create")
|
|
69
|
+
@dry_run_option
|
|
68
70
|
@pass_postgresql_instance
|
|
69
71
|
@click.pass_obj
|
|
70
72
|
@async_command
|
|
71
73
|
async def create(
|
|
72
|
-
obj: Obj,
|
|
74
|
+
obj: Obj,
|
|
75
|
+
instance: PostgreSQLInstance,
|
|
76
|
+
database: interface.Database,
|
|
77
|
+
dry_run: bool,
|
|
73
78
|
) -> None:
|
|
74
79
|
"""Create a database in a PostgreSQL instance"""
|
|
75
|
-
with obj.lock, audit():
|
|
80
|
+
with obj.lock, audit(dry_run=dry_run), system_configure(dry_run=dry_run):
|
|
76
81
|
async with postgresql.running(instance):
|
|
77
82
|
if await databases.exists(instance, database.name):
|
|
78
83
|
raise click.ClickException("database already exists")
|
|
@@ -83,14 +88,19 @@ async def create(
|
|
|
83
88
|
@cli.command("alter")
|
|
84
89
|
@model.as_parameters(interface.Database, "update")
|
|
85
90
|
@click.argument("dbname")
|
|
91
|
+
@dry_run_option
|
|
86
92
|
@pass_postgresql_instance
|
|
87
93
|
@click.pass_obj # type: ignore[arg-type]
|
|
88
94
|
@async_command
|
|
89
95
|
async def alter(
|
|
90
|
-
obj: Obj,
|
|
96
|
+
obj: Obj,
|
|
97
|
+
instance: PostgreSQLInstance,
|
|
98
|
+
dbname: str,
|
|
99
|
+
dry_run: bool,
|
|
100
|
+
**changes: Any,
|
|
91
101
|
) -> None:
|
|
92
102
|
"""Alter a database in a PostgreSQL instance"""
|
|
93
|
-
with obj.lock, audit():
|
|
103
|
+
with obj.lock, audit(dry_run=dry_run), system_configure(dry_run=dry_run):
|
|
94
104
|
async with postgresql.running(instance):
|
|
95
105
|
values = (await databases.get(instance, dbname)).model_dump(by_alias=True)
|
|
96
106
|
values = deep_update(values, changes)
|
|
@@ -117,12 +127,14 @@ async def apply(
|
|
|
117
127
|
) -> None:
|
|
118
128
|
"""Apply manifest as a database"""
|
|
119
129
|
database = interface.Database.model_validate(data)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
130
|
+
with (
|
|
131
|
+
obj.lock,
|
|
132
|
+
audit(dry_run=dry_run),
|
|
133
|
+
diff.enabled(diff_format),
|
|
134
|
+
system_configure(dry_run=dry_run),
|
|
135
|
+
):
|
|
136
|
+
async with postgresql.running(instance):
|
|
137
|
+
ret = await databases.apply(instance, database)
|
|
126
138
|
if output_format == "json":
|
|
127
139
|
print_json_for(ret)
|
|
128
140
|
else:
|
|
@@ -181,6 +193,7 @@ async def ls(
|
|
|
181
193
|
|
|
182
194
|
@cli.command("drop")
|
|
183
195
|
@model.as_parameters(interface.DatabaseDropped, "create")
|
|
196
|
+
@dry_run_option
|
|
184
197
|
@pass_postgresql_instance
|
|
185
198
|
@click.pass_obj
|
|
186
199
|
@async_command
|
|
@@ -188,9 +201,10 @@ async def drop(
|
|
|
188
201
|
obj: Obj,
|
|
189
202
|
instance: PostgreSQLInstance,
|
|
190
203
|
databasedropped: interface.DatabaseDropped,
|
|
204
|
+
dry_run: bool,
|
|
191
205
|
) -> None:
|
|
192
206
|
"""Drop a database"""
|
|
193
|
-
with obj.lock, audit():
|
|
207
|
+
with obj.lock, audit(dry_run=dry_run), system_configure(dry_run=dry_run):
|
|
194
208
|
async with postgresql.running(instance):
|
|
195
209
|
await databases.drop(instance, databasedropped)
|
|
196
210
|
|
|
@@ -274,11 +288,15 @@ async def run(
|
|
|
274
288
|
)
|
|
275
289
|
@click.argument("dbname")
|
|
276
290
|
@pass_postgresql_instance
|
|
291
|
+
@dry_run_option
|
|
277
292
|
@async_command
|
|
278
|
-
async def dump(
|
|
293
|
+
async def dump(
|
|
294
|
+
instance: PostgreSQLInstance, dbname: str, output: Path | None, dry_run: bool
|
|
295
|
+
) -> None:
|
|
279
296
|
"""Dump a database"""
|
|
280
|
-
|
|
281
|
-
|
|
297
|
+
with system_configure(dry_run=dry_run):
|
|
298
|
+
async with postgresql.running(instance):
|
|
299
|
+
await databases.dump(instance, dbname, output)
|
|
282
300
|
|
|
283
301
|
|
|
284
302
|
@cli.command("dumps")
|
|
@@ -307,9 +325,10 @@ async def dumps(
|
|
|
307
325
|
@click.argument("dump_id")
|
|
308
326
|
@click.argument("targetdbname", required=False)
|
|
309
327
|
@pass_postgresql_instance
|
|
328
|
+
@dry_run_option
|
|
310
329
|
@async_command
|
|
311
330
|
async def restore(
|
|
312
|
-
instance: PostgreSQLInstance, dump_id: str, targetdbname: str | None
|
|
331
|
+
instance: PostgreSQLInstance, dump_id: str, targetdbname: str | None, dry_run: bool
|
|
313
332
|
) -> None:
|
|
314
333
|
"""Restore a database dump
|
|
315
334
|
|
|
@@ -322,5 +341,6 @@ async def restore(
|
|
|
322
341
|
name that appears in the dump. In this case, the restore command will
|
|
323
342
|
create the database so it needs to be dropped before running the command.
|
|
324
343
|
"""
|
|
325
|
-
|
|
326
|
-
|
|
344
|
+
with system_configure(dry_run=dry_run):
|
|
345
|
+
async with postgresql.running(instance):
|
|
346
|
+
await databases.restore(instance, dump_id, targetdbname)
|
|
@@ -25,8 +25,11 @@ from pglift import (
|
|
|
25
25
|
task,
|
|
26
26
|
)
|
|
27
27
|
from pglift.models import Instance, PostgreSQLInstance, interface
|
|
28
|
-
from pglift.settings import
|
|
29
|
-
|
|
28
|
+
from pglift.settings import (
|
|
29
|
+
POSTGRESQL_VERSIONS,
|
|
30
|
+
PostgreSQLVersion,
|
|
31
|
+
default_postgresql_version,
|
|
32
|
+
)
|
|
30
33
|
from pglift.types import Operation, Status, validation_context
|
|
31
34
|
|
|
32
35
|
from . import _site, model
|
|
@@ -114,8 +117,8 @@ async def create(obj: Obj, instance: interface.Instance, drop_on_error: bool) ->
|
|
|
114
117
|
with obj.lock, audit():
|
|
115
118
|
if instances.exists(instance.name, instance.version, _site.SETTINGS):
|
|
116
119
|
raise click.ClickException("instance already exists")
|
|
117
|
-
|
|
118
|
-
with
|
|
120
|
+
with manager.from_manifest(instance, settings=_site.SETTINGS):
|
|
121
|
+
async with task.async_transaction(drop_on_error):
|
|
119
122
|
await instances.apply(_site.SETTINGS, instance)
|
|
120
123
|
|
|
121
124
|
|
|
@@ -213,6 +216,41 @@ async def promote(obj: Obj, instance: Instance) -> None:
|
|
|
213
216
|
await instances.promote(instance)
|
|
214
217
|
|
|
215
218
|
|
|
219
|
+
@cli.command("demote")
|
|
220
|
+
@instance_identifier(nargs=1)
|
|
221
|
+
@model.as_parameters(postgresql.RewindSource, "create")
|
|
222
|
+
@click.option(
|
|
223
|
+
"--start/--no-start",
|
|
224
|
+
help="Start the instance at the end of the demotion process",
|
|
225
|
+
default=True,
|
|
226
|
+
show_default=True,
|
|
227
|
+
)
|
|
228
|
+
@click.argument("rewind_opts", nargs=-1, type=click.UNPROCESSED)
|
|
229
|
+
@click.pass_obj
|
|
230
|
+
@async_command
|
|
231
|
+
async def demote(
|
|
232
|
+
obj: Obj,
|
|
233
|
+
instance: Instance,
|
|
234
|
+
rewindsource: postgresql.RewindSource,
|
|
235
|
+
start: bool,
|
|
236
|
+
rewind_opts: tuple[str, ...],
|
|
237
|
+
) -> None:
|
|
238
|
+
"""Demote PostgreSQL INSTANCE as standby of specified source server using pg_rewind.
|
|
239
|
+
|
|
240
|
+
The instance must not be running and it may be started at the end of the
|
|
241
|
+
"demotion" process.
|
|
242
|
+
|
|
243
|
+
Extra options can be passed to the pg_rewind command. They may need to
|
|
244
|
+
be prefixed with -- to separate them from the current command options
|
|
245
|
+
when confusion arises. When using extra options, providing the instance
|
|
246
|
+
identifier is required.
|
|
247
|
+
"""
|
|
248
|
+
with obj.lock, audit(), manager.from_instance(instance.postgresql):
|
|
249
|
+
await instances.demote(instance, rewindsource, rewind_opts=rewind_opts)
|
|
250
|
+
if start:
|
|
251
|
+
await instances.start(instance)
|
|
252
|
+
|
|
253
|
+
|
|
216
254
|
@cli.command("get")
|
|
217
255
|
@output_format_option
|
|
218
256
|
@instance_identifier(nargs=1)
|
|
@@ -244,7 +282,7 @@ async def get(instance: Instance, output_format: OutputFormat | None) -> None:
|
|
|
244
282
|
@cli.command("list")
|
|
245
283
|
@click.option(
|
|
246
284
|
"--version",
|
|
247
|
-
type=click.Choice(
|
|
285
|
+
type=click.Choice(POSTGRESQL_VERSIONS),
|
|
248
286
|
help="Only list instances of specified version.",
|
|
249
287
|
)
|
|
250
288
|
@output_format_option
|
|
@@ -368,7 +406,7 @@ async def restart(
|
|
|
368
406
|
@instance_identifier(nargs=1, required=True)
|
|
369
407
|
@click.argument("command", required=True, nargs=-1, type=click.UNPROCESSED)
|
|
370
408
|
def exec(instance: Instance, command: tuple[str, ...]) -> None:
|
|
371
|
-
"""Execute
|
|
409
|
+
"""Execute COMMAND in the libpq environment for PostgreSQL INSTANCE.
|
|
372
410
|
|
|
373
411
|
COMMAND parts may need to be prefixed with -- to separate them from
|
|
374
412
|
options when confusion arises.
|
|
@@ -491,7 +529,7 @@ async def list_privileges(
|
|
|
491
529
|
@click.option(
|
|
492
530
|
"--version",
|
|
493
531
|
"newversion",
|
|
494
|
-
type=click.Choice(
|
|
532
|
+
type=click.Choice(POSTGRESQL_VERSIONS),
|
|
495
533
|
help="PostgreSQL version of the new instance (default to site-configured value).",
|
|
496
534
|
)
|
|
497
535
|
@click.option(
|
|
@@ -519,8 +557,8 @@ async def upgrade(
|
|
|
519
557
|
"""
|
|
520
558
|
with obj.lock, audit():
|
|
521
559
|
await postgresql.check_status(instance.postgresql, Status.not_running)
|
|
522
|
-
|
|
523
|
-
with
|
|
560
|
+
with manager.from_instance(instance.postgresql):
|
|
561
|
+
async with task.async_transaction():
|
|
524
562
|
new_instance = await instances.upgrade(
|
|
525
563
|
instance,
|
|
526
564
|
version=newversion,
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
import enum
|
|
8
7
|
import functools
|
|
9
8
|
import inspect
|
|
10
9
|
import typing
|
|
@@ -24,7 +23,6 @@ from pglift.models.helpers import is_optional, optional_type
|
|
|
24
23
|
from pglift.models.interface import PresenceState
|
|
25
24
|
from pglift.types import (
|
|
26
25
|
Operation,
|
|
27
|
-
StrEnum,
|
|
28
26
|
field_annotation,
|
|
29
27
|
validation_context,
|
|
30
28
|
)
|
|
@@ -106,7 +104,7 @@ def as_parameters(model_type: ModelType, operation: Operation) -> ClickDecorator
|
|
|
106
104
|
)
|
|
107
105
|
|
|
108
106
|
@functools.wraps(f)
|
|
109
|
-
def callback(**kwargs: Any) -> Any:
|
|
107
|
+
def callback(**kwargs: Any) -> Any:
|
|
110
108
|
args = params_to_modelargs(kwargs)
|
|
111
109
|
with (
|
|
112
110
|
catch_validationerror(*paramspecs),
|
|
@@ -358,14 +356,6 @@ def _paramspecs_from_model(
|
|
|
358
356
|
choices = config.choices
|
|
359
357
|
attrs["type"] = click.Choice(choices)
|
|
360
358
|
|
|
361
|
-
elif lenient_issubclass(ftype, enum.Enum):
|
|
362
|
-
if config:
|
|
363
|
-
assert isinstance(config, cli.Choices)
|
|
364
|
-
choices = config.choices
|
|
365
|
-
else:
|
|
366
|
-
choices = choices_from_enum(ftype)
|
|
367
|
-
attrs["type"] = click.Choice(choices)
|
|
368
|
-
|
|
369
359
|
elif lenient_issubclass(origin_type or ftype, list):
|
|
370
360
|
attrs["multiple"] = True
|
|
371
361
|
try:
|
|
@@ -373,10 +363,7 @@ def _paramspecs_from_model(
|
|
|
373
363
|
except ValueError:
|
|
374
364
|
pass
|
|
375
365
|
else:
|
|
376
|
-
|
|
377
|
-
attrs["type"] = click.Choice(choices_from_enum(itemtype))
|
|
378
|
-
else:
|
|
379
|
-
attrs["metavar"] = metavar
|
|
366
|
+
attrs["metavar"] = metavar
|
|
380
367
|
if not _parents and operation == "update" and is_editable(itemtype):
|
|
381
368
|
# List fields for the "update" operation are mapped to
|
|
382
369
|
# --add-<fname>, --remove-<fname> options; built and yield
|
|
@@ -460,13 +447,6 @@ def _paramspecs_from_model(
|
|
|
460
447
|
)
|
|
461
448
|
|
|
462
449
|
|
|
463
|
-
def choices_from_enum(e: type[enum.Enum]) -> list[Any]:
|
|
464
|
-
if lenient_issubclass(e, StrEnum):
|
|
465
|
-
return list(e)
|
|
466
|
-
else:
|
|
467
|
-
return [v.value for v in e]
|
|
468
|
-
|
|
469
|
-
|
|
470
450
|
@contextmanager
|
|
471
451
|
def catch_validationerror(*paramspec: ParamSpec) -> Iterator[None]:
|
|
472
452
|
try:
|
|
@@ -12,7 +12,7 @@ import click
|
|
|
12
12
|
|
|
13
13
|
from pglift import pgbackrest, postgresql, types
|
|
14
14
|
from pglift.models import Instance
|
|
15
|
-
from pglift.pgbackrest import
|
|
15
|
+
from pglift.pgbackrest import models
|
|
16
16
|
from pglift.pgbackrest import register_if as register_if
|
|
17
17
|
|
|
18
18
|
from .. import _site, hookimpl
|
|
@@ -46,7 +46,7 @@ def pgbackrest_proxy(
|
|
|
46
46
|
"""Proxy to pgbackrest operations on an instance"""
|
|
47
47
|
s = context.obj.instance.service(models.Service)
|
|
48
48
|
settings = pgbackrest.get_settings(_site.SETTINGS)
|
|
49
|
-
cmd_args =
|
|
49
|
+
cmd_args = pgbackrest.make_cmd(s.stanza, settings, *command)
|
|
50
50
|
try:
|
|
51
51
|
subprocess.run(cmd_args, capture_output=False, check=True) # nosec
|
|
52
52
|
except subprocess.CalledProcessError as e:
|
|
@@ -70,7 +70,7 @@ async def instance_restore(
|
|
|
70
70
|
) from None
|
|
71
71
|
settings = pgbackrest.get_settings(_site.SETTINGS)
|
|
72
72
|
with obj.lock, audit():
|
|
73
|
-
await
|
|
73
|
+
await pgbackrest.restore(instance, settings, label=label, date=date)
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
@click.command("backups", cls=Command)
|
|
@@ -82,7 +82,7 @@ async def instance_backups(
|
|
|
82
82
|
) -> None:
|
|
83
83
|
"""List available backups for INSTANCE"""
|
|
84
84
|
settings = pgbackrest.get_settings(_site.SETTINGS)
|
|
85
|
-
backups = [b async for b in
|
|
85
|
+
backups = [b async for b in pgbackrest.iter_backups(instance, settings)]
|
|
86
86
|
if output_format == "json":
|
|
87
87
|
print_json_for([model_dump(b) for b in backups])
|
|
88
88
|
else:
|
|
@@ -35,6 +35,7 @@ from .util import (
|
|
|
35
35
|
print_result_diff,
|
|
36
36
|
print_schema,
|
|
37
37
|
print_table_for,
|
|
38
|
+
system_configure,
|
|
38
39
|
)
|
|
39
40
|
|
|
40
41
|
|
|
@@ -76,11 +77,14 @@ def cli(**kwargs: Any) -> None:
|
|
|
76
77
|
@cli.command("create")
|
|
77
78
|
@model.as_parameters(_site.ROLE_MODEL, "create")
|
|
78
79
|
@pass_postgresql_instance
|
|
80
|
+
@dry_run_option
|
|
79
81
|
@click.pass_obj
|
|
80
82
|
@async_command
|
|
81
|
-
async def create(
|
|
83
|
+
async def create(
|
|
84
|
+
obj: Obj, instance: PostgreSQLInstance, role: interface.Role, dry_run: bool
|
|
85
|
+
) -> None:
|
|
82
86
|
"""Create a role in a PostgreSQL instance"""
|
|
83
|
-
with obj.lock, audit():
|
|
87
|
+
with obj.lock, audit(dry_run=dry_run), system_configure(dry_run=dry_run):
|
|
84
88
|
async with postgresql.running(instance):
|
|
85
89
|
if await roles.exists(instance, role.name):
|
|
86
90
|
raise click.ClickException("role already exists")
|
|
@@ -92,13 +96,14 @@ async def create(obj: Obj, instance: PostgreSQLInstance, role: interface.Role) -
|
|
|
92
96
|
@model.as_parameters(_site.ROLE_MODEL, "update")
|
|
93
97
|
@click.argument("rolname")
|
|
94
98
|
@pass_postgresql_instance
|
|
99
|
+
@dry_run_option
|
|
95
100
|
@click.pass_obj # type: ignore[arg-type]
|
|
96
101
|
@async_command
|
|
97
102
|
async def alter(
|
|
98
|
-
obj: Obj, instance: PostgreSQLInstance, rolname: str, **changes: Any
|
|
103
|
+
obj: Obj, instance: PostgreSQLInstance, rolname: str, dry_run: bool, **changes: Any
|
|
99
104
|
) -> None:
|
|
100
105
|
"""Alter a role in a PostgreSQL instance"""
|
|
101
|
-
with obj.lock, audit():
|
|
106
|
+
with obj.lock, audit(dry_run=dry_run), system_configure(dry_run=dry_run):
|
|
102
107
|
async with postgresql.running(instance):
|
|
103
108
|
with manager.from_instance(instance):
|
|
104
109
|
values = model_dump(await roles.get(instance, rolname))
|
|
@@ -136,13 +141,15 @@ async def apply(
|
|
|
136
141
|
)
|
|
137
142
|
with validation_context(operation=op, settings=_site.SETTINGS):
|
|
138
143
|
role = _site.ROLE_MODEL.model_validate(data)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
with (
|
|
145
|
+
obj.lock,
|
|
146
|
+
audit(dry_run=dry_run),
|
|
147
|
+
diff.enabled(diff_format),
|
|
148
|
+
system_configure(dry_run=dry_run),
|
|
149
|
+
):
|
|
150
|
+
async with postgresql.running(instance):
|
|
151
|
+
with manager.from_instance(instance):
|
|
152
|
+
ret = await roles.apply(instance, role)
|
|
146
153
|
if output_format == "json":
|
|
147
154
|
print_json_for(ret)
|
|
148
155
|
else:
|
|
@@ -192,14 +199,16 @@ async def get(
|
|
|
192
199
|
@cli.command("drop")
|
|
193
200
|
@model.as_parameters(interface.RoleDropped, "create")
|
|
194
201
|
@pass_postgresql_instance
|
|
202
|
+
@dry_run_option
|
|
195
203
|
@async_command
|
|
196
204
|
async def drop(
|
|
197
|
-
instance: PostgreSQLInstance, roledropped: interface.RoleDropped
|
|
205
|
+
instance: PostgreSQLInstance, roledropped: interface.RoleDropped, dry_run: bool
|
|
198
206
|
) -> None:
|
|
199
207
|
"""Drop a role"""
|
|
200
|
-
|
|
201
|
-
with
|
|
202
|
-
|
|
208
|
+
with audit(dry_run=dry_run), system_configure(dry_run=dry_run):
|
|
209
|
+
async with postgresql.running(instance):
|
|
210
|
+
with manager.from_instance(instance):
|
|
211
|
+
await roles.drop(instance, roledropped)
|
|
203
212
|
|
|
204
213
|
|
|
205
214
|
@cli.command("privileges")
|
|
@@ -37,7 +37,7 @@ from rich.table import Table
|
|
|
37
37
|
|
|
38
38
|
from pglift import exceptions, instances, system
|
|
39
39
|
from pglift.models import Instance, PostgreSQLInstance, helpers, interface
|
|
40
|
-
from pglift.settings import Settings
|
|
40
|
+
from pglift.settings import PostgreSQLVersion, Settings
|
|
41
41
|
from pglift.system import install
|
|
42
42
|
from pglift.types import ByteSizeType
|
|
43
43
|
|
|
@@ -116,7 +116,6 @@ def print_table_for(
|
|
|
116
116
|
**kwargs: Any,
|
|
117
117
|
) -> None:
|
|
118
118
|
"""Render a list of items as a table."""
|
|
119
|
-
table = None
|
|
120
119
|
headers: list[str] = []
|
|
121
120
|
rows = []
|
|
122
121
|
for item in items:
|
|
@@ -139,7 +138,13 @@ def print_table_for(
|
|
|
139
138
|
rows.append(row)
|
|
140
139
|
if not rows:
|
|
141
140
|
return
|
|
142
|
-
table = Table(
|
|
141
|
+
table = Table(title=title, **kwargs)
|
|
142
|
+
# https://github.com/Textualize/rich/issues/3761
|
|
143
|
+
overflow: Literal["ellipsis", "fold"] = (
|
|
144
|
+
"fold" if console.options.ascii_only else "ellipsis"
|
|
145
|
+
)
|
|
146
|
+
for header in headers:
|
|
147
|
+
table.add_column(header, overflow=overflow)
|
|
143
148
|
for row in rows:
|
|
144
149
|
table.add_row(*row)
|
|
145
150
|
console.print(table)
|
|
@@ -219,13 +224,13 @@ def pass_postgresql_instance(f: Callable[P, None]) -> Callable[P, None]:
|
|
|
219
224
|
|
|
220
225
|
|
|
221
226
|
def get_postgresql_instance(
|
|
222
|
-
name: str, version:
|
|
227
|
+
name: str, version: PostgreSQLVersion | None, settings: Settings
|
|
223
228
|
) -> PostgreSQLInstance:
|
|
224
229
|
"""Return a PostgreSQLInstance from name/version, possibly guessing version if unspecified."""
|
|
225
230
|
if version is None:
|
|
226
231
|
found = None
|
|
227
232
|
for v in settings.postgresql.versions:
|
|
228
|
-
version = v.version
|
|
233
|
+
version = v.version
|
|
229
234
|
try:
|
|
230
235
|
instance = PostgreSQLInstance.system_lookup(name, version, settings)
|
|
231
236
|
except exceptions.InstanceNotFound:
|
|
@@ -249,19 +254,21 @@ def get_postgresql_instance(
|
|
|
249
254
|
raise click.BadParameter(str(e)) from None
|
|
250
255
|
|
|
251
256
|
|
|
252
|
-
def get_instance(
|
|
257
|
+
def get_instance(
|
|
258
|
+
name: str, version: PostgreSQLVersion | None, settings: Settings
|
|
259
|
+
) -> Instance:
|
|
253
260
|
"""Return an Instance from name/version, possibly guessing version if unspecified."""
|
|
254
261
|
pg_instance = get_postgresql_instance(name, version, settings)
|
|
255
262
|
return Instance.from_postgresql(pg_instance)
|
|
256
263
|
|
|
257
264
|
|
|
258
|
-
def nameversion_from_id(instance_id: str) -> tuple[str,
|
|
265
|
+
def nameversion_from_id(instance_id: str) -> tuple[str, PostgreSQLVersion | None]:
|
|
259
266
|
version = None
|
|
260
267
|
try:
|
|
261
268
|
version, name = instance_id.split("/", 1)
|
|
262
269
|
except ValueError:
|
|
263
270
|
name = instance_id
|
|
264
|
-
return name, version
|
|
271
|
+
return name, typing.cast(PostgreSQLVersion, version)
|
|
265
272
|
|
|
266
273
|
|
|
267
274
|
def postgresql_instance_lookup(
|
|
@@ -732,12 +739,13 @@ class Group(click.Group):
|
|
|
732
739
|
super().add_command(command, name)
|
|
733
740
|
|
|
734
741
|
def invoke(self, ctx: click.Context) -> Any:
|
|
735
|
-
if
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
742
|
+
if set(ctx.help_option_names) - set(ctx.args):
|
|
743
|
+
if is_root():
|
|
744
|
+
raise click.ClickException("pglift cannot be used as root")
|
|
745
|
+
if not install.check(_site.SETTINGS):
|
|
746
|
+
raise click.ClickException(
|
|
747
|
+
"broken installation; did you run 'site-configure install'?",
|
|
748
|
+
)
|
|
741
749
|
return super().invoke(ctx)
|
|
742
750
|
|
|
743
751
|
|