pglift-cli 1.3.1__tar.gz → 1.4.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.3.1 → pglift_cli-1.4.0}/PKG-INFO +2 -2
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/src/pglift_cli/database.py +6 -4
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/src/pglift_cli/instance.py +13 -6
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/src/pglift_cli/model.py +13 -5
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/src/pglift_cli/pgbackrest/__init__.py +3 -2
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/src/pglift_cli/prometheus.py +16 -11
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/src/pglift_cli/role.py +6 -4
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/src/pglift_cli/util.py +38 -3
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/.gitignore +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/README.md +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/hatch.toml +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/pyproject.toml +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/src/pglift_cli/__init__.py +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/src/pglift_cli/__main__.py +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/src/pglift_cli/_settings.py +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/src/pglift_cli/_site.py +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/src/pglift_cli/base.py +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/src/pglift_cli/console.py +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/src/pglift_cli/hookspecs.py +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/src/pglift_cli/main.py +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/src/pglift_cli/patroni.py +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/src/pglift_cli/pgbackrest/repo_path.py +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/src/pglift_cli/pgconf.py +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/src/pglift_cli/pm.py +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.4.0}/src/pglift_cli/postgres.py +0 -0
|
@@ -7,7 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
import functools
|
|
8
8
|
from collections.abc import Sequence
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from typing import
|
|
10
|
+
from typing import Any
|
|
11
11
|
|
|
12
12
|
import click
|
|
13
13
|
import psycopg
|
|
@@ -19,11 +19,13 @@ from pglift.models import interface, system
|
|
|
19
19
|
from . import model
|
|
20
20
|
from .util import (
|
|
21
21
|
Group,
|
|
22
|
+
ManifestData,
|
|
22
23
|
Obj,
|
|
23
24
|
OutputFormat,
|
|
24
25
|
async_command,
|
|
25
26
|
dry_run_option,
|
|
26
27
|
instance_identifier_option,
|
|
28
|
+
manifest_option,
|
|
27
29
|
model_dump,
|
|
28
30
|
output_format_option,
|
|
29
31
|
pass_instance,
|
|
@@ -93,7 +95,7 @@ async def alter(
|
|
|
93
95
|
|
|
94
96
|
|
|
95
97
|
@cli.command("apply", hidden=True)
|
|
96
|
-
@
|
|
98
|
+
@manifest_option
|
|
97
99
|
@output_format_option
|
|
98
100
|
@dry_run_option
|
|
99
101
|
@pass_instance
|
|
@@ -102,12 +104,12 @@ async def alter(
|
|
|
102
104
|
async def apply(
|
|
103
105
|
obj: Obj,
|
|
104
106
|
instance: system.Instance,
|
|
105
|
-
|
|
107
|
+
data: ManifestData,
|
|
106
108
|
output_format: OutputFormat,
|
|
107
109
|
dry_run: bool,
|
|
108
110
|
) -> None:
|
|
109
111
|
"""Apply manifest as a database"""
|
|
110
|
-
database = interface.Database.
|
|
112
|
+
database = interface.Database.model_validate(data)
|
|
111
113
|
if dry_run:
|
|
112
114
|
ret = interface.ApplyResult(change_state=None)
|
|
113
115
|
else:
|
|
@@ -7,7 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
import os
|
|
8
8
|
from collections.abc import Sequence
|
|
9
9
|
from functools import partial
|
|
10
|
-
from typing import
|
|
10
|
+
from typing import Any
|
|
11
11
|
|
|
12
12
|
import click
|
|
13
13
|
from pydantic.v1.utils import deep_update
|
|
@@ -23,12 +23,13 @@ from pglift import (
|
|
|
23
23
|
)
|
|
24
24
|
from pglift.models import interface, system
|
|
25
25
|
from pglift.settings._postgresql import PostgreSQLVersion
|
|
26
|
-
from pglift.types import Status
|
|
26
|
+
from pglift.types import Status, validation_context
|
|
27
27
|
|
|
28
28
|
from . import _site
|
|
29
29
|
from . import hookspecs as h
|
|
30
30
|
from . import model
|
|
31
31
|
from .util import (
|
|
32
|
+
ManifestData,
|
|
32
33
|
Obj,
|
|
33
34
|
OutputFormat,
|
|
34
35
|
PluggableCommandGroup,
|
|
@@ -36,6 +37,7 @@ from .util import (
|
|
|
36
37
|
dry_run_option,
|
|
37
38
|
foreground_option,
|
|
38
39
|
instance_identifier,
|
|
40
|
+
manifest_option,
|
|
39
41
|
model_dump,
|
|
40
42
|
output_format_option,
|
|
41
43
|
print_argspec,
|
|
@@ -121,23 +123,28 @@ async def alter(obj: Obj, instance: system.Instance, **changes: Any) -> None:
|
|
|
121
123
|
manifest = await instances._get(instance, status)
|
|
122
124
|
values = manifest.model_dump(by_alias=True, exclude={"settings"})
|
|
123
125
|
values = deep_update(values, changes)
|
|
124
|
-
|
|
126
|
+
with validation_context({"operation": "update"}):
|
|
127
|
+
altered = _site.INSTANCE_MODEL.model_validate(values)
|
|
125
128
|
await instances.apply(
|
|
126
129
|
_site.SETTINGS, altered, _is_running=status == Status.running
|
|
127
130
|
)
|
|
128
131
|
|
|
129
132
|
|
|
130
133
|
@cli.command("apply", hidden=True)
|
|
131
|
-
@
|
|
134
|
+
@manifest_option
|
|
132
135
|
@output_format_option
|
|
133
136
|
@dry_run_option
|
|
134
137
|
@click.pass_obj
|
|
135
138
|
@async_command
|
|
136
139
|
async def apply(
|
|
137
|
-
obj: Obj,
|
|
140
|
+
obj: Obj, data: ManifestData, output_format: OutputFormat, dry_run: bool
|
|
138
141
|
) -> None:
|
|
139
142
|
"""Apply manifest as a PostgreSQL instance"""
|
|
140
|
-
|
|
143
|
+
name, version = data["name"], data.get("version")
|
|
144
|
+
assert isinstance(version, (str, type(None)))
|
|
145
|
+
op = "update" if instances.exists(name, version, _site.SETTINGS) else "create"
|
|
146
|
+
with validation_context({"operation": op}):
|
|
147
|
+
instance = _site.INSTANCE_MODEL.model_validate(data)
|
|
141
148
|
if dry_run:
|
|
142
149
|
ret = interface.InstanceApplyResult(change_state=None)
|
|
143
150
|
else:
|
|
@@ -20,10 +20,15 @@ import pydantic
|
|
|
20
20
|
from pydantic.fields import FieldInfo
|
|
21
21
|
from pydantic.v1.utils import deep_update, lenient_issubclass
|
|
22
22
|
|
|
23
|
-
from pglift import exceptions
|
|
24
23
|
from pglift._compat import zip
|
|
25
|
-
from pglift.models.helpers import
|
|
26
|
-
from pglift.types import
|
|
24
|
+
from pglift.models.helpers import is_optional, optional_type
|
|
25
|
+
from pglift.types import (
|
|
26
|
+
CLIConfig,
|
|
27
|
+
Operation,
|
|
28
|
+
StrEnum,
|
|
29
|
+
field_annotation,
|
|
30
|
+
validation_context,
|
|
31
|
+
)
|
|
27
32
|
|
|
28
33
|
logger = logging.getLogger(__name__)
|
|
29
34
|
|
|
@@ -94,7 +99,10 @@ def as_parameters(
|
|
|
94
99
|
@functools.wraps(f)
|
|
95
100
|
def callback(**kwargs: Any) -> Any:
|
|
96
101
|
args = params_to_modelargs(kwargs)
|
|
97
|
-
with
|
|
102
|
+
with (
|
|
103
|
+
catch_validationerror(*paramspecs),
|
|
104
|
+
validation_context({"operation": operation}),
|
|
105
|
+
):
|
|
98
106
|
model = parse_params_as(model_type, args)
|
|
99
107
|
kwargs[model_argname] = model
|
|
100
108
|
return f(**kwargs)
|
|
@@ -336,7 +344,7 @@ def choices_from_enum(e: type[enum.Enum]) -> list[Any]:
|
|
|
336
344
|
def catch_validationerror(*paramspec: ParamSpec) -> Iterator[None]:
|
|
337
345
|
try:
|
|
338
346
|
yield None
|
|
339
|
-
except
|
|
347
|
+
except pydantic.ValidationError as e:
|
|
340
348
|
errors = e.errors()
|
|
341
349
|
for pspec in paramspec:
|
|
342
350
|
for err in errors:
|
|
@@ -12,8 +12,9 @@ import click
|
|
|
12
12
|
|
|
13
13
|
from pglift import pgbackrest, postgresql, types
|
|
14
14
|
from pglift.models import system
|
|
15
|
-
from pglift.pgbackrest import base
|
|
15
|
+
from pglift.pgbackrest import base
|
|
16
16
|
from pglift.pgbackrest import register_if as register_if # noqa: F401
|
|
17
|
+
from pglift.pgbackrest.models.system import Service
|
|
17
18
|
|
|
18
19
|
from .. import _site, hookimpl
|
|
19
20
|
from ..util import (
|
|
@@ -43,7 +44,7 @@ def pgbackrest_proxy(
|
|
|
43
44
|
context: click.Context, /, command: tuple[str, ...], **kwargs: Any
|
|
44
45
|
) -> None:
|
|
45
46
|
"""Proxy to pgbackrest operations on an instance"""
|
|
46
|
-
s = context.obj.instance.service(
|
|
47
|
+
s = context.obj.instance.service(Service)
|
|
47
48
|
settings = pgbackrest.get_settings(_site.SETTINGS)
|
|
48
49
|
cmd_args = base.make_cmd(s.stanza, settings, *command)
|
|
49
50
|
try:
|
|
@@ -5,23 +5,25 @@
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
7
|
from functools import partial
|
|
8
|
-
from typing import IO
|
|
9
8
|
|
|
10
9
|
import click
|
|
11
10
|
|
|
12
|
-
from pglift import exceptions, prometheus, task
|
|
11
|
+
from pglift import exceptions, prometheus, task, types
|
|
13
12
|
from pglift.models import interface
|
|
14
|
-
from pglift.prometheus import impl
|
|
13
|
+
from pglift.prometheus import impl
|
|
15
14
|
from pglift.prometheus import register_if as register_if # noqa: F401
|
|
15
|
+
from pglift.prometheus.models.interface import PostgresExporter
|
|
16
16
|
|
|
17
17
|
from . import _site, hookimpl, model
|
|
18
18
|
from .util import (
|
|
19
19
|
Group,
|
|
20
|
+
ManifestData,
|
|
20
21
|
Obj,
|
|
21
22
|
OutputFormat,
|
|
22
23
|
async_command,
|
|
23
24
|
dry_run_option,
|
|
24
25
|
foreground_option,
|
|
26
|
+
manifest_option,
|
|
25
27
|
output_format_option,
|
|
26
28
|
print_argspec,
|
|
27
29
|
print_json_for,
|
|
@@ -33,7 +35,7 @@ from .util import (
|
|
|
33
35
|
@click.option(
|
|
34
36
|
"--schema",
|
|
35
37
|
is_flag=True,
|
|
36
|
-
callback=partial(print_schema, model=
|
|
38
|
+
callback=partial(print_schema, model=PostgresExporter),
|
|
37
39
|
expose_value=False,
|
|
38
40
|
is_eager=True,
|
|
39
41
|
help="Print the JSON schema of postgres_exporter model and exit.",
|
|
@@ -41,7 +43,7 @@ from .util import (
|
|
|
41
43
|
@click.option(
|
|
42
44
|
"--ansible-argspec",
|
|
43
45
|
is_flag=True,
|
|
44
|
-
callback=partial(print_argspec, model=
|
|
46
|
+
callback=partial(print_argspec, model=PostgresExporter),
|
|
45
47
|
expose_value=False,
|
|
46
48
|
is_eager=True,
|
|
47
49
|
hidden=True,
|
|
@@ -52,20 +54,23 @@ def cli() -> None:
|
|
|
52
54
|
|
|
53
55
|
|
|
54
56
|
@cli.command("apply")
|
|
55
|
-
@
|
|
57
|
+
@manifest_option
|
|
56
58
|
@output_format_option
|
|
57
59
|
@dry_run_option
|
|
58
60
|
@click.pass_obj
|
|
59
61
|
@async_command
|
|
60
62
|
async def apply(
|
|
61
|
-
obj: Obj,
|
|
63
|
+
obj: Obj, data: ManifestData, output_format: OutputFormat, dry_run: bool
|
|
62
64
|
) -> None:
|
|
63
65
|
"""Apply manifest as a Prometheus postgres_exporter."""
|
|
64
|
-
|
|
66
|
+
settings = prometheus.get_settings(_site.SETTINGS)
|
|
67
|
+
name = data["name"]
|
|
68
|
+
op = "update" if impl.exists(name, settings) else "create"
|
|
69
|
+
with types.validation_context({"operation": op}):
|
|
70
|
+
exporter = PostgresExporter.model_validate(data)
|
|
65
71
|
if dry_run:
|
|
66
72
|
ret = interface.ApplyResult(change_state=None)
|
|
67
73
|
else:
|
|
68
|
-
settings = prometheus.get_settings(_site.SETTINGS)
|
|
69
74
|
with obj.lock:
|
|
70
75
|
ret = await impl.apply(exporter, _site.SETTINGS, settings)
|
|
71
76
|
if output_format == OutputFormat.json:
|
|
@@ -73,10 +78,10 @@ async def apply(
|
|
|
73
78
|
|
|
74
79
|
|
|
75
80
|
@cli.command("install")
|
|
76
|
-
@model.as_parameters(
|
|
81
|
+
@model.as_parameters(PostgresExporter, "create")
|
|
77
82
|
@click.pass_obj
|
|
78
83
|
@async_command
|
|
79
|
-
async def install(obj: Obj, postgresexporter:
|
|
84
|
+
async def install(obj: Obj, postgresexporter: PostgresExporter) -> None:
|
|
80
85
|
"""Install the service for a (non-local) instance."""
|
|
81
86
|
settings = prometheus.get_settings(_site.SETTINGS)
|
|
82
87
|
with obj.lock:
|
|
@@ -6,7 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
from collections.abc import Sequence
|
|
8
8
|
from functools import partial
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import Any
|
|
10
10
|
|
|
11
11
|
import click
|
|
12
12
|
from pydantic.v1.utils import deep_update
|
|
@@ -17,11 +17,13 @@ from pglift.models import interface, system
|
|
|
17
17
|
from . import _site, model
|
|
18
18
|
from .util import (
|
|
19
19
|
Group,
|
|
20
|
+
ManifestData,
|
|
20
21
|
Obj,
|
|
21
22
|
OutputFormat,
|
|
22
23
|
async_command,
|
|
23
24
|
dry_run_option,
|
|
24
25
|
instance_identifier_option,
|
|
26
|
+
manifest_option,
|
|
25
27
|
model_dump,
|
|
26
28
|
output_format_option,
|
|
27
29
|
pass_instance,
|
|
@@ -100,7 +102,7 @@ async def alter(
|
|
|
100
102
|
|
|
101
103
|
|
|
102
104
|
@cli.command("apply", hidden=True)
|
|
103
|
-
@
|
|
105
|
+
@manifest_option
|
|
104
106
|
@output_format_option
|
|
105
107
|
@dry_run_option
|
|
106
108
|
@pass_instance
|
|
@@ -109,12 +111,12 @@ async def alter(
|
|
|
109
111
|
async def apply(
|
|
110
112
|
obj: Obj,
|
|
111
113
|
instance: system.Instance,
|
|
112
|
-
|
|
114
|
+
data: ManifestData,
|
|
113
115
|
output_format: OutputFormat,
|
|
114
116
|
dry_run: bool,
|
|
115
117
|
) -> None:
|
|
116
118
|
"""Apply manifest as a role"""
|
|
117
|
-
role = _site.ROLE_MODEL.
|
|
119
|
+
role = _site.ROLE_MODEL.model_validate(data)
|
|
118
120
|
if dry_run:
|
|
119
121
|
ret = interface.ApplyResult(change_state=None)
|
|
120
122
|
else:
|
|
@@ -17,7 +17,7 @@ import typing
|
|
|
17
17
|
from collections.abc import Coroutine, Iterable, Iterator, Sequence
|
|
18
18
|
from contextlib import contextmanager
|
|
19
19
|
from functools import cache, cached_property, singledispatch, wraps
|
|
20
|
-
from typing import Any, Callable, TypeVar
|
|
20
|
+
from typing import IO, Any, Callable, TypedDict, TypeVar
|
|
21
21
|
|
|
22
22
|
import click
|
|
23
23
|
import filelock
|
|
@@ -26,6 +26,7 @@ import pydantic
|
|
|
26
26
|
import pydantic_core
|
|
27
27
|
import rich
|
|
28
28
|
import rich.prompt
|
|
29
|
+
import yaml
|
|
29
30
|
from click.shell_completion import CompletionItem
|
|
30
31
|
from rich.console import Console
|
|
31
32
|
from rich.table import Table
|
|
@@ -364,6 +365,35 @@ instance_identifier_option = click.option(
|
|
|
364
365
|
)
|
|
365
366
|
|
|
366
367
|
|
|
368
|
+
def yaml_load(
|
|
369
|
+
ctx: click.Context, param: click.Parameter, value: IO[str]
|
|
370
|
+
) -> dict[str, Any]:
|
|
371
|
+
try:
|
|
372
|
+
data = yaml.safe_load(value)
|
|
373
|
+
except yaml.YAMLError as e:
|
|
374
|
+
raise click.BadParameter(f"invalid YAML: {e}") from e
|
|
375
|
+
if not isinstance(data, dict):
|
|
376
|
+
raise click.BadParameter(f"invalid YAML: expecting an object, got {type(data)}")
|
|
377
|
+
if "name" not in data:
|
|
378
|
+
raise click.BadParameter("invalid YAML: missing required 'name' field")
|
|
379
|
+
return data
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
manifest_option = click.option(
|
|
383
|
+
"-f",
|
|
384
|
+
"--file",
|
|
385
|
+
"data",
|
|
386
|
+
type=click.File("r"),
|
|
387
|
+
metavar="MANIFEST",
|
|
388
|
+
required=True,
|
|
389
|
+
callback=yaml_load,
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
class ManifestData(TypedDict, total=False):
|
|
394
|
+
name: str
|
|
395
|
+
|
|
396
|
+
|
|
367
397
|
class OutputFormat(AutoStrEnum):
|
|
368
398
|
"""Output format"""
|
|
369
399
|
|
|
@@ -517,7 +547,7 @@ class Command(click.Command):
|
|
|
517
547
|
except exceptions.Cancelled as e:
|
|
518
548
|
logger.warning(str(e))
|
|
519
549
|
raise click.Abort from None
|
|
520
|
-
except
|
|
550
|
+
except pydantic.ValidationError as e:
|
|
521
551
|
raise click.ClickException(str(e)) from None
|
|
522
552
|
except exceptions.Error as e:
|
|
523
553
|
logger.debug("an internal error occurred", exc_info=obj.debug)
|
|
@@ -529,7 +559,12 @@ class Command(click.Command):
|
|
|
529
559
|
msg += f"\n{e.stdout}"
|
|
530
560
|
raise click.ClickException(msg) from None
|
|
531
561
|
except psycopg.DatabaseError as e:
|
|
532
|
-
logger.debug(
|
|
562
|
+
logger.debug(
|
|
563
|
+
"a database error occurred: %s (SQLSTATE=%s)",
|
|
564
|
+
e,
|
|
565
|
+
e.sqlstate,
|
|
566
|
+
exc_info=obj.debug,
|
|
567
|
+
)
|
|
533
568
|
raise click.ClickException(str(e).strip()) from None
|
|
534
569
|
|
|
535
570
|
|
|
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
|