pglift-cli 1.7.0__tar.gz → 1.8.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 (27) hide show
  1. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/PKG-INFO +6 -6
  2. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/pyproject.toml +3 -3
  3. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/src/pglift_cli/base.py +1 -0
  4. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/src/pglift_cli/instance.py +17 -6
  5. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/src/pglift_cli/pgconf.py +6 -6
  6. pglift_cli-1.8.0/src/pglift_cli/pghba.py +57 -0
  7. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/src/pglift_cli/role.py +12 -3
  8. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/src/pglift_cli/util.py +15 -1
  9. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/.gitignore +0 -0
  10. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/README.md +0 -0
  11. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/hatch.toml +0 -0
  12. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/src/pglift_cli/__init__.py +0 -0
  13. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/src/pglift_cli/__main__.py +0 -0
  14. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/src/pglift_cli/_settings.py +0 -0
  15. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/src/pglift_cli/_site.py +0 -0
  16. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/src/pglift_cli/console.py +0 -0
  17. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/src/pglift_cli/database.py +0 -0
  18. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/src/pglift_cli/hookspecs.py +0 -0
  19. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/src/pglift_cli/main.py +0 -0
  20. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/src/pglift_cli/model.py +0 -0
  21. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/src/pglift_cli/patroni.py +0 -0
  22. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/src/pglift_cli/pgbackrest/__init__.py +0 -0
  23. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/src/pglift_cli/pgbackrest/repo_path.py +0 -0
  24. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/src/pglift_cli/pm.py +0 -0
  25. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/src/pglift_cli/postgres.py +0 -0
  26. {pglift_cli-1.7.0 → pglift_cli-1.8.0}/src/pglift_cli/prometheus.py +0 -0
  27. {pglift_cli-1.7.0 → pglift_cli-1.8.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.7.0
3
+ Version: 1.8.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/
@@ -28,16 +28,16 @@ Requires-Dist: pluggy
28
28
  Requires-Dist: psycopg>=3.1
29
29
  Requires-Dist: pydantic>=2.5.0
30
30
  Requires-Dist: pyyaml>=6.0.1
31
- Requires-Dist: rich>=11.0.0
31
+ Requires-Dist: rich!=13.9.0,>=11.0.0
32
32
  Provides-Extra: dev
33
33
  Requires-Dist: anyio; extra == 'dev'
34
- Requires-Dist: mypy!=1.11.*,>=1.10.0; (python_version < '3.10') and extra == 'dev'
34
+ Requires-Dist: mypy!=1.11.*,!=1.12.*,>=1.10.0; (python_version < '3.10') and extra == 'dev'
35
35
  Requires-Dist: mypy>=1.10.0; (python_version >= '3.10') and extra == 'dev'
36
36
  Requires-Dist: patroni[etcd]>=2.1.5; extra == 'dev'
37
37
  Requires-Dist: port-for; extra == 'dev'
38
38
  Requires-Dist: prysk[pytest-plugin]>=0.14.0; extra == 'dev'
39
- Requires-Dist: pytest; extra == 'dev'
40
39
  Requires-Dist: pytest-cov; extra == 'dev'
40
+ Requires-Dist: pytest>=8; extra == 'dev'
41
41
  Requires-Dist: trustme; extra == 'dev'
42
42
  Requires-Dist: types-pyyaml>=6.0.12.10; extra == 'dev'
43
43
  Provides-Extra: test
@@ -45,11 +45,11 @@ Requires-Dist: anyio; extra == 'test'
45
45
  Requires-Dist: patroni[etcd]>=2.1.5; extra == 'test'
46
46
  Requires-Dist: port-for; extra == 'test'
47
47
  Requires-Dist: prysk[pytest-plugin]>=0.14.0; extra == 'test'
48
- Requires-Dist: pytest; extra == 'test'
49
48
  Requires-Dist: pytest-cov; extra == 'test'
49
+ Requires-Dist: pytest>=8; extra == 'test'
50
50
  Requires-Dist: trustme; extra == 'test'
51
51
  Provides-Extra: typing
52
- Requires-Dist: mypy!=1.11.*,>=1.10.0; (python_version < '3.10') and extra == 'typing'
52
+ Requires-Dist: mypy!=1.11.*,!=1.12.*,>=1.10.0; (python_version < '3.10') and extra == 'typing'
53
53
  Requires-Dist: mypy>=1.10.0; (python_version >= '3.10') and extra == 'typing'
54
54
  Requires-Dist: types-pyyaml>=6.0.12.10; extra == 'typing'
55
55
  Description-Content-Type: text/markdown
@@ -43,7 +43,7 @@ dependencies = [
43
43
  "pluggy",
44
44
  "psycopg >= 3.1",
45
45
  "pydantic >= 2.5.0",
46
- "rich >= 11.0.0",
46
+ "rich >= 11.0.0, != 13.9.0",
47
47
  ]
48
48
 
49
49
  [project.optional-dependencies]
@@ -52,13 +52,13 @@ test = [
52
52
  "patroni[etcd] >= 2.1.5",
53
53
  "port-for",
54
54
  "prysk[pytest-plugin] >= 0.14.0",
55
- "pytest",
55
+ "pytest >= 8",
56
56
  "pytest-cov",
57
57
  "trustme",
58
58
  ]
59
59
  typing = [
60
60
  "mypy >= 1.10.0 ; python_version >= '3.10'",
61
- "mypy >= 1.10.0, != 1.11.* ; python_version < '3.10'",
61
+ "mypy >= 1.10.0, != 1.11.*, != 1.12.* ; python_version < '3.10'",
62
62
  "types-PyYAML >= 6.0.12.10",
63
63
  ]
64
64
  dev = [
@@ -26,6 +26,7 @@ class CLIGroup(click.Group):
26
26
  "role",
27
27
  "database",
28
28
  "postgres",
29
+ "pghba",
29
30
  ]
30
31
 
31
32
  @classmethod
@@ -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
- "update" if instances.exists(name, version, _site.SETTINGS) else "create"
154
- )
155
- with validation_context(operation=op, settings=_site.SETTINGS):
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([i], partial(model_dump, exclude=exclude), box=None)
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")
@@ -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
- changes = await instances.configure(
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
- changes = await instances.configure(
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
- changes = await instances.configure(
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" if await roles.exists(instance, name=data["name"]) else "create"
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,9 @@ 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(rls, partial(model_dump, mode="pretty", exclude={"in_roles"}))
160
+ print_table_for(
161
+ rls, partial(model_dump, mode="pretty", exclude={"in_roles", "hba_records"})
162
+ )
156
163
 
157
164
 
158
165
  @cli.command("get")
@@ -170,7 +177,9 @@ async def get(
170
177
  print_json_for(model_dump(r))
171
178
  else:
172
179
  print_table_for(
173
- [r], partial(model_dump, mode="pretty", exclude={"in_roles"}), box=None
180
+ [r],
181
+ partial(model_dump, mode="pretty", exclude={"in_roles", "hba_records"}),
182
+ box=None,
174
183
  )
175
184
 
176
185
 
@@ -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
 
@@ -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__(self, *, debug: bool = False) -> None:
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)
File without changes
File without changes
File without changes