pglift-cli 2.1.0__tar.gz → 2.3.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.
Files changed (47) hide show
  1. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/PKG-INFO +2 -2
  2. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/pyproject.toml +4 -1
  3. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/database.py +5 -3
  4. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/instance.py +8 -5
  5. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/model.py +2 -22
  6. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/pghba.py +7 -4
  7. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/util.py +22 -14
  8. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/tests/expect/test-cli-walkthrough.t +45 -23
  9. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/tests/expect/test-help.t +65 -36
  10. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/tests/unit/conftest.py +1 -1
  11. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/tests/unit/test_cli.py +8 -1
  12. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/tests/unit/test_main.py +2 -2
  13. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/tests/unit/test_model.py +1 -11
  14. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/tests/unit/test_util.py +2 -2
  15. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/.gitignore +0 -0
  16. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/README.md +0 -0
  17. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/hatch.toml +0 -0
  18. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/pytest.ini +0 -0
  19. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/__init__.py +0 -0
  20. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/__main__.py +0 -0
  21. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/_settings.py +0 -0
  22. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/_site.py +0 -0
  23. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/base.py +0 -0
  24. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/console.py +0 -0
  25. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/hookspecs.py +0 -0
  26. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/main.py +0 -0
  27. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/patroni.py +0 -0
  28. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/pgbackrest/__init__.py +0 -0
  29. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/pgbackrest/repo_path.py +0 -0
  30. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/pgconf.py +0 -0
  31. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/pm.py +0 -0
  32. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/postgres.py +0 -0
  33. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/prometheus.py +0 -0
  34. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/py.typed +0 -0
  35. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/src/pglift_cli/role.py +0 -0
  36. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/tests/expect/.gitignore +0 -0
  37. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/tests/expect/test-base.t +0 -0
  38. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/tests/expect/test-demote.t +0 -0
  39. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/tests/expect/test-port-validation.t +0 -0
  40. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/tests/expect/test-prometheus.t +0 -0
  41. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/tests/expect/test-standby-pgbackrest.t +0 -0
  42. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/tests/expect/test-transactions.t +0 -0
  43. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/tests/expect/test-upgrade.t +0 -0
  44. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/tests/unit/__init__.py +0 -0
  45. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/tests/unit/test__site.py +0 -0
  46. {pglift_cli-2.1.0 → pglift_cli-2.3.0}/tests/unit/test_audit.py +0 -0
  47. {pglift_cli-2.1.0 → pglift_cli-2.3.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.1.0
3
+ Version: 2.3.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!=8.1.0,!=8.1.4,<8.2.0,>=8.0.0
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.0.0, != 8.1.0, != 8.1.4, < 8.2.0",
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"
@@ -325,9 +325,10 @@ async def dumps(
325
325
  @click.argument("dump_id")
326
326
  @click.argument("targetdbname", required=False)
327
327
  @pass_postgresql_instance
328
+ @dry_run_option
328
329
  @async_command
329
330
  async def restore(
330
- instance: PostgreSQLInstance, dump_id: str, targetdbname: str | None
331
+ instance: PostgreSQLInstance, dump_id: str, targetdbname: str | None, dry_run: bool
331
332
  ) -> None:
332
333
  """Restore a database dump
333
334
 
@@ -340,5 +341,6 @@ async def restore(
340
341
  name that appears in the dump. In this case, the restore command will
341
342
  create the database so it needs to be dropped before running the command.
342
343
  """
343
- async with postgresql.running(instance):
344
- await databases.restore(instance, dump_id, targetdbname)
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 default_postgresql_version
29
- from pglift.settings._postgresql import PostgreSQLVersion
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
@@ -279,7 +282,7 @@ async def get(instance: Instance, output_format: OutputFormat | None) -> None:
279
282
  @cli.command("list")
280
283
  @click.option(
281
284
  "--version",
282
- type=click.Choice(list(PostgreSQLVersion)),
285
+ type=click.Choice(POSTGRESQL_VERSIONS),
283
286
  help="Only list instances of specified version.",
284
287
  )
285
288
  @output_format_option
@@ -403,7 +406,7 @@ async def restart(
403
406
  @instance_identifier(nargs=1, required=True)
404
407
  @click.argument("command", required=True, nargs=-1, type=click.UNPROCESSED)
405
408
  def exec(instance: Instance, command: tuple[str, ...]) -> None:
406
- """Execute command in the libpq environment for PostgreSQL INSTANCE.
409
+ """Execute COMMAND in the libpq environment for PostgreSQL INSTANCE.
407
410
 
408
411
  COMMAND parts may need to be prefixed with -- to separate them from
409
412
  options when confusion arises.
@@ -526,7 +529,7 @@ async def list_privileges(
526
529
  @click.option(
527
530
  "--version",
528
531
  "newversion",
529
- type=click.Choice(list(PostgreSQLVersion)),
532
+ type=click.Choice(POSTGRESQL_VERSIONS),
530
533
  help="PostgreSQL version of the new instance (default to site-configured value).",
531
534
  )
532
535
  @click.option(
@@ -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: # type: ignore[misc]
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
- if lenient_issubclass(itemtype, enum.Enum):
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:
@@ -16,6 +16,7 @@ from .console import console
16
16
  from .util import (
17
17
  Group,
18
18
  Obj,
19
+ async_command,
19
20
  audit,
20
21
  diff_options,
21
22
  dry_run_option,
@@ -38,7 +39,8 @@ def cli(**kwargs: Any) -> None:
38
39
  @diff_options["unified"]
39
40
  @diff_options["ansible"]
40
41
  @click.pass_obj
41
- def add(
42
+ @async_command
43
+ async def add(
42
44
  obj: Obj,
43
45
  instance: PostgreSQLInstance,
44
46
  hbarecord: interface.HbaRecord,
@@ -56,7 +58,7 @@ def add(
56
58
  system_configure(dry_run=dry_run),
57
59
  manager.from_instance(instance),
58
60
  ):
59
- hba.add(instance, hbarecord)
61
+ await hba.add(instance, hbarecord)
60
62
  if (diffvalue := diff.get()) is not None:
61
63
  for diffitem in diffvalue:
62
64
  console.print(diffitem)
@@ -69,7 +71,8 @@ def add(
69
71
  @diff_options["unified"]
70
72
  @diff_options["ansible"]
71
73
  @click.pass_obj
72
- def remove(
74
+ @async_command
75
+ async def remove(
73
76
  obj: Obj,
74
77
  instance: PostgreSQLInstance,
75
78
  hbarecord: interface.HbaRecord,
@@ -87,7 +90,7 @@ def remove(
87
90
  system_configure(dry_run=dry_run),
88
91
  manager.from_instance(instance),
89
92
  ):
90
- hba.remove(instance, hbarecord)
93
+ await hba.remove(instance, hbarecord)
91
94
  if (diffvalue := diff.get()) is not None:
92
95
  for diffitem in diffvalue:
93
96
  console.print(diffitem)
@@ -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(*headers, title=title, **kwargs)
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: str | None, settings: Settings
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.value
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(name: str, version: str | None, settings: Settings) -> 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, str | None]:
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 is_root():
736
- raise click.ClickException("pglift cannot be used as root")
737
- if not install.check(_site.SETTINGS):
738
- raise click.ClickException(
739
- "broken installation; did you run 'site-configure install'?",
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
 
@@ -4,6 +4,7 @@
4
4
 
5
5
  Site settings:
6
6
 
7
+ $ export PYTHONIOENCODING=ascii
7
8
  $ cat > $TMPDIR/passwords.json << 'EOF'
8
9
  > {
9
10
  > "main": {
@@ -249,8 +250,8 @@ Stop, alter, (re)start an instance:
249
250
  --- $TMPDIR/etc/prometheus/postgres_exporter-1*-main.conf (glob)
250
251
  +++ $TMPDIR/etc/prometheus/postgres_exporter-1*-main.conf (glob)
251
252
  @@ -1,2 +1,2 @@
252
- DATA_SOURCE_NAME=postgresql://prometheus@:*/postgres?host=%2Ftmp%2Fprysk-te (glob)
253
- sts-*%2Ftmp%2Frun%2Fpostgresql&sslmode=disable (glob)
253
+ DATA_SOURCE_NAME=postgresql://prometheus@:*/postgres?host=* (glob)
254
+ *%2Fpostgresql&sslmode=disable (glob)
254
255
  -POSTGRES_EXPORTER_OPTS='--web.listen-address :\d+ --log.level info' (re)
255
256
  \+POSTGRES_EXPORTER_OPTS='--web.listen-address :\d+ --log.level info' (re)
256
257
  --- /dev/null
@@ -293,7 +294,7 @@ Stop, alter, (re)start an instance:
293
294
  INFO reloading PostgreSQL configuration for 1\d\/main (re)
294
295
  INFO starting Prometheus postgres_exporter 1\d-main (re)
295
296
  INFO creating role 'arole'
296
- INFO pg_hba.conf updated
297
+ INFO HBA configuration updated
297
298
  INFO reloading PostgreSQL configuration for 1\d\/main (re)
298
299
  --- $TMPDIR/srv/pgsql/1*/main/data/postgresql.conf (glob)
299
300
  +++ $TMPDIR/srv/pgsql/1*/main/data/postgresql.conf (glob)
@@ -390,7 +391,7 @@ PostgreSQL configuration
390
391
  port: \d+ -> 1234 (re)
391
392
  DRY RUN: no changes made
392
393
 
393
- pg_hba.conf management:
394
+ HBA configuration management:
394
395
 
395
396
  $ pglift pghba add --user bob --method trust
396
397
  INFO entry added to HBA configuration
@@ -500,8 +501,14 @@ Add and manipulate roles:
500
501
  \*:\d+:\*:dba:qwerty (re)
501
502
 
502
503
  $ pglift role get dba
503
- name has_\xe2\x80\xa6 inhe\xe2\x80\xa6 login supe\xe2\x80\xa6 crea\xe2\x80\xa6 crea\xe2\x80\xa6 rep\xe2\x80\xa6 conn\xe2\x80\xa6 val\xe2\x80\xa6 memb\xe2\x80\xa6 pgp\xe2\x80\xa6 (esc)
504
- dba True True True False False False Fal\xe2\x80\xa6 pg_r\xe2\x80\xa6 True (esc)
504
+ conne
505
+ has_p repl ction vali membe
506
+ asswo inher super creat creat icat _limi d_un rship pgpa
507
+ name rd it login user edb erole ion t til s ss
508
+ dba True True True False False False Fals pg_re True
509
+ e ad_al
510
+ l_sta
511
+ ts
505
512
 
506
513
  $ pglift role -i main create dba --dry-run
507
514
  Error: role already exists
@@ -576,7 +583,7 @@ Add and manipulate roles:
576
583
  > EOF
577
584
  $ pglift role apply -f $TMPDIR/role.yaml --dry-run
578
585
  INFO creating role 'test'
579
- INFO pg_hba.conf updated
586
+ INFO HBA configuration updated
580
587
  INFO reloading PostgreSQL configuration for 1\d\/main (re)
581
588
  DRY RUN: no changes made
582
589
  $ grep 'test' $TMPDIR/srv/pgsql/${version}/main/data/pg_hba.conf
@@ -586,7 +593,7 @@ Add and manipulate roles:
586
593
  [1]
587
594
  $ pglift role apply -f $TMPDIR/role.yaml --diff
588
595
  INFO creating role 'test'
589
- INFO pg_hba.conf updated
596
+ INFO HBA configuration updated
590
597
  INFO reloading PostgreSQL configuration for 1\d\/main (re)
591
598
  --- $TMPDIR/srv/pgsql/1*/main/data/pg_hba.conf (glob)
592
599
  +++ $TMPDIR/srv/pgsql/1*/main/data/pg_hba.conf (glob)
@@ -822,11 +829,11 @@ Databases
822
829
  INFO SELECT 1
823
830
  Database
824
831
  other
825
- ┏━━━━━━━━┓
826
- exists
827
- ┡━━━━━━━━┩
828
- False
829
- └────────┘
832
+ +--------+
833
+ | exists |
834
+ |--------|
835
+ | False |
836
+ +--------+
830
837
  $ pglift database apply -f $TMPDIR/other.yaml -o json --diff
831
838
  INFO altering 'other' database on instance 1\d\/main (re)
832
839
  INFO creating extension 'pg_stat_statements' in database other
@@ -839,11 +846,11 @@ Databases
839
846
  INFO SELECT 1
840
847
  Database
841
848
  other
842
- ┏━━━━━━━━┓
843
- exists
844
- ┡━━━━━━━━┩
845
- True
846
- └────────┘
849
+ +--------+
850
+ | exists |
851
+ |--------|
852
+ | True |
853
+ +--------+
847
854
  $ cat > $TMPDIR/other.yaml <<EOF
848
855
  > name: other
849
856
  > state: absent
@@ -855,8 +862,11 @@ Databases
855
862
  INFO dropping 'other' database
856
863
  DRY RUN: no changes made
857
864
  $ pglift database get other
858
- name owner settin… schemas extensi… locale public… subscri… tables… (esc)
859
- other postgr… public pg_stat… C pg_def… (esc)
865
+ setting extensio publica subscrip tablesp
866
+ name owner s schemas ns locale tions tions ace
867
+ other postgre public pg_stat_ C pg_defa
868
+ s statemen ult
869
+ ts
860
870
  $ pglift database create other
861
871
  Error: database already exists
862
872
  [1]
@@ -1040,6 +1050,18 @@ Databases
1040
1050
  $ pglift database drop myapp
1041
1051
  INFO dropping 'myapp' database
1042
1052
 
1053
+ $ pglift database restore doesnt_exist_id --dry-run
1054
+ Error: dump 'doesnt_exist_id' not found
1055
+ [1]
1056
+
1057
+ $ pglift database restore doesnt_exist_id
1058
+ Error: dump 'doesnt_exist_id' not found
1059
+ [1]
1060
+
1061
+ $ pglift database restore $DUMP_ID --dry-run
1062
+ INFO restoring dump for 'myapp' on instance 1\d\/main (re)
1063
+ DRY RUN: no changes made
1064
+
1043
1065
  $ pglift database restore $DUMP_ID
1044
1066
  INFO restoring dump for 'myapp' on instance 1\d\/main (re)
1045
1067
 
@@ -1080,7 +1102,7 @@ Databases
1080
1102
 
1081
1103
  $ pglift role drop test --dry-run
1082
1104
  INFO dropping role 'test'
1083
- INFO removing entries from pg_hba.conf
1105
+ INFO removing entries from HBA configuration
1084
1106
  DRY RUN: no changes made
1085
1107
  $ pglift role drop test
1086
1108
  INFO dropping role 'test'
@@ -1095,11 +1117,11 @@ Databases
1095
1117
  [2]
1096
1118
  $ pglift role drop test --drop-owned --dry-run
1097
1119
  INFO dropping role 'test'
1098
- INFO removing entries from pg_hba.conf
1120
+ INFO removing entries from HBA configuration
1099
1121
  DRY RUN: no changes made
1100
1122
  $ pglift role drop test --drop-owned
1101
1123
  INFO dropping role 'test'
1102
- INFO removing entries from pg_hba.conf
1124
+ INFO removing entries from HBA configuration
1103
1125
 
1104
1126
  Profiles
1105
1127
  $ pglift role -i main create dba1 --password mySup3rS3cr3t1377 --login
@@ -6,53 +6,77 @@ Tests for --help options of all commands
6
6
 
7
7
  Site settings
8
8
 
9
- $ PATRONI_CMD=$(command -v patroni)
10
- $ PATRONICTL=$(command -v patronictl)
11
- $ POSTGRES_EXPORTER=$(command -v postgres_exporter || command -v prometheus-postgres-exporter)
12
9
  $ export PGLIFT_CLI__LOG_FORMAT="%(levelname)-4s %(message)s"
13
10
  $ export PGLIFT_CONFIG_DIR=$TMPDIR
14
11
  $ export PGLIFT_PREFIX=$TMPDIR
15
12
  $ export PGLIFT_RUN_PREFIX=$TMPDIR/run
16
- $ export PGLIFT_PATRONI='{"execpath": "'$PATRONI_CMD'", "ctlpath": "'$PATRONICTL'"}'
17
- $ export PGLIFT_PGBACKREST='{
18
- > "repository": {
19
- > "mode": "path",
20
- > "path": "'$TMPDIR'/test-help/backups"
21
- > }
22
- > }'
23
- $ export PGLIFT_POSTGRESQL__AUTH__PASSFILE="null"
24
- $ export PGLIFT_POSTGRESQL__REPLROLE="replication"
13
+
14
+ $ bindir=$TMPDIR/$TESTFILE/bin
15
+ $ mkdir -p $bindir/pgsql/17/bin
16
+ $ touch $bindir/pgsql/17/bin/pg_ctl
17
+ $ chmod +x $bindir/pgsql/17/bin/pg_ctl
18
+ $ export PGLIFT_POSTGRESQL__BINDIR=$bindir/pgsql/{version}/bin \
19
+ > PGLIFT_POSTGRESQL__AUTH__PASSFILE="null" \
20
+ > PGLIFT_POSTGRESQL__REPLROLE="replication"
21
+ $ export PGLIFT_PATRONI__EXECPATH=$bindir/patroni \
22
+ > PGLIFT_PATRONI__CTLPATH=$bindir/patronictl \
23
+ > PGLIFT_PGBACKREST__EXECPATH=$bindir/pgbackrest \
24
+ > PGLIFT_PROMETHEUS__EXECPATH=$bindir/postgres_exporter
25
+ $ touch $PGLIFT_PATRONI__EXECPATH \
26
+ > $PGLIFT_PATRONI__CTLPATH \
27
+ > $PGLIFT_PGBACKREST__EXECPATH \
28
+ > $PGLIFT_PROMETHEUS__EXECPATH
29
+ $ export PGLIFT_PGBACKREST__REPOSITORY__MODE=path \
30
+ > PGLIFT_PGBACKREST__REPOSITORY__PATH=$TMPDIR/$TESTFILE/backups
31
+ $ mkdir $PGLIFT_PGBACKREST__REPOSITORY__PATH
25
32
  $ export PGLIFT_POWA='{}'
26
- $ export PGLIFT_PROMETHEUS__EXECPATH=$POSTGRES_EXPORTER
33
+
27
34
  $ pglift site-settings --no-defaults -o json \
28
- > | jq '.pgbackrest, .prometheus, .prefix, .run_prefix'
35
+ > | jq '.postgresql, .pgbackrest, .prometheus, .prefix, .run_prefix'
29
36
  {
37
+ "bindir": "$TMPDIR/*/bin/pgsql/{version}/bin", (glob)
38
+ "versions": [
39
+ {
40
+ "version": "17",
41
+ "bindir": "$TMPDIR/*/bin/pgsql/17/bin" (glob)
42
+ }
43
+ ],
44
+ "auth": {
45
+ "passfile": null
46
+ },
47
+ "replrole": "replication",
48
+ "datadir": "$TMPDIR/srv/pgsql/{version}/{name}/data",
49
+ "waldir": "$TMPDIR/srv/pgsql/{version}/{name}/wal",
50
+ "logpath": "$TMPDIR/log/postgresql",
51
+ "socket_directory": "$TMPDIR/run/postgresql",
52
+ "dumps_directory": "$TMPDIR/srv/dumps/{version}-{name}"
53
+ }
54
+ {
55
+ "execpath": "$TMPDIR/*/bin/pgbackrest", (glob)
30
56
  "configpath": "$TMPDIR/etc/pgbackrest",
31
57
  "repository": {
32
58
  "mode": "path",
33
- "path": "$TMPDIR/test-help/backups"
59
+ "path": "$TMPDIR/*/backups" (glob)
34
60
  },
35
61
  "logpath": "$TMPDIR/log/pgbackrest",
36
62
  "spoolpath": "$TMPDIR/srv/pgbackrest/spool",
37
63
  "lockpath": "$TMPDIR/run/pgbackrest/lock"
38
64
  }
39
65
  {
40
- "execpath": ".*postgres[-_]exporter", (re)
66
+ "execpath": "$TMPDIR/*/bin/postgres_exporter", (glob)
41
67
  "configpath": "$TMPDIR/etc/prometheus/postgres_exporter-{name}.conf",
42
68
  "pid_file": "$TMPDIR/run/prometheus/{name}.pid"
43
69
  }
44
70
  "$TMPDIR"
45
71
  "$TMPDIR/run"
46
72
 
47
- $ trap "pglift --non-interactive --log-level=INFO site-configure uninstall" EXIT
48
-
49
73
  $ pglift --help
50
74
  Usage: pglift [OPTIONS] COMMAND [ARGS]...
51
75
 
52
76
  Deploy production-ready instances of PostgreSQL
53
77
 
54
78
  Options:
55
- -L, --log-level [DEBUG|INFO|WARNING|ERROR|CRITICAL]
79
+ -L, --log-level [debug|info|warning|error|critical]
56
80
  Set log threshold (default to INFO when
57
81
  logging to stderr or WARNING when logging to
58
82
  a file).
@@ -81,23 +105,17 @@ Site configuration
81
105
  $ pglift site-configure list
82
106
  $TMPDIR/etc/pgbackrest/conf.d
83
107
  $TMPDIR/etc/pgbackrest/pgbackrest.conf
84
- $TMPDIR/test-help/backups
108
+ $TMPDIR/*/backups (glob)
85
109
  $TMPDIR/log/pgbackrest
86
110
  $TMPDIR/srv/pgbackrest/spool
87
111
  $TMPDIR/log/postgresql
88
- $ pglift --log-level=INFO site-configure install
89
- INFO creating base pgBackRest configuration directory: $TMPDIR/etc/pgbackrest
90
- INFO installing base pgBackRest configuration
91
- INFO creating pgBackRest include directory
92
- INFO creating pgBackRest repository backups and archive directory: $TMPDIR/test-help/backups
93
- INFO creating pgBackRest log directory: $TMPDIR/log/pgbackrest
94
- INFO creating pgBackRest spool directory: $TMPDIR/srv/pgbackrest/spool
95
- INFO creating PostgreSQL log directory: $TMPDIR/log/postgresql
96
112
  $ env PGLIFT_LOGGERS='pglift_cli,pglift,filelock' \
97
113
  > pglift --log-level=debug --log-file=$TMPDIR/check.log site-configure check
114
+ [1]
98
115
  $ cat $TMPDIR/check.log
99
116
  DEBUG Attempting to acquire lock \d+ on \$TMPDIR\/run\/.pglift.lock (re)
100
117
  DEBUG Lock \d+ acquired on \$TMPDIR\/run\/.pglift.lock (re)
118
+ ERROR pgBackRest configuration path $TMPDIR/etc/pgbackrest/conf.d missing
101
119
  DEBUG Attempting to release lock \d+ on \$TMPDIR\/run\/.pglift.lock (re)
102
120
  DEBUG Lock \d+ released on \$TMPDIR\/run\/.pglift.lock (re)
103
121
 
@@ -120,7 +138,7 @@ Instance commands
120
138
  demote Demote PostgreSQL INSTANCE as standby of specified source...
121
139
  drop Drop PostgreSQL INSTANCE
122
140
  env Output environment variables suitable to handle to...
123
- exec Execute command in the libpq environment for PostgreSQL...
141
+ exec Execute COMMAND in the libpq environment for PostgreSQL...
124
142
  get Get the description of PostgreSQL INSTANCE.
125
143
  list List the available instances
126
144
  logs Output PostgreSQL logs of INSTANCE.
@@ -313,7 +331,7 @@ Instance commands
313
331
  $ pglift instance exec --help
314
332
  Usage: pglift instance exec [OPTIONS] INSTANCE COMMAND...
315
333
 
316
- Execute command in the libpq environment for PostgreSQL INSTANCE.
334
+ Execute COMMAND in the libpq environment for PostgreSQL INSTANCE.
317
335
 
318
336
  COMMAND parts may need to be prefixed with -- to separate them from options
319
337
  when confusion arises.
@@ -774,6 +792,23 @@ Database commands
774
792
  Options:
775
793
  -o, --output-format [json] Specify the output format.
776
794
  --help Show this message and exit.
795
+ $ pglift database restore --help
796
+ Usage: pglift database restore [OPTIONS] DUMP_ID [TARGETDBNAME]
797
+
798
+ Restore a database dump
799
+
800
+ DUMP_ID identifies the dump id.
801
+
802
+ TARGETDBNAME identifies the (optional) name of the database in which the
803
+ dump is reloaded. If provided, the database needs to be created beforehand.
804
+
805
+ If TARGETDBNAME is not provided, the dump is reloaded using the database
806
+ name that appears in the dump. In this case, the restore command will create
807
+ the database so it needs to be dropped before running the command.
808
+
809
+ Options:
810
+ --dry-run Simulate change operations.
811
+ --help Show this message and exit.
777
812
  $ pglift database get --help
778
813
  Usage: pglift database get [OPTIONS] NAME
779
814
 
@@ -1023,9 +1058,3 @@ Patroni commands:
1023
1058
  Options:
1024
1059
  --help Show this message and exit.
1025
1060
 
1026
- (Cleanup)
1027
- INFO deleting pgBackRest include directory
1028
- INFO uninstalling base pgBackRest configuration
1029
- INFO deleting pgBackRest log directory
1030
- INFO deleting pgBackRest spool directory
1031
- INFO deleting PostgreSQL log directory (no-eol)
@@ -19,7 +19,7 @@ pytest_plugins = [
19
19
 
20
20
  @pytest.fixture
21
21
  def runner() -> CliRunner:
22
- return CliRunner(mix_stderr=False)
22
+ return CliRunner()
23
23
 
24
24
 
25
25
  @pytest.fixture
@@ -259,7 +259,7 @@ def test_command_as_root(runner: CliRunner, obj: Obj) -> None:
259
259
 
260
260
  @app.command
261
261
  def cmd(instance: system.Instance) -> None:
262
- pass
262
+ """Something for testing."""
263
263
 
264
264
  with patch("pglift_cli.util.is_root", autospec=True, return_value=True) as is_root:
265
265
  result = runner.invoke(app, ["cmd"], obj=obj)
@@ -267,6 +267,13 @@ def test_command_as_root(runner: CliRunner, obj: Obj) -> None:
267
267
  assert result.exit_code == 1
268
268
  assert "Error: pglift cannot be used as root" in result.stderr
269
269
 
270
+ # Invocation with --help is allowed though.
271
+ with patch("pglift_cli.util.is_root", autospec=True, return_value=True) as is_root:
272
+ result = runner.invoke(app, ["cmd", "myinstance", "--help"], obj=obj)
273
+ assert not is_root.called
274
+ assert result.exit_code == 0
275
+ assert "Something for testing." in result.stdout
276
+
270
277
 
271
278
  @pytest.mark.usefixtures("site_settings")
272
279
  def test_instance_commands_completion(obj: Obj) -> None:
@@ -24,8 +24,8 @@ def obj() -> Obj:
24
24
  def test_cli(runner: CliRunner, obj: Obj) -> None:
25
25
  # invoke the CLI with no option, sanity check
26
26
  result = runner.invoke(main.cli, obj=obj)
27
- assert result.exit_code == 0
28
- assert result.stdout.splitlines()[0] == "Usage: cli [OPTIONS] COMMAND [ARGS]..."
27
+ assert result.exit_code == 2
28
+ assert result.stderr.splitlines()[0] == "Usage: cli [OPTIONS] COMMAND [ARGS]..."
29
29
 
30
30
 
31
31
  def test_non_interactive(runner: CliRunner) -> None:
@@ -52,7 +52,7 @@ def test_as_parameters_typeerror() -> None:
52
52
  pass
53
53
 
54
54
 
55
- def test_as_parameters() -> None:
55
+ def test_as_parameters(runner: CliRunner) -> None:
56
56
  @click.command("add-person")
57
57
  @click.option("--exclude-none", is_flag=True, default=False)
58
58
  @model.as_parameters(models.Person, "create")
@@ -66,7 +66,6 @@ def test_as_parameters() -> None:
66
66
  err=True,
67
67
  )
68
68
 
69
- runner = CliRunner(mix_stderr=False)
70
69
  result = runner.invoke(add_person, ["--help"])
71
70
  assert result.exit_code == 0, click_result_traceback(result)
72
71
  assert result.stdout == (
@@ -77,7 +76,6 @@ def test_as_parameters() -> None:
77
76
  "Options:\n"
78
77
  " --exclude-none\n"
79
78
  " --nickname TEXT Your secret nickname. [required]\n"
80
- " --gender [M|F]\n"
81
79
  " --age AGE Age.\n"
82
80
  " --address-street STREET Street lines. (Can be used multiple times.)\n"
83
81
  " --address-zip-code ZIP_CODE ZIP code.\n"
@@ -104,7 +102,6 @@ def test_as_parameters() -> None:
104
102
  "friend",
105
103
  "--exclude-none",
106
104
  "--age=42",
107
- "--gender=F",
108
105
  "--address-street=bd montparnasse",
109
106
  "--address-street=far far away",
110
107
  "--address-town=paris",
@@ -131,7 +128,6 @@ def test_as_parameters() -> None:
131
128
  },
132
129
  "age": 42,
133
130
  "birth": {"date": "1981-02-18"},
134
- "gender": "F",
135
131
  "name": "alice",
136
132
  "nickname": "**********",
137
133
  "relation": "friend",
@@ -174,7 +170,6 @@ def test_as_parameters_update() -> None:
174
170
  "\n"
175
171
  "Options:\n"
176
172
  " --nickname TEXT Your secret nickname. [required]\n"
177
- " --gender [M|F]\n"
178
173
  " --age AGE Age.\n"
179
174
  " --address-zip-code ZIP_CODE ZIP code.\n"
180
175
  " --address-town CITY City.\n"
@@ -279,7 +274,6 @@ def test_unnest() -> None:
279
274
  params = {
280
275
  "name": "alice",
281
276
  "age": 42,
282
- "gender": "F",
283
277
  "address_city": "paris",
284
278
  "address_country": "fr",
285
279
  "address_street": ["bd montparnasse"],
@@ -291,7 +285,6 @@ def test_unnest() -> None:
291
285
  assert model.unnest(models.Person, params) == {
292
286
  "name": "alice",
293
287
  "age": 42,
294
- "gender": "F",
295
288
  "address": {
296
289
  "city": "paris",
297
290
  "coords": {"long": 0, "lat": 1.2},
@@ -330,7 +323,6 @@ def test_parse_params_as() -> None:
330
323
  "relation": "other",
331
324
  "nickname": "la malice",
332
325
  "age": 42,
333
- "gender": "F",
334
326
  "address": address_params,
335
327
  "birth": {"date": "1976-05-04"},
336
328
  }
@@ -339,7 +331,6 @@ def test_parse_params_as() -> None:
339
331
  nickname="la malice",
340
332
  relation="other",
341
333
  age=42,
342
- gender=models.Gender.female,
343
334
  address=address,
344
335
  birth=models.BirthInformation(date=date(1976, 5, 4)), # type: ignore[call-arg]
345
336
  )
@@ -350,7 +341,6 @@ def test_parse_params_as() -> None:
350
341
  "relation": "other",
351
342
  "nickname": "la malice",
352
343
  "age": 42,
353
- "gender": "F",
354
344
  "birth_date": "1976-05-04",
355
345
  }
356
346
  params_nested.update({f"address_{k}": v for k, v in address_params.items()})
@@ -21,7 +21,7 @@ from rich.console import Console
21
21
 
22
22
  from pglift import exceptions
23
23
  from pglift.models import interface, system
24
- from pglift.settings import Settings
24
+ from pglift.settings import PostgreSQLVersion, Settings
25
25
  from pglift.types import ByteSizeType
26
26
  from pglift_cli import util
27
27
 
@@ -346,7 +346,7 @@ def test_asynccommand(runner: CliRunner) -> None:
346
346
 
347
347
 
348
348
  def test_get_instance(
349
- settings: Settings, pg_version: str, instance: system.Instance
349
+ settings: Settings, pg_version: PostgreSQLVersion, instance: system.Instance
350
350
  ) -> None:
351
351
  assert util.get_instance(instance.name, pg_version, settings) == instance
352
352
 
File without changes
File without changes
File without changes
File without changes