pglift-cli 1.6.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.
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/PKG-INFO +8 -6
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/pyproject.toml +4 -3
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/src/pglift_cli/base.py +5 -1
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/src/pglift_cli/database.py +53 -39
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/src/pglift_cli/instance.py +56 -31
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/src/pglift_cli/main.py +2 -2
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/src/pglift_cli/model.py +151 -49
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/src/pglift_cli/pgbackrest/__init__.py +3 -3
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/src/pglift_cli/pgconf.py +19 -15
- pglift_cli-1.8.0/src/pglift_cli/pghba.py +57 -0
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/src/pglift_cli/postgres.py +1 -1
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/src/pglift_cli/prometheus.py +2 -2
- pglift_cli-1.8.0/src/pglift_cli/py.typed +0 -0
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/src/pglift_cli/role.py +69 -27
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/src/pglift_cli/util.py +100 -44
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/.gitignore +0 -0
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/README.md +0 -0
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/hatch.toml +0 -0
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/src/pglift_cli/__init__.py +0 -0
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/src/pglift_cli/__main__.py +0 -0
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/src/pglift_cli/_settings.py +0 -0
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/src/pglift_cli/_site.py +0 -0
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/src/pglift_cli/console.py +0 -0
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/src/pglift_cli/hookspecs.py +0 -0
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/src/pglift_cli/patroni.py +0 -0
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/src/pglift_cli/pgbackrest/repo_path.py +0 -0
- {pglift_cli-1.6.0 → pglift_cli-1.8.0}/src/pglift_cli/pm.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pglift_cli
|
|
3
|
-
Version: 1.
|
|
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,15 +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
|
|
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
|
|
34
|
+
Requires-Dist: mypy!=1.11.*,!=1.12.*,>=1.10.0; (python_version < '3.10') and extra == 'dev'
|
|
35
|
+
Requires-Dist: mypy>=1.10.0; (python_version >= '3.10') and extra == 'dev'
|
|
35
36
|
Requires-Dist: patroni[etcd]>=2.1.5; extra == 'dev'
|
|
36
37
|
Requires-Dist: port-for; extra == 'dev'
|
|
37
38
|
Requires-Dist: prysk[pytest-plugin]>=0.14.0; extra == 'dev'
|
|
38
|
-
Requires-Dist: pytest; extra == 'dev'
|
|
39
39
|
Requires-Dist: pytest-cov; extra == 'dev'
|
|
40
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
40
41
|
Requires-Dist: trustme; extra == 'dev'
|
|
41
42
|
Requires-Dist: types-pyyaml>=6.0.12.10; extra == 'dev'
|
|
42
43
|
Provides-Extra: test
|
|
@@ -44,11 +45,12 @@ Requires-Dist: anyio; extra == 'test'
|
|
|
44
45
|
Requires-Dist: patroni[etcd]>=2.1.5; extra == 'test'
|
|
45
46
|
Requires-Dist: port-for; extra == 'test'
|
|
46
47
|
Requires-Dist: prysk[pytest-plugin]>=0.14.0; extra == 'test'
|
|
47
|
-
Requires-Dist: pytest; extra == 'test'
|
|
48
48
|
Requires-Dist: pytest-cov; extra == 'test'
|
|
49
|
+
Requires-Dist: pytest>=8; extra == 'test'
|
|
49
50
|
Requires-Dist: trustme; extra == 'test'
|
|
50
51
|
Provides-Extra: typing
|
|
51
|
-
Requires-Dist: mypy
|
|
52
|
+
Requires-Dist: mypy!=1.11.*,!=1.12.*,>=1.10.0; (python_version < '3.10') and extra == 'typing'
|
|
53
|
+
Requires-Dist: mypy>=1.10.0; (python_version >= '3.10') and extra == 'typing'
|
|
52
54
|
Requires-Dist: types-pyyaml>=6.0.12.10; extra == 'typing'
|
|
53
55
|
Description-Content-Type: text/markdown
|
|
54
56
|
|
|
@@ -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,12 +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
|
-
"mypy >= 1.10.0",
|
|
60
|
+
"mypy >= 1.10.0 ; python_version >= '3.10'",
|
|
61
|
+
"mypy >= 1.10.0, != 1.11.*, != 1.12.* ; python_version < '3.10'",
|
|
61
62
|
"types-PyYAML >= 6.0.12.10",
|
|
62
63
|
]
|
|
63
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
|
|
@@ -38,7 +39,10 @@ class CLIGroup(click.Group):
|
|
|
38
39
|
|
|
39
40
|
def list_commands(self, context: click.Context) -> list[str]:
|
|
40
41
|
main_commands = super().list_commands(context)
|
|
41
|
-
plugins_commands: list[str] = sorted(
|
|
42
|
+
plugins_commands: list[str] = sorted(
|
|
43
|
+
g.name # type: ignore[misc]
|
|
44
|
+
for g in hooks(_site.PLUGIN_MANAGER, h.command)
|
|
45
|
+
)
|
|
42
46
|
return main_commands + self.submodules + plugins_commands
|
|
43
47
|
|
|
44
48
|
def get_command(self, context: click.Context, name: str) -> click.Command | None:
|
|
@@ -30,7 +30,7 @@ from .util import (
|
|
|
30
30
|
manifest_option,
|
|
31
31
|
model_dump,
|
|
32
32
|
output_format_option,
|
|
33
|
-
|
|
33
|
+
pass_postgresql_instance,
|
|
34
34
|
print_argspec,
|
|
35
35
|
print_json_for,
|
|
36
36
|
print_schema,
|
|
@@ -63,11 +63,11 @@ def cli(**kwargs: Any) -> None:
|
|
|
63
63
|
|
|
64
64
|
@cli.command("create")
|
|
65
65
|
@model.as_parameters(interface.Database, "create")
|
|
66
|
-
@
|
|
66
|
+
@pass_postgresql_instance
|
|
67
67
|
@click.pass_obj
|
|
68
68
|
@async_command
|
|
69
69
|
async def create(
|
|
70
|
-
obj: Obj, instance: system.
|
|
70
|
+
obj: Obj, instance: system.PostgreSQLInstance, database: interface.Database
|
|
71
71
|
) -> None:
|
|
72
72
|
"""Create a database in a PostgreSQL instance"""
|
|
73
73
|
with obj.lock, audit():
|
|
@@ -79,13 +79,13 @@ async def create(
|
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
@cli.command("alter") # type: ignore[arg-type]
|
|
82
|
-
@model.as_parameters(interface.Database, "update"
|
|
82
|
+
@model.as_parameters(interface.Database, "update")
|
|
83
83
|
@click.argument("dbname")
|
|
84
|
-
@
|
|
84
|
+
@pass_postgresql_instance
|
|
85
85
|
@click.pass_obj
|
|
86
86
|
@async_command
|
|
87
87
|
async def alter(
|
|
88
|
-
obj: Obj, instance: system.
|
|
88
|
+
obj: Obj, instance: system.PostgreSQLInstance, dbname: str, **changes: Any
|
|
89
89
|
) -> None:
|
|
90
90
|
"""Alter a database in a PostgreSQL instance"""
|
|
91
91
|
with obj.lock, audit():
|
|
@@ -100,14 +100,14 @@ async def alter(
|
|
|
100
100
|
@manifest_option
|
|
101
101
|
@output_format_option
|
|
102
102
|
@dry_run_option
|
|
103
|
-
@
|
|
103
|
+
@pass_postgresql_instance
|
|
104
104
|
@click.pass_obj
|
|
105
105
|
@async_command
|
|
106
106
|
async def apply(
|
|
107
107
|
obj: Obj,
|
|
108
|
-
instance: system.
|
|
108
|
+
instance: system.PostgreSQLInstance,
|
|
109
109
|
data: ManifestData,
|
|
110
|
-
output_format: OutputFormat,
|
|
110
|
+
output_format: OutputFormat | None,
|
|
111
111
|
dry_run: bool,
|
|
112
112
|
) -> None:
|
|
113
113
|
"""Apply manifest as a database"""
|
|
@@ -118,38 +118,44 @@ async def apply(
|
|
|
118
118
|
with obj.lock, audit():
|
|
119
119
|
async with postgresql.running(instance):
|
|
120
120
|
ret = await databases.apply(instance, database)
|
|
121
|
-
if output_format ==
|
|
121
|
+
if output_format == "json":
|
|
122
122
|
print_json_for(ret)
|
|
123
123
|
|
|
124
124
|
|
|
125
125
|
@cli.command("get")
|
|
126
126
|
@output_format_option
|
|
127
127
|
@click.argument("name")
|
|
128
|
-
@
|
|
128
|
+
@pass_postgresql_instance
|
|
129
129
|
@async_command
|
|
130
130
|
async def get(
|
|
131
|
-
instance: system.
|
|
131
|
+
instance: system.PostgreSQLInstance, name: str, output_format: OutputFormat | None
|
|
132
132
|
) -> None:
|
|
133
133
|
"""Get the description of a database"""
|
|
134
134
|
async with postgresql.running(instance):
|
|
135
135
|
db = await databases.get(instance, name)
|
|
136
|
-
if output_format ==
|
|
136
|
+
if output_format == "json":
|
|
137
137
|
print_json_for(model_dump(db))
|
|
138
138
|
else:
|
|
139
|
-
print_table_for(
|
|
140
|
-
[db],
|
|
141
|
-
functools.partial(model_dump, exclude={"extensions", "schemas"}),
|
|
142
|
-
box=None,
|
|
143
|
-
)
|
|
139
|
+
print_table_for([db], functools.partial(model_dump, mode="pretty"), box=None)
|
|
144
140
|
|
|
145
141
|
|
|
146
142
|
@cli.command("list")
|
|
147
143
|
@output_format_option
|
|
144
|
+
@click.option(
|
|
145
|
+
"-x",
|
|
146
|
+
"--exclude-database",
|
|
147
|
+
"exclude_dbnames",
|
|
148
|
+
multiple=True,
|
|
149
|
+
help="Database to exclude from listing.",
|
|
150
|
+
)
|
|
148
151
|
@click.argument("dbname", nargs=-1)
|
|
149
|
-
@
|
|
152
|
+
@pass_postgresql_instance
|
|
150
153
|
@async_command
|
|
151
154
|
async def ls(
|
|
152
|
-
instance: system.
|
|
155
|
+
instance: system.PostgreSQLInstance,
|
|
156
|
+
dbname: Sequence[str],
|
|
157
|
+
exclude_dbnames: Sequence[str],
|
|
158
|
+
output_format: OutputFormat | None,
|
|
153
159
|
) -> None:
|
|
154
160
|
"""List databases (all or specified ones)
|
|
155
161
|
|
|
@@ -157,8 +163,10 @@ async def ls(
|
|
|
157
163
|
"""
|
|
158
164
|
|
|
159
165
|
async with postgresql.running(instance):
|
|
160
|
-
dbs = await databases.ls(
|
|
161
|
-
|
|
166
|
+
dbs = await databases.ls(
|
|
167
|
+
instance, dbnames=dbname, exclude_dbnames=exclude_dbnames
|
|
168
|
+
)
|
|
169
|
+
if output_format == "json":
|
|
162
170
|
print_json_for([model_dump(db) for db in dbs])
|
|
163
171
|
else:
|
|
164
172
|
print_table_for(dbs, model_dump)
|
|
@@ -166,11 +174,13 @@ async def ls(
|
|
|
166
174
|
|
|
167
175
|
@cli.command("drop")
|
|
168
176
|
@model.as_parameters(interface.DatabaseDropped, "create")
|
|
169
|
-
@
|
|
177
|
+
@pass_postgresql_instance
|
|
170
178
|
@click.pass_obj
|
|
171
179
|
@async_command
|
|
172
180
|
async def drop(
|
|
173
|
-
obj: Obj,
|
|
181
|
+
obj: Obj,
|
|
182
|
+
instance: system.PostgreSQLInstance,
|
|
183
|
+
databasedropped: interface.DatabaseDropped,
|
|
174
184
|
) -> None:
|
|
175
185
|
"""Drop a database"""
|
|
176
186
|
with obj.lock, audit():
|
|
@@ -183,14 +193,14 @@ async def drop(
|
|
|
183
193
|
@click.option("-r", "--role", "roles", multiple=True, help="Role to inspect")
|
|
184
194
|
@click.option("--default", "defaults", is_flag=True, help="Display default privileges")
|
|
185
195
|
@output_format_option
|
|
186
|
-
@
|
|
196
|
+
@pass_postgresql_instance
|
|
187
197
|
@async_command
|
|
188
198
|
async def list_privileges(
|
|
189
|
-
instance: system.
|
|
199
|
+
instance: system.PostgreSQLInstance,
|
|
190
200
|
name: str,
|
|
191
201
|
roles: Sequence[str],
|
|
192
202
|
defaults: bool,
|
|
193
|
-
output_format: OutputFormat,
|
|
203
|
+
output_format: OutputFormat | None,
|
|
194
204
|
) -> None:
|
|
195
205
|
"""List privileges on a database."""
|
|
196
206
|
async with postgresql.running(instance):
|
|
@@ -201,7 +211,7 @@ async def list_privileges(
|
|
|
201
211
|
)
|
|
202
212
|
except ValueError as e:
|
|
203
213
|
raise click.ClickException(str(e)) from None
|
|
204
|
-
if output_format ==
|
|
214
|
+
if output_format == "json":
|
|
205
215
|
print_json_for([model_dump(p) for p in prvlgs])
|
|
206
216
|
else:
|
|
207
217
|
print_table_for(prvlgs, model_dump)
|
|
@@ -220,14 +230,14 @@ async def list_privileges(
|
|
|
220
230
|
help="Database to not run command on",
|
|
221
231
|
)
|
|
222
232
|
@output_format_option
|
|
223
|
-
@
|
|
233
|
+
@pass_postgresql_instance
|
|
224
234
|
@async_command
|
|
225
235
|
async def run(
|
|
226
|
-
instance: system.
|
|
236
|
+
instance: system.PostgreSQLInstance,
|
|
227
237
|
sql_command: str,
|
|
228
238
|
dbnames: Sequence[str],
|
|
229
239
|
exclude_dbnames: Sequence[str],
|
|
230
|
-
output_format: OutputFormat,
|
|
240
|
+
output_format: OutputFormat | None,
|
|
231
241
|
) -> None:
|
|
232
242
|
"""Run given command on databases of a PostgreSQL instance"""
|
|
233
243
|
async with postgresql.running(instance):
|
|
@@ -240,7 +250,7 @@ async def run(
|
|
|
240
250
|
)
|
|
241
251
|
except psycopg.ProgrammingError as e:
|
|
242
252
|
raise click.ClickException(str(e)) from None
|
|
243
|
-
if output_format ==
|
|
253
|
+
if output_format == "json":
|
|
244
254
|
print_json_for(result)
|
|
245
255
|
else:
|
|
246
256
|
for dbname, rows in result.items():
|
|
@@ -256,9 +266,11 @@ async def run(
|
|
|
256
266
|
help="Write dump file(s) to DIRECTORY instead of default dumps directory.",
|
|
257
267
|
)
|
|
258
268
|
@click.argument("dbname")
|
|
259
|
-
@
|
|
269
|
+
@pass_postgresql_instance
|
|
260
270
|
@async_command
|
|
261
|
-
async def dump(
|
|
271
|
+
async def dump(
|
|
272
|
+
instance: system.PostgreSQLInstance, dbname: str, output: Path | None
|
|
273
|
+
) -> None:
|
|
262
274
|
"""Dump a database"""
|
|
263
275
|
async with postgresql.running(instance):
|
|
264
276
|
await databases.dump(instance, dbname, output)
|
|
@@ -267,17 +279,19 @@ async def dump(instance: system.Instance, dbname: str, output: Path | None) -> N
|
|
|
267
279
|
@cli.command("dumps")
|
|
268
280
|
@click.argument("dbname", nargs=-1)
|
|
269
281
|
@output_format_option
|
|
270
|
-
@
|
|
282
|
+
@pass_postgresql_instance
|
|
271
283
|
@async_command
|
|
272
284
|
async def dumps(
|
|
273
|
-
instance: system.
|
|
285
|
+
instance: system.PostgreSQLInstance,
|
|
286
|
+
dbname: Sequence[str],
|
|
287
|
+
output_format: OutputFormat | None,
|
|
274
288
|
) -> None:
|
|
275
289
|
"""List the database dumps
|
|
276
290
|
|
|
277
291
|
Only dumps created in the default dumps directory are listed.
|
|
278
292
|
"""
|
|
279
293
|
values = [asdict(dump) async for dump in databases.dumps(instance, dbnames=dbname)]
|
|
280
|
-
if output_format ==
|
|
294
|
+
if output_format == "json":
|
|
281
295
|
print_json_for(values)
|
|
282
296
|
else:
|
|
283
297
|
dbnames = ", ".join(dbname) if dbname else "all databases"
|
|
@@ -287,10 +301,10 @@ async def dumps(
|
|
|
287
301
|
@cli.command("restore")
|
|
288
302
|
@click.argument("dump_id")
|
|
289
303
|
@click.argument("targetdbname", required=False)
|
|
290
|
-
@
|
|
304
|
+
@pass_postgresql_instance
|
|
291
305
|
@async_command
|
|
292
306
|
async def restore(
|
|
293
|
-
instance: system.
|
|
307
|
+
instance: system.PostgreSQLInstance, dump_id: str, targetdbname: str | None
|
|
294
308
|
) -> None:
|
|
295
309
|
"""Restore a database dump
|
|
296
310
|
|
|
@@ -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,
|
|
@@ -26,9 +27,8 @@ from pglift.settings import default_postgresql_version
|
|
|
26
27
|
from pglift.settings._postgresql import PostgreSQLVersion
|
|
27
28
|
from pglift.types import Operation, Status, validation_context
|
|
28
29
|
|
|
29
|
-
from . import _site
|
|
30
|
+
from . import _site, model
|
|
30
31
|
from . import hookspecs as h
|
|
31
|
-
from . import model
|
|
32
32
|
from .util import (
|
|
33
33
|
ManifestData,
|
|
34
34
|
Obj,
|
|
@@ -42,6 +42,7 @@ from .util import (
|
|
|
42
42
|
manifest_option,
|
|
43
43
|
model_dump,
|
|
44
44
|
output_format_option,
|
|
45
|
+
postgresql_instance_identifier,
|
|
45
46
|
print_argspec,
|
|
46
47
|
print_json_for,
|
|
47
48
|
print_schema,
|
|
@@ -115,19 +116,19 @@ async def create(obj: Obj, instance: interface.Instance, drop_on_error: bool) ->
|
|
|
115
116
|
|
|
116
117
|
@cli.command("alter") # type: ignore[arg-type]
|
|
117
118
|
@instance_identifier(nargs=1)
|
|
118
|
-
@model.as_parameters(_site.INSTANCE_MODEL, "update"
|
|
119
|
+
@model.as_parameters(_site.INSTANCE_MODEL, "update")
|
|
119
120
|
@click.pass_obj
|
|
120
121
|
@async_command
|
|
121
122
|
async def alter(obj: Obj, instance: system.Instance, **changes: Any) -> None:
|
|
122
123
|
"""Alter PostgreSQL INSTANCE"""
|
|
123
124
|
with obj.lock, audit():
|
|
124
|
-
status = await postgresql.status(instance)
|
|
125
|
+
status = await postgresql.status(instance.postgresql)
|
|
125
126
|
manifest = await instances._get(instance, status)
|
|
126
127
|
values = manifest.model_dump(by_alias=True, exclude={"settings"})
|
|
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
|
|
@@ -141,7 +142,7 @@ async def alter(obj: Obj, instance: system.Instance, **changes: Any) -> None:
|
|
|
141
142
|
@click.pass_obj
|
|
142
143
|
@async_command
|
|
143
144
|
async def apply(
|
|
144
|
-
obj: Obj, data: ManifestData, output_format: OutputFormat, dry_run: bool
|
|
145
|
+
obj: Obj, data: ManifestData, output_format: OutputFormat | None, dry_run: bool
|
|
145
146
|
) -> None:
|
|
146
147
|
"""Apply manifest as a PostgreSQL instance"""
|
|
147
148
|
name, version = data["name"], data.get("version")
|
|
@@ -149,17 +150,25 @@ 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
|
-
|
|
154
|
-
)
|
|
155
|
-
|
|
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)
|
|
159
168
|
else:
|
|
160
169
|
with obj.lock, audit():
|
|
161
170
|
ret = await instances.apply(_site.SETTINGS, instance)
|
|
162
|
-
if output_format ==
|
|
171
|
+
if output_format == "json":
|
|
163
172
|
print_json_for(ret)
|
|
164
173
|
|
|
165
174
|
|
|
@@ -177,14 +186,14 @@ async def promote(obj: Obj, instance: system.Instance) -> None:
|
|
|
177
186
|
@output_format_option
|
|
178
187
|
@instance_identifier(nargs=1)
|
|
179
188
|
@async_command
|
|
180
|
-
async def get(instance: system.Instance, output_format: OutputFormat) -> None:
|
|
189
|
+
async def get(instance: system.Instance, output_format: OutputFormat | None) -> None:
|
|
181
190
|
"""Get the description of PostgreSQL INSTANCE.
|
|
182
191
|
|
|
183
192
|
Unless --output-format is specified, 'settings' and 'state' fields are not
|
|
184
193
|
shown as well as 'standby' information if INSTANCE is not a standby.
|
|
185
194
|
"""
|
|
186
195
|
i = await instances.get(instance)
|
|
187
|
-
if output_format ==
|
|
196
|
+
if output_format == "json":
|
|
188
197
|
print_json_for(model_dump(i))
|
|
189
198
|
else:
|
|
190
199
|
exclude = {
|
|
@@ -194,9 +203,11 @@ async def get(instance: system.Instance, output_format: OutputFormat) -> None:
|
|
|
194
203
|
"wal_directory",
|
|
195
204
|
"powa",
|
|
196
205
|
}
|
|
197
|
-
if not instance.standby:
|
|
206
|
+
if not instance.postgresql.standby:
|
|
198
207
|
exclude.add("standby")
|
|
199
|
-
print_table_for(
|
|
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")
|
|
@@ -207,10 +218,12 @@ async def get(instance: system.Instance, output_format: OutputFormat) -> None:
|
|
|
207
218
|
)
|
|
208
219
|
@output_format_option
|
|
209
220
|
@async_command
|
|
210
|
-
async def ls(
|
|
221
|
+
async def ls(
|
|
222
|
+
version: PostgreSQLVersion | None, output_format: OutputFormat | None
|
|
223
|
+
) -> None:
|
|
211
224
|
"""List the available instances"""
|
|
212
225
|
items = [i async for i in instances.ls(_site.SETTINGS, version=version)]
|
|
213
|
-
if output_format ==
|
|
226
|
+
if output_format == "json":
|
|
214
227
|
print_json_for([model_dump(m) for m in items])
|
|
215
228
|
else:
|
|
216
229
|
print_table_for(items, model_dump)
|
|
@@ -290,12 +303,12 @@ async def stop(
|
|
|
290
303
|
|
|
291
304
|
|
|
292
305
|
@cli.command("reload")
|
|
293
|
-
@
|
|
306
|
+
@postgresql_instance_identifier(nargs=-1)
|
|
294
307
|
@click.option("--all", "all_instances", is_flag=True, help="Reload all instances.")
|
|
295
308
|
@click.pass_obj
|
|
296
309
|
@async_command
|
|
297
310
|
async def reload(
|
|
298
|
-
obj: Obj, instance: tuple[system.
|
|
311
|
+
obj: Obj, instance: tuple[system.PostgreSQLInstance, ...], all_instances: bool
|
|
299
312
|
) -> None:
|
|
300
313
|
"""Reload PostgreSQL INSTANCE"""
|
|
301
314
|
with obj.lock, audit():
|
|
@@ -362,7 +375,7 @@ def shell(instance: system.Instance, shell: str) -> None:
|
|
|
362
375
|
@cli.command("env")
|
|
363
376
|
@instance_identifier(nargs=1)
|
|
364
377
|
@output_format_option
|
|
365
|
-
def env(instance: system.Instance, output_format: OutputFormat) -> None:
|
|
378
|
+
def env(instance: system.Instance, output_format: OutputFormat | None) -> None:
|
|
366
379
|
"""Output environment variables suitable to handle to PostgreSQL INSTANCE.
|
|
367
380
|
|
|
368
381
|
This can be injected in shell using:
|
|
@@ -370,7 +383,7 @@ def env(instance: system.Instance, output_format: OutputFormat) -> None:
|
|
|
370
383
|
export $(pglift instance env myinstance)
|
|
371
384
|
"""
|
|
372
385
|
instance_env = instances.env_for(instance, path=True)
|
|
373
|
-
if output_format ==
|
|
386
|
+
if output_format == "json":
|
|
374
387
|
print_json_for(instance_env)
|
|
375
388
|
else:
|
|
376
389
|
for key, value in sorted(instance_env.items()):
|
|
@@ -379,8 +392,8 @@ def env(instance: system.Instance, output_format: OutputFormat) -> None:
|
|
|
379
392
|
|
|
380
393
|
@cli.command("logs")
|
|
381
394
|
@click.option("--follow/--no-follow", "-f/", default=False, help="Follow log output.")
|
|
382
|
-
@
|
|
383
|
-
def logs(instance: system.
|
|
395
|
+
@postgresql_instance_identifier(nargs=1)
|
|
396
|
+
def logs(instance: system.PostgreSQLInstance, follow: bool) -> None:
|
|
384
397
|
"""Output PostgreSQL logs of INSTANCE.
|
|
385
398
|
|
|
386
399
|
This assumes that the PostgreSQL instance is configured to use file-based
|
|
@@ -400,7 +413,7 @@ def logs(instance: system.Instance, follow: bool) -> None:
|
|
|
400
413
|
|
|
401
414
|
|
|
402
415
|
@cli.command("privileges")
|
|
403
|
-
@
|
|
416
|
+
@postgresql_instance_identifier(nargs=1)
|
|
404
417
|
@click.option(
|
|
405
418
|
"-d",
|
|
406
419
|
"--database",
|
|
@@ -413,11 +426,11 @@ def logs(instance: system.Instance, follow: bool) -> None:
|
|
|
413
426
|
@output_format_option
|
|
414
427
|
@async_command
|
|
415
428
|
async def list_privileges(
|
|
416
|
-
instance: system.
|
|
429
|
+
instance: system.PostgreSQLInstance,
|
|
417
430
|
databases: Sequence[str],
|
|
418
431
|
roles: Sequence[str],
|
|
419
432
|
defaults: bool,
|
|
420
|
-
output_format: OutputFormat,
|
|
433
|
+
output_format: OutputFormat | None,
|
|
421
434
|
) -> None:
|
|
422
435
|
"""List privileges on INSTANCE's databases."""
|
|
423
436
|
async with postgresql.running(instance):
|
|
@@ -427,7 +440,7 @@ async def list_privileges(
|
|
|
427
440
|
)
|
|
428
441
|
except ValueError as e:
|
|
429
442
|
raise click.ClickException(str(e)) from None
|
|
430
|
-
if output_format ==
|
|
443
|
+
if output_format == "json":
|
|
431
444
|
print_json_for([model_dump(p) for p in prvlgs])
|
|
432
445
|
else:
|
|
433
446
|
if defaults:
|
|
@@ -455,8 +468,9 @@ async def list_privileges(
|
|
|
455
468
|
"--jobs",
|
|
456
469
|
required=False,
|
|
457
470
|
type=click.INT,
|
|
458
|
-
help="Number of simultaneous processes or threads to use (from pg_upgrade).",
|
|
471
|
+
help="Number of simultaneous processes or threads to use (from pg_upgrade). Deprecated. Use extra options instead.",
|
|
459
472
|
)
|
|
473
|
+
@click.argument("extra_opts", nargs=-1, type=click.UNPROCESSED)
|
|
460
474
|
@click.pass_obj
|
|
461
475
|
@async_command
|
|
462
476
|
async def upgrade(
|
|
@@ -466,12 +480,23 @@ async def upgrade(
|
|
|
466
480
|
newname: str | None,
|
|
467
481
|
port: int | None,
|
|
468
482
|
jobs: int | None,
|
|
483
|
+
extra_opts: tuple[str, ...],
|
|
469
484
|
) -> None:
|
|
470
|
-
"""Upgrade INSTANCE using pg_upgrade
|
|
485
|
+
"""Upgrade INSTANCE using pg_upgrade
|
|
486
|
+
|
|
487
|
+
Extra options can be passed to the pg_upgrade command. They may need to be prefixed
|
|
488
|
+
with -- to separate them from the current command options when confusion arises. When using
|
|
489
|
+
extra options, providing the instance identifier is required.
|
|
490
|
+
"""
|
|
471
491
|
with obj.lock, audit():
|
|
472
|
-
await postgresql.check_status(instance, Status.not_running)
|
|
492
|
+
await postgresql.check_status(instance.postgresql, Status.not_running)
|
|
473
493
|
async with task.async_transaction():
|
|
474
494
|
new_instance = await instances.upgrade(
|
|
475
|
-
instance,
|
|
495
|
+
instance,
|
|
496
|
+
version=newversion,
|
|
497
|
+
name=newname,
|
|
498
|
+
port=port,
|
|
499
|
+
jobs=jobs,
|
|
500
|
+
extra_opts=extra_opts,
|
|
476
501
|
)
|
|
477
502
|
await instances.start(new_instance)
|
|
@@ -207,7 +207,7 @@ def site_settings(
|
|
|
207
207
|
/,
|
|
208
208
|
defaults: bool | None,
|
|
209
209
|
schema: bool,
|
|
210
|
-
output_format: OutputFormat,
|
|
210
|
+
output_format: OutputFormat | None,
|
|
211
211
|
) -> None:
|
|
212
212
|
"""Show site settings.
|
|
213
213
|
|
|
@@ -227,7 +227,7 @@ def site_settings(
|
|
|
227
227
|
value = _site.SETTINGS.model_dump(
|
|
228
228
|
mode="json", exclude_defaults=defaults is False
|
|
229
229
|
)
|
|
230
|
-
if output_format ==
|
|
230
|
+
if output_format == "json":
|
|
231
231
|
console.print_json(data=value)
|
|
232
232
|
else:
|
|
233
233
|
assert output_format is None
|