pglift-cli 1.3.1__tar.gz → 1.5.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.5.0}/PKG-INFO +12 -4
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/pyproject.toml +1 -1
- pglift_cli-1.5.0/src/pglift_cli/__init__.py +15 -0
- pglift_cli-1.5.0/src/pglift_cli/_settings.py +75 -0
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/src/pglift_cli/database.py +56 -9
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/src/pglift_cli/instance.py +32 -16
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/src/pglift_cli/main.py +13 -15
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/src/pglift_cli/model.py +67 -33
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/src/pglift_cli/pgbackrest/__init__.py +5 -3
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/src/pglift_cli/pgbackrest/repo_path.py +2 -2
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/src/pglift_cli/pgconf.py +11 -4
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/src/pglift_cli/prometheus.py +22 -16
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/src/pglift_cli/role.py +10 -7
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/src/pglift_cli/util.py +139 -35
- pglift_cli-1.3.1/src/pglift_cli/__init__.py +0 -7
- pglift_cli-1.3.1/src/pglift_cli/_settings.py +0 -44
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/.gitignore +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/README.md +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/hatch.toml +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/src/pglift_cli/__main__.py +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/src/pglift_cli/_site.py +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/src/pglift_cli/base.py +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/src/pglift_cli/console.py +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/src/pglift_cli/hookspecs.py +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/src/pglift_cli/patroni.py +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/src/pglift_cli/pm.py +0 -0
- {pglift_cli-1.3.1 → pglift_cli-1.5.0}/src/pglift_cli/postgres.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: pglift_cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.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/
|
|
@@ -30,7 +30,15 @@ Requires-Dist: pydantic>=2.5.0
|
|
|
30
30
|
Requires-Dist: pyyaml>=6.0.1
|
|
31
31
|
Requires-Dist: rich>=11.0.0
|
|
32
32
|
Provides-Extra: dev
|
|
33
|
-
Requires-Dist:
|
|
33
|
+
Requires-Dist: anyio; extra == 'dev'
|
|
34
|
+
Requires-Dist: mypy>=1.10.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: patroni[etcd]>=2.1.5; extra == 'dev'
|
|
36
|
+
Requires-Dist: port-for; extra == 'dev'
|
|
37
|
+
Requires-Dist: prysk[pytest-plugin]>=0.14.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
40
|
+
Requires-Dist: trustme; extra == 'dev'
|
|
41
|
+
Requires-Dist: types-pyyaml>=6.0.12.10; extra == 'dev'
|
|
34
42
|
Provides-Extra: test
|
|
35
43
|
Requires-Dist: anyio; extra == 'test'
|
|
36
44
|
Requires-Dist: patroni[etcd]>=2.1.5; extra == 'test'
|
|
@@ -40,7 +48,7 @@ Requires-Dist: pytest; extra == 'test'
|
|
|
40
48
|
Requires-Dist: pytest-cov; extra == 'test'
|
|
41
49
|
Requires-Dist: trustme; extra == 'test'
|
|
42
50
|
Provides-Extra: typing
|
|
43
|
-
Requires-Dist: mypy>=1.
|
|
51
|
+
Requires-Dist: mypy>=1.10.0; extra == 'typing'
|
|
44
52
|
Requires-Dist: types-pyyaml>=6.0.12.10; extra == 'typing'
|
|
45
53
|
Description-Content-Type: text/markdown
|
|
46
54
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2024 Dalibo
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
import pluggy
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
try:
|
|
12
|
+
loggers = list(os.environ["PGLIFT_LOGGERS"].split(","))
|
|
13
|
+
except KeyError:
|
|
14
|
+
loggers = [__name__, "pglift"]
|
|
15
|
+
hookimpl = pluggy.HookimplMarker(__name__)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2021 Dalibo
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
import warnings
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Annotated, Any, Optional
|
|
8
|
+
|
|
9
|
+
from pydantic import AfterValidator, Field, ValidationInfo
|
|
10
|
+
|
|
11
|
+
from pglift.settings import Settings as BaseSettings
|
|
12
|
+
from pglift.settings import SiteSettings as BaseSiteSettings
|
|
13
|
+
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
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AuditSettings(BaseModel):
|
|
25
|
+
"""Settings for change operations auditing."""
|
|
26
|
+
|
|
27
|
+
path: Annotated[
|
|
28
|
+
Annotated[Path, LogPath],
|
|
29
|
+
Field(description="Log file path"),
|
|
30
|
+
]
|
|
31
|
+
log_format: Annotated[
|
|
32
|
+
str,
|
|
33
|
+
Field(description="Format for log messages"),
|
|
34
|
+
] = "%(levelname)-8s - %(asctime)s - %(name)s - %(message)s"
|
|
35
|
+
date_format: Annotated[
|
|
36
|
+
str,
|
|
37
|
+
Field(description="Date format in log messages"),
|
|
38
|
+
] = "%Y-%m-%d %H:%M:%S"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class CLISettings(BaseModel):
|
|
42
|
+
"""Settings for pglift's command-line interface."""
|
|
43
|
+
|
|
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
|
+
log_format: Annotated[
|
|
53
|
+
str, Field(description="Format for log messages when written to a file")
|
|
54
|
+
] = "%(asctime)s %(levelname)-8s %(name)s - %(message)s"
|
|
55
|
+
|
|
56
|
+
date_format: Annotated[
|
|
57
|
+
str, Field(description="Date format in log messages when written to a file")
|
|
58
|
+
] = "%Y-%m-%d %H:%M:%S"
|
|
59
|
+
|
|
60
|
+
lock_file: Annotated[
|
|
61
|
+
Path, RunPath, Field(description="Path to lock file dedicated to pglift")
|
|
62
|
+
] = Path(".pglift.lock")
|
|
63
|
+
|
|
64
|
+
audit: Annotated[
|
|
65
|
+
Optional[AuditSettings],
|
|
66
|
+
Field(description="Settings for change operations auditing"),
|
|
67
|
+
] = None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Settings(BaseSettings):
|
|
71
|
+
cli: Annotated[CLISettings, Field(default_factory=CLISettings)]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class SiteSettings(Settings, BaseSiteSettings):
|
|
75
|
+
pass
|
|
@@ -7,10 +7,11 @@ 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
|
|
14
|
+
from attrs import asdict
|
|
14
15
|
from pydantic.v1.utils import deep_update
|
|
15
16
|
|
|
16
17
|
from pglift import databases, postgresql, privileges, task
|
|
@@ -19,11 +20,14 @@ from pglift.models import interface, system
|
|
|
19
20
|
from . import model
|
|
20
21
|
from .util import (
|
|
21
22
|
Group,
|
|
23
|
+
ManifestData,
|
|
22
24
|
Obj,
|
|
23
25
|
OutputFormat,
|
|
24
26
|
async_command,
|
|
27
|
+
audit,
|
|
25
28
|
dry_run_option,
|
|
26
29
|
instance_identifier_option,
|
|
30
|
+
manifest_option,
|
|
27
31
|
model_dump,
|
|
28
32
|
output_format_option,
|
|
29
33
|
pass_instance,
|
|
@@ -66,7 +70,7 @@ async def create(
|
|
|
66
70
|
obj: Obj, instance: system.Instance, database: interface.Database
|
|
67
71
|
) -> None:
|
|
68
72
|
"""Create a database in a PostgreSQL instance"""
|
|
69
|
-
with obj.lock:
|
|
73
|
+
with obj.lock, audit():
|
|
70
74
|
async with postgresql.running(instance):
|
|
71
75
|
if await databases.exists(instance, database.name):
|
|
72
76
|
raise click.ClickException("database already exists")
|
|
@@ -84,7 +88,7 @@ async def alter(
|
|
|
84
88
|
obj: Obj, instance: system.Instance, dbname: str, **changes: Any
|
|
85
89
|
) -> None:
|
|
86
90
|
"""Alter a database in a PostgreSQL instance"""
|
|
87
|
-
with obj.lock:
|
|
91
|
+
with obj.lock, audit():
|
|
88
92
|
async with postgresql.running(instance):
|
|
89
93
|
values = (await databases.get(instance, dbname)).model_dump(by_alias=True)
|
|
90
94
|
values = deep_update(values, changes)
|
|
@@ -93,7 +97,7 @@ async def alter(
|
|
|
93
97
|
|
|
94
98
|
|
|
95
99
|
@cli.command("apply", hidden=True)
|
|
96
|
-
@
|
|
100
|
+
@manifest_option
|
|
97
101
|
@output_format_option
|
|
98
102
|
@dry_run_option
|
|
99
103
|
@pass_instance
|
|
@@ -102,16 +106,16 @@ async def alter(
|
|
|
102
106
|
async def apply(
|
|
103
107
|
obj: Obj,
|
|
104
108
|
instance: system.Instance,
|
|
105
|
-
|
|
109
|
+
data: ManifestData,
|
|
106
110
|
output_format: OutputFormat,
|
|
107
111
|
dry_run: bool,
|
|
108
112
|
) -> None:
|
|
109
113
|
"""Apply manifest as a database"""
|
|
110
|
-
database = interface.Database.
|
|
114
|
+
database = interface.Database.model_validate(data)
|
|
111
115
|
if dry_run:
|
|
112
116
|
ret = interface.ApplyResult(change_state=None)
|
|
113
117
|
else:
|
|
114
|
-
with obj.lock:
|
|
118
|
+
with obj.lock, audit():
|
|
115
119
|
async with postgresql.running(instance):
|
|
116
120
|
ret = await databases.apply(instance, database)
|
|
117
121
|
if output_format == OutputFormat.json:
|
|
@@ -169,7 +173,7 @@ async def drop(
|
|
|
169
173
|
obj: Obj, instance: system.Instance, databasedropped: interface.DatabaseDropped
|
|
170
174
|
) -> None:
|
|
171
175
|
"""Drop a database"""
|
|
172
|
-
with obj.lock:
|
|
176
|
+
with obj.lock, audit():
|
|
173
177
|
async with postgresql.running(instance):
|
|
174
178
|
await databases.drop(instance, databasedropped)
|
|
175
179
|
|
|
@@ -249,7 +253,7 @@ async def run(
|
|
|
249
253
|
"--output",
|
|
250
254
|
metavar="DIRECTORY",
|
|
251
255
|
type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
|
|
252
|
-
help="Write dump file(s) to DIRECTORY instead of
|
|
256
|
+
help="Write dump file(s) to DIRECTORY instead of default dumps directory.",
|
|
253
257
|
)
|
|
254
258
|
@click.argument("dbname")
|
|
255
259
|
@pass_instance
|
|
@@ -258,3 +262,46 @@ async def dump(instance: system.Instance, dbname: str, output: Path | None) -> N
|
|
|
258
262
|
"""Dump a database"""
|
|
259
263
|
async with postgresql.running(instance):
|
|
260
264
|
await databases.dump(instance, dbname, output)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@cli.command("dumps")
|
|
268
|
+
@click.argument("dbname", nargs=-1)
|
|
269
|
+
@output_format_option
|
|
270
|
+
@pass_instance
|
|
271
|
+
@async_command
|
|
272
|
+
async def dumps(
|
|
273
|
+
instance: system.Instance, dbname: Sequence[str], output_format: OutputFormat
|
|
274
|
+
) -> None:
|
|
275
|
+
"""List the database dumps
|
|
276
|
+
|
|
277
|
+
Only dumps created in the default dumps directory are listed.
|
|
278
|
+
"""
|
|
279
|
+
values = [asdict(dump) async for dump in databases.dumps(instance, dbnames=dbname)]
|
|
280
|
+
if output_format == OutputFormat.json:
|
|
281
|
+
print_json_for(values)
|
|
282
|
+
else:
|
|
283
|
+
dbnames = ", ".join(dbname) if dbname else "all databases"
|
|
284
|
+
print_table_for(values, lambda d: d, title=f"Dumps for {dbnames}")
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@cli.command("restore")
|
|
288
|
+
@click.argument("dump_id")
|
|
289
|
+
@click.argument("targetdbname", required=False)
|
|
290
|
+
@pass_instance
|
|
291
|
+
@async_command
|
|
292
|
+
async def restore(
|
|
293
|
+
instance: system.Instance, dump_id: str, targetdbname: str | None
|
|
294
|
+
) -> None:
|
|
295
|
+
"""Restore a database dump
|
|
296
|
+
|
|
297
|
+
DUMP_ID identifies the dump id.
|
|
298
|
+
|
|
299
|
+
TARGETDBNAME identifies the (optional) name of the database in which the
|
|
300
|
+
dump is reloaded. If provided, the database needs to be created beforehand.
|
|
301
|
+
|
|
302
|
+
If TARGETDBNAME is not provided, the dump is reloaded using the database
|
|
303
|
+
name that appears in the dump. In this case, the restore command will
|
|
304
|
+
create the database so it needs to be dropped before running the command.
|
|
305
|
+
"""
|
|
306
|
+
async with postgresql.running(instance):
|
|
307
|
+
await databases.restore(instance, dump_id, targetdbname)
|
|
@@ -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
|
|
@@ -22,20 +22,24 @@ from pglift import (
|
|
|
22
22
|
task,
|
|
23
23
|
)
|
|
24
24
|
from pglift.models import interface, system
|
|
25
|
+
from pglift.settings import default_postgresql_version
|
|
25
26
|
from pglift.settings._postgresql import PostgreSQLVersion
|
|
26
|
-
from pglift.types import Status
|
|
27
|
+
from pglift.types import Operation, Status, validation_context
|
|
27
28
|
|
|
28
29
|
from . import _site
|
|
29
30
|
from . import hookspecs as h
|
|
30
31
|
from . import model
|
|
31
32
|
from .util import (
|
|
33
|
+
ManifestData,
|
|
32
34
|
Obj,
|
|
33
35
|
OutputFormat,
|
|
34
36
|
PluggableCommandGroup,
|
|
35
37
|
async_command,
|
|
38
|
+
audit,
|
|
36
39
|
dry_run_option,
|
|
37
40
|
foreground_option,
|
|
38
41
|
instance_identifier,
|
|
42
|
+
manifest_option,
|
|
39
43
|
model_dump,
|
|
40
44
|
output_format_option,
|
|
41
45
|
print_argspec,
|
|
@@ -102,7 +106,7 @@ def cli() -> None:
|
|
|
102
106
|
@async_command
|
|
103
107
|
async def create(obj: Obj, instance: interface.Instance, drop_on_error: bool) -> None:
|
|
104
108
|
"""Initialize a PostgreSQL instance"""
|
|
105
|
-
with obj.lock:
|
|
109
|
+
with obj.lock, audit():
|
|
106
110
|
if instances.exists(instance.name, instance.version, _site.SETTINGS):
|
|
107
111
|
raise click.ClickException("instance already exists")
|
|
108
112
|
async with task.async_transaction(drop_on_error):
|
|
@@ -116,32 +120,44 @@ async def create(obj: Obj, instance: interface.Instance, drop_on_error: bool) ->
|
|
|
116
120
|
@async_command
|
|
117
121
|
async def alter(obj: Obj, instance: system.Instance, **changes: Any) -> None:
|
|
118
122
|
"""Alter PostgreSQL INSTANCE"""
|
|
119
|
-
with obj.lock:
|
|
123
|
+
with obj.lock, audit():
|
|
120
124
|
status = await postgresql.status(instance)
|
|
121
125
|
manifest = await instances._get(instance, status)
|
|
122
126
|
values = manifest.model_dump(by_alias=True, exclude={"settings"})
|
|
123
127
|
values = deep_update(values, changes)
|
|
124
|
-
|
|
128
|
+
# No need for 'settings' in validation_context() as a 'version' key
|
|
129
|
+
# must be present in 'values' when altering.
|
|
130
|
+
with validation_context(operation="update"):
|
|
131
|
+
altered = _site.INSTANCE_MODEL.model_validate(values)
|
|
125
132
|
await instances.apply(
|
|
126
133
|
_site.SETTINGS, altered, _is_running=status == Status.running
|
|
127
134
|
)
|
|
128
135
|
|
|
129
136
|
|
|
130
137
|
@cli.command("apply", hidden=True)
|
|
131
|
-
@
|
|
138
|
+
@manifest_option
|
|
132
139
|
@output_format_option
|
|
133
140
|
@dry_run_option
|
|
134
141
|
@click.pass_obj
|
|
135
142
|
@async_command
|
|
136
143
|
async def apply(
|
|
137
|
-
obj: Obj,
|
|
144
|
+
obj: Obj, data: ManifestData, output_format: OutputFormat, dry_run: bool
|
|
138
145
|
) -> None:
|
|
139
146
|
"""Apply manifest as a PostgreSQL instance"""
|
|
140
|
-
|
|
147
|
+
name, version = data["name"], data.get("version")
|
|
148
|
+
if version is None:
|
|
149
|
+
version = default_postgresql_version(_site.SETTINGS.postgresql)
|
|
150
|
+
elif not isinstance(version, str):
|
|
151
|
+
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):
|
|
156
|
+
instance = _site.INSTANCE_MODEL.model_validate(data)
|
|
141
157
|
if dry_run:
|
|
142
158
|
ret = interface.InstanceApplyResult(change_state=None)
|
|
143
159
|
else:
|
|
144
|
-
with obj.lock:
|
|
160
|
+
with obj.lock, audit():
|
|
145
161
|
ret = await instances.apply(_site.SETTINGS, instance)
|
|
146
162
|
if output_format == OutputFormat.json:
|
|
147
163
|
print_json_for(ret)
|
|
@@ -153,7 +169,7 @@ async def apply(
|
|
|
153
169
|
@async_command
|
|
154
170
|
async def promote(obj: Obj, instance: system.Instance) -> None:
|
|
155
171
|
"""Promote standby PostgreSQL INSTANCE"""
|
|
156
|
-
with obj.lock:
|
|
172
|
+
with obj.lock, audit():
|
|
157
173
|
await instances.promote(instance)
|
|
158
174
|
|
|
159
175
|
|
|
@@ -206,7 +222,7 @@ async def ls(version: PostgreSQLVersion | None, output_format: OutputFormat) ->
|
|
|
206
222
|
@async_command
|
|
207
223
|
async def drop(obj: Obj, instance: tuple[system.Instance, ...]) -> None:
|
|
208
224
|
"""Drop PostgreSQL INSTANCE"""
|
|
209
|
-
with obj.lock:
|
|
225
|
+
with obj.lock, audit():
|
|
210
226
|
for i in instance:
|
|
211
227
|
await instances.drop(i)
|
|
212
228
|
|
|
@@ -254,7 +270,7 @@ async def start(
|
|
|
254
270
|
raise click.UsageError(
|
|
255
271
|
"only one INSTANCE argument may be given with --foreground"
|
|
256
272
|
)
|
|
257
|
-
with obj.lock:
|
|
273
|
+
with obj.lock, audit():
|
|
258
274
|
for i in instance:
|
|
259
275
|
await instances.start(i, foreground=foreground)
|
|
260
276
|
|
|
@@ -268,7 +284,7 @@ async def stop(
|
|
|
268
284
|
obj: Obj, instance: tuple[system.Instance, ...], all_instances: bool
|
|
269
285
|
) -> None:
|
|
270
286
|
"""Stop PostgreSQL INSTANCE"""
|
|
271
|
-
with obj.lock:
|
|
287
|
+
with obj.lock, audit():
|
|
272
288
|
for i in instance:
|
|
273
289
|
await instances.stop(i)
|
|
274
290
|
|
|
@@ -282,7 +298,7 @@ async def reload(
|
|
|
282
298
|
obj: Obj, instance: tuple[system.Instance, ...], all_instances: bool
|
|
283
299
|
) -> None:
|
|
284
300
|
"""Reload PostgreSQL INSTANCE"""
|
|
285
|
-
with obj.lock:
|
|
301
|
+
with obj.lock, audit():
|
|
286
302
|
for i in instance:
|
|
287
303
|
await instances.reload(i)
|
|
288
304
|
|
|
@@ -296,7 +312,7 @@ async def restart(
|
|
|
296
312
|
obj: Obj, instance: tuple[system.Instance, ...], all_instances: bool
|
|
297
313
|
) -> None:
|
|
298
314
|
"""Restart PostgreSQL INSTANCE"""
|
|
299
|
-
with obj.lock:
|
|
315
|
+
with obj.lock, audit():
|
|
300
316
|
for i in instance:
|
|
301
317
|
await instances.restart(i)
|
|
302
318
|
|
|
@@ -452,7 +468,7 @@ async def upgrade(
|
|
|
452
468
|
jobs: int | None,
|
|
453
469
|
) -> None:
|
|
454
470
|
"""Upgrade INSTANCE using pg_upgrade"""
|
|
455
|
-
with obj.lock:
|
|
471
|
+
with obj.lock, audit():
|
|
456
472
|
await postgresql.check_status(instance, Status.not_running)
|
|
457
473
|
async with task.async_transaction():
|
|
458
474
|
new_instance = await instances.upgrade(
|
|
@@ -26,16 +26,16 @@ from pglift import install, ui
|
|
|
26
26
|
from pglift._compat import assert_never
|
|
27
27
|
|
|
28
28
|
from . import __name__ as pkgname
|
|
29
|
-
from . import _site
|
|
29
|
+
from . import _site, loggers
|
|
30
30
|
from ._settings import Settings
|
|
31
31
|
from .base import CLIGroup
|
|
32
32
|
from .console import console as console
|
|
33
33
|
from .util import (
|
|
34
34
|
InteractiveUserInterface,
|
|
35
|
-
LogDisplayer,
|
|
36
35
|
Obj,
|
|
37
36
|
OutputFormat,
|
|
38
37
|
async_command,
|
|
38
|
+
audit,
|
|
39
39
|
output_format_option,
|
|
40
40
|
)
|
|
41
41
|
|
|
@@ -145,10 +145,7 @@ def cli(
|
|
|
145
145
|
stacklevel=1,
|
|
146
146
|
)
|
|
147
147
|
if not context.obj:
|
|
148
|
-
context.obj = Obj(
|
|
149
|
-
displayer=None if log_file else LogDisplayer(),
|
|
150
|
-
debug=debug,
|
|
151
|
-
)
|
|
148
|
+
context.obj = Obj(debug=debug)
|
|
152
149
|
else:
|
|
153
150
|
assert isinstance(context.obj, Obj), context.obj
|
|
154
151
|
|
|
@@ -156,12 +153,9 @@ def cli(
|
|
|
156
153
|
if interactive:
|
|
157
154
|
ui_token = ui.set(InteractiveUserInterface())
|
|
158
155
|
|
|
159
|
-
|
|
160
|
-
for logger in loggers:
|
|
161
|
-
logger.setLevel(logging.DEBUG)
|
|
156
|
+
handler: logging.Handler | rich.logging.RichHandler
|
|
162
157
|
if debug:
|
|
163
158
|
log_level = logging.DEBUG
|
|
164
|
-
handler: logging.Handler | rich.logging.RichHandler
|
|
165
159
|
if log_file or not sys.stderr.isatty():
|
|
166
160
|
if log_file:
|
|
167
161
|
handler = logging.FileHandler(log_file)
|
|
@@ -182,11 +176,15 @@ def cli(
|
|
|
182
176
|
show_path=False,
|
|
183
177
|
highlighter=NullHighlighter(),
|
|
184
178
|
)
|
|
185
|
-
|
|
179
|
+
|
|
180
|
+
for name in loggers:
|
|
181
|
+
logger = logging.getLogger(name)
|
|
182
|
+
logger.setLevel(logging.DEBUG)
|
|
186
183
|
logger.addHandler(handler)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
184
|
+
# Remove rich handler on close since this would pollute all tests
|
|
185
|
+
# stderr otherwise.
|
|
186
|
+
context.call_on_close(partial(logger.removeHandler, handler))
|
|
187
|
+
|
|
190
188
|
# Reset contextvars
|
|
191
189
|
if ui_token is not None:
|
|
192
190
|
context.call_on_close(partial(ui.reset, ui_token))
|
|
@@ -264,7 +262,7 @@ async def site_configure(
|
|
|
264
262
|
|
|
265
263
|
This is an INTERNAL command.
|
|
266
264
|
"""
|
|
267
|
-
with obj.lock:
|
|
265
|
+
with obj.lock, audit():
|
|
268
266
|
if action == "install":
|
|
269
267
|
env = {"SETTINGS": f"@{settings_file}"} if settings_file else {}
|
|
270
268
|
await install.do(_site.SETTINGS, env=env)
|