pglift-cli 2.0.0__tar.gz → 2.1.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-2.0.0 → pglift_cli-2.1.0}/PKG-INFO +2 -2
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/pyproject.toml +1 -1
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/database.py +32 -14
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/instance.py +39 -4
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/pgbackrest/__init__.py +4 -4
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/role.py +24 -15
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/tests/expect/test-cli-walkthrough.t +93 -3
- pglift_cli-2.1.0/tests/expect/test-demote.t +231 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/tests/expect/test-help.t +32 -0
- pglift_cli-2.1.0/tests/expect/test-transactions.t +70 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/tests/unit/test__site.py +9 -13
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/tests/unit/test_cli.py +13 -17
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/tests/unit/test_main.py +7 -9
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/.gitignore +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/README.md +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/hatch.toml +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/pytest.ini +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/__init__.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/__main__.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/_settings.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/_site.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/base.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/console.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/hookspecs.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/main.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/model.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/patroni.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/pgbackrest/repo_path.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/pgconf.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/pghba.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/pm.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/postgres.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/prometheus.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/py.typed +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/src/pglift_cli/util.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/tests/expect/.gitignore +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/tests/expect/test-base.t +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/tests/expect/test-port-validation.t +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/tests/expect/test-prometheus.t +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/tests/expect/test-standby-pgbackrest.t +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/tests/expect/test-upgrade.t +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/tests/unit/__init__.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/tests/unit/conftest.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/tests/unit/test_audit.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/tests/unit/test_model.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/tests/unit/test_pm.py +0 -0
- {pglift_cli-2.0.0 → pglift_cli-2.1.0}/tests/unit/test_util.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pglift_cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.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.0.0
|
|
26
|
+
Requires-Dist: click!=8.1.0,!=8.1.4,<8.2.0,>=8.0.0
|
|
27
27
|
Requires-Dist: filelock!=3.12.1,>=3.9.0
|
|
28
28
|
Requires-Dist: pluggy
|
|
29
29
|
Requires-Dist: psycopg>=3.1
|
|
@@ -37,6 +37,7 @@ from .util import (
|
|
|
37
37
|
print_result_diff,
|
|
38
38
|
print_schema,
|
|
39
39
|
print_table_for,
|
|
40
|
+
system_configure,
|
|
40
41
|
)
|
|
41
42
|
|
|
42
43
|
|
|
@@ -65,14 +66,18 @@ def cli(**kwargs: Any) -> None:
|
|
|
65
66
|
|
|
66
67
|
@cli.command("create")
|
|
67
68
|
@model.as_parameters(interface.Database, "create")
|
|
69
|
+
@dry_run_option
|
|
68
70
|
@pass_postgresql_instance
|
|
69
71
|
@click.pass_obj
|
|
70
72
|
@async_command
|
|
71
73
|
async def create(
|
|
72
|
-
obj: Obj,
|
|
74
|
+
obj: Obj,
|
|
75
|
+
instance: PostgreSQLInstance,
|
|
76
|
+
database: interface.Database,
|
|
77
|
+
dry_run: bool,
|
|
73
78
|
) -> None:
|
|
74
79
|
"""Create a database in a PostgreSQL instance"""
|
|
75
|
-
with obj.lock, audit():
|
|
80
|
+
with obj.lock, audit(dry_run=dry_run), system_configure(dry_run=dry_run):
|
|
76
81
|
async with postgresql.running(instance):
|
|
77
82
|
if await databases.exists(instance, database.name):
|
|
78
83
|
raise click.ClickException("database already exists")
|
|
@@ -83,14 +88,19 @@ async def create(
|
|
|
83
88
|
@cli.command("alter")
|
|
84
89
|
@model.as_parameters(interface.Database, "update")
|
|
85
90
|
@click.argument("dbname")
|
|
91
|
+
@dry_run_option
|
|
86
92
|
@pass_postgresql_instance
|
|
87
93
|
@click.pass_obj # type: ignore[arg-type]
|
|
88
94
|
@async_command
|
|
89
95
|
async def alter(
|
|
90
|
-
obj: Obj,
|
|
96
|
+
obj: Obj,
|
|
97
|
+
instance: PostgreSQLInstance,
|
|
98
|
+
dbname: str,
|
|
99
|
+
dry_run: bool,
|
|
100
|
+
**changes: Any,
|
|
91
101
|
) -> None:
|
|
92
102
|
"""Alter a database in a PostgreSQL instance"""
|
|
93
|
-
with obj.lock, audit():
|
|
103
|
+
with obj.lock, audit(dry_run=dry_run), system_configure(dry_run=dry_run):
|
|
94
104
|
async with postgresql.running(instance):
|
|
95
105
|
values = (await databases.get(instance, dbname)).model_dump(by_alias=True)
|
|
96
106
|
values = deep_update(values, changes)
|
|
@@ -117,12 +127,14 @@ async def apply(
|
|
|
117
127
|
) -> None:
|
|
118
128
|
"""Apply manifest as a database"""
|
|
119
129
|
database = interface.Database.model_validate(data)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
130
|
+
with (
|
|
131
|
+
obj.lock,
|
|
132
|
+
audit(dry_run=dry_run),
|
|
133
|
+
diff.enabled(diff_format),
|
|
134
|
+
system_configure(dry_run=dry_run),
|
|
135
|
+
):
|
|
136
|
+
async with postgresql.running(instance):
|
|
137
|
+
ret = await databases.apply(instance, database)
|
|
126
138
|
if output_format == "json":
|
|
127
139
|
print_json_for(ret)
|
|
128
140
|
else:
|
|
@@ -181,6 +193,7 @@ async def ls(
|
|
|
181
193
|
|
|
182
194
|
@cli.command("drop")
|
|
183
195
|
@model.as_parameters(interface.DatabaseDropped, "create")
|
|
196
|
+
@dry_run_option
|
|
184
197
|
@pass_postgresql_instance
|
|
185
198
|
@click.pass_obj
|
|
186
199
|
@async_command
|
|
@@ -188,9 +201,10 @@ async def drop(
|
|
|
188
201
|
obj: Obj,
|
|
189
202
|
instance: PostgreSQLInstance,
|
|
190
203
|
databasedropped: interface.DatabaseDropped,
|
|
204
|
+
dry_run: bool,
|
|
191
205
|
) -> None:
|
|
192
206
|
"""Drop a database"""
|
|
193
|
-
with obj.lock, audit():
|
|
207
|
+
with obj.lock, audit(dry_run=dry_run), system_configure(dry_run=dry_run):
|
|
194
208
|
async with postgresql.running(instance):
|
|
195
209
|
await databases.drop(instance, databasedropped)
|
|
196
210
|
|
|
@@ -274,11 +288,15 @@ async def run(
|
|
|
274
288
|
)
|
|
275
289
|
@click.argument("dbname")
|
|
276
290
|
@pass_postgresql_instance
|
|
291
|
+
@dry_run_option
|
|
277
292
|
@async_command
|
|
278
|
-
async def dump(
|
|
293
|
+
async def dump(
|
|
294
|
+
instance: PostgreSQLInstance, dbname: str, output: Path | None, dry_run: bool
|
|
295
|
+
) -> None:
|
|
279
296
|
"""Dump a database"""
|
|
280
|
-
|
|
281
|
-
|
|
297
|
+
with system_configure(dry_run=dry_run):
|
|
298
|
+
async with postgresql.running(instance):
|
|
299
|
+
await databases.dump(instance, dbname, output)
|
|
282
300
|
|
|
283
301
|
|
|
284
302
|
@cli.command("dumps")
|
|
@@ -114,8 +114,8 @@ async def create(obj: Obj, instance: interface.Instance, drop_on_error: bool) ->
|
|
|
114
114
|
with obj.lock, audit():
|
|
115
115
|
if instances.exists(instance.name, instance.version, _site.SETTINGS):
|
|
116
116
|
raise click.ClickException("instance already exists")
|
|
117
|
-
|
|
118
|
-
with
|
|
117
|
+
with manager.from_manifest(instance, settings=_site.SETTINGS):
|
|
118
|
+
async with task.async_transaction(drop_on_error):
|
|
119
119
|
await instances.apply(_site.SETTINGS, instance)
|
|
120
120
|
|
|
121
121
|
|
|
@@ -213,6 +213,41 @@ async def promote(obj: Obj, instance: Instance) -> None:
|
|
|
213
213
|
await instances.promote(instance)
|
|
214
214
|
|
|
215
215
|
|
|
216
|
+
@cli.command("demote")
|
|
217
|
+
@instance_identifier(nargs=1)
|
|
218
|
+
@model.as_parameters(postgresql.RewindSource, "create")
|
|
219
|
+
@click.option(
|
|
220
|
+
"--start/--no-start",
|
|
221
|
+
help="Start the instance at the end of the demotion process",
|
|
222
|
+
default=True,
|
|
223
|
+
show_default=True,
|
|
224
|
+
)
|
|
225
|
+
@click.argument("rewind_opts", nargs=-1, type=click.UNPROCESSED)
|
|
226
|
+
@click.pass_obj
|
|
227
|
+
@async_command
|
|
228
|
+
async def demote(
|
|
229
|
+
obj: Obj,
|
|
230
|
+
instance: Instance,
|
|
231
|
+
rewindsource: postgresql.RewindSource,
|
|
232
|
+
start: bool,
|
|
233
|
+
rewind_opts: tuple[str, ...],
|
|
234
|
+
) -> None:
|
|
235
|
+
"""Demote PostgreSQL INSTANCE as standby of specified source server using pg_rewind.
|
|
236
|
+
|
|
237
|
+
The instance must not be running and it may be started at the end of the
|
|
238
|
+
"demotion" process.
|
|
239
|
+
|
|
240
|
+
Extra options can be passed to the pg_rewind command. They may need to
|
|
241
|
+
be prefixed with -- to separate them from the current command options
|
|
242
|
+
when confusion arises. When using extra options, providing the instance
|
|
243
|
+
identifier is required.
|
|
244
|
+
"""
|
|
245
|
+
with obj.lock, audit(), manager.from_instance(instance.postgresql):
|
|
246
|
+
await instances.demote(instance, rewindsource, rewind_opts=rewind_opts)
|
|
247
|
+
if start:
|
|
248
|
+
await instances.start(instance)
|
|
249
|
+
|
|
250
|
+
|
|
216
251
|
@cli.command("get")
|
|
217
252
|
@output_format_option
|
|
218
253
|
@instance_identifier(nargs=1)
|
|
@@ -519,8 +554,8 @@ async def upgrade(
|
|
|
519
554
|
"""
|
|
520
555
|
with obj.lock, audit():
|
|
521
556
|
await postgresql.check_status(instance.postgresql, Status.not_running)
|
|
522
|
-
|
|
523
|
-
with
|
|
557
|
+
with manager.from_instance(instance.postgresql):
|
|
558
|
+
async with task.async_transaction():
|
|
524
559
|
new_instance = await instances.upgrade(
|
|
525
560
|
instance,
|
|
526
561
|
version=newversion,
|
|
@@ -12,7 +12,7 @@ import click
|
|
|
12
12
|
|
|
13
13
|
from pglift import pgbackrest, postgresql, types
|
|
14
14
|
from pglift.models import Instance
|
|
15
|
-
from pglift.pgbackrest import
|
|
15
|
+
from pglift.pgbackrest import models
|
|
16
16
|
from pglift.pgbackrest import register_if as register_if
|
|
17
17
|
|
|
18
18
|
from .. import _site, hookimpl
|
|
@@ -46,7 +46,7 @@ def pgbackrest_proxy(
|
|
|
46
46
|
"""Proxy to pgbackrest operations on an instance"""
|
|
47
47
|
s = context.obj.instance.service(models.Service)
|
|
48
48
|
settings = pgbackrest.get_settings(_site.SETTINGS)
|
|
49
|
-
cmd_args =
|
|
49
|
+
cmd_args = pgbackrest.make_cmd(s.stanza, settings, *command)
|
|
50
50
|
try:
|
|
51
51
|
subprocess.run(cmd_args, capture_output=False, check=True) # nosec
|
|
52
52
|
except subprocess.CalledProcessError as e:
|
|
@@ -70,7 +70,7 @@ async def instance_restore(
|
|
|
70
70
|
) from None
|
|
71
71
|
settings = pgbackrest.get_settings(_site.SETTINGS)
|
|
72
72
|
with obj.lock, audit():
|
|
73
|
-
await
|
|
73
|
+
await pgbackrest.restore(instance, settings, label=label, date=date)
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
@click.command("backups", cls=Command)
|
|
@@ -82,7 +82,7 @@ async def instance_backups(
|
|
|
82
82
|
) -> None:
|
|
83
83
|
"""List available backups for INSTANCE"""
|
|
84
84
|
settings = pgbackrest.get_settings(_site.SETTINGS)
|
|
85
|
-
backups = [b async for b in
|
|
85
|
+
backups = [b async for b in pgbackrest.iter_backups(instance, settings)]
|
|
86
86
|
if output_format == "json":
|
|
87
87
|
print_json_for([model_dump(b) for b in backups])
|
|
88
88
|
else:
|
|
@@ -35,6 +35,7 @@ from .util import (
|
|
|
35
35
|
print_result_diff,
|
|
36
36
|
print_schema,
|
|
37
37
|
print_table_for,
|
|
38
|
+
system_configure,
|
|
38
39
|
)
|
|
39
40
|
|
|
40
41
|
|
|
@@ -76,11 +77,14 @@ def cli(**kwargs: Any) -> None:
|
|
|
76
77
|
@cli.command("create")
|
|
77
78
|
@model.as_parameters(_site.ROLE_MODEL, "create")
|
|
78
79
|
@pass_postgresql_instance
|
|
80
|
+
@dry_run_option
|
|
79
81
|
@click.pass_obj
|
|
80
82
|
@async_command
|
|
81
|
-
async def create(
|
|
83
|
+
async def create(
|
|
84
|
+
obj: Obj, instance: PostgreSQLInstance, role: interface.Role, dry_run: bool
|
|
85
|
+
) -> None:
|
|
82
86
|
"""Create a role in a PostgreSQL instance"""
|
|
83
|
-
with obj.lock, audit():
|
|
87
|
+
with obj.lock, audit(dry_run=dry_run), system_configure(dry_run=dry_run):
|
|
84
88
|
async with postgresql.running(instance):
|
|
85
89
|
if await roles.exists(instance, role.name):
|
|
86
90
|
raise click.ClickException("role already exists")
|
|
@@ -92,13 +96,14 @@ async def create(obj: Obj, instance: PostgreSQLInstance, role: interface.Role) -
|
|
|
92
96
|
@model.as_parameters(_site.ROLE_MODEL, "update")
|
|
93
97
|
@click.argument("rolname")
|
|
94
98
|
@pass_postgresql_instance
|
|
99
|
+
@dry_run_option
|
|
95
100
|
@click.pass_obj # type: ignore[arg-type]
|
|
96
101
|
@async_command
|
|
97
102
|
async def alter(
|
|
98
|
-
obj: Obj, instance: PostgreSQLInstance, rolname: str, **changes: Any
|
|
103
|
+
obj: Obj, instance: PostgreSQLInstance, rolname: str, dry_run: bool, **changes: Any
|
|
99
104
|
) -> None:
|
|
100
105
|
"""Alter a role in a PostgreSQL instance"""
|
|
101
|
-
with obj.lock, audit():
|
|
106
|
+
with obj.lock, audit(dry_run=dry_run), system_configure(dry_run=dry_run):
|
|
102
107
|
async with postgresql.running(instance):
|
|
103
108
|
with manager.from_instance(instance):
|
|
104
109
|
values = model_dump(await roles.get(instance, rolname))
|
|
@@ -136,13 +141,15 @@ async def apply(
|
|
|
136
141
|
)
|
|
137
142
|
with validation_context(operation=op, settings=_site.SETTINGS):
|
|
138
143
|
role = _site.ROLE_MODEL.model_validate(data)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
with (
|
|
145
|
+
obj.lock,
|
|
146
|
+
audit(dry_run=dry_run),
|
|
147
|
+
diff.enabled(diff_format),
|
|
148
|
+
system_configure(dry_run=dry_run),
|
|
149
|
+
):
|
|
150
|
+
async with postgresql.running(instance):
|
|
151
|
+
with manager.from_instance(instance):
|
|
152
|
+
ret = await roles.apply(instance, role)
|
|
146
153
|
if output_format == "json":
|
|
147
154
|
print_json_for(ret)
|
|
148
155
|
else:
|
|
@@ -192,14 +199,16 @@ async def get(
|
|
|
192
199
|
@cli.command("drop")
|
|
193
200
|
@model.as_parameters(interface.RoleDropped, "create")
|
|
194
201
|
@pass_postgresql_instance
|
|
202
|
+
@dry_run_option
|
|
195
203
|
@async_command
|
|
196
204
|
async def drop(
|
|
197
|
-
instance: PostgreSQLInstance, roledropped: interface.RoleDropped
|
|
205
|
+
instance: PostgreSQLInstance, roledropped: interface.RoleDropped, dry_run: bool
|
|
198
206
|
) -> None:
|
|
199
207
|
"""Drop a role"""
|
|
200
|
-
|
|
201
|
-
with
|
|
202
|
-
|
|
208
|
+
with audit(dry_run=dry_run), system_configure(dry_run=dry_run):
|
|
209
|
+
async with postgresql.running(instance):
|
|
210
|
+
with manager.from_instance(instance):
|
|
211
|
+
await roles.drop(instance, roledropped)
|
|
203
212
|
|
|
204
213
|
|
|
205
214
|
@cli.command("privileges")
|
|
@@ -218,7 +218,6 @@ Get the status of an instance:
|
|
|
218
218
|
DEBUG debug logging at \$TMPDIR/pglift-\w+-\d+.\d+.log (re)
|
|
219
219
|
DEBUG looking for 'postgres_exporter@1\d-main' service status by its PID at \$TMPDIR\/run\/prometheus\/1\d-main.pid (re)
|
|
220
220
|
DEBUG get status of PostgreSQL instance 1\d\/main (re)
|
|
221
|
-
DEBUG \/usr\/.+1\d\/bin\/pg_ctl --version (re)
|
|
222
221
|
DEBUG \/usr\/.+1\d\/bin\/pg_ctl status -D \$TMPDIR\/srv\/pgsql\/1\d\/main\/data (re)
|
|
223
222
|
|
|
224
223
|
Reload, restart:
|
|
@@ -347,6 +346,7 @@ PostgreSQL configuration
|
|
|
347
346
|
Error: Invalid value for '<PARAMETER>=<VALUE>...': invalid
|
|
348
347
|
[2]
|
|
349
348
|
$ pglift --non-interactive pgconf set --dry-run log_statement=ddl
|
|
349
|
+
WARNING failed to get data_checksums status: 'Data page checksum version' not found in controldata
|
|
350
350
|
INFO configuring PostgreSQL
|
|
351
351
|
INFO instance 1\d\/main needs reload due to parameter changes: log_statement (re)
|
|
352
352
|
INFO reloading PostgreSQL configuration for 1\d\/main (re)
|
|
@@ -368,6 +368,7 @@ PostgreSQL configuration
|
|
|
368
368
|
Error: 'fsync' not found in managed configuration
|
|
369
369
|
[1]
|
|
370
370
|
$ pglift --non-interactive pgconf remove --dry-run log_statement
|
|
371
|
+
WARNING failed to get data_checksums status: 'Data page checksum version' not found in controldata
|
|
371
372
|
INFO configuring PostgreSQL
|
|
372
373
|
INFO instance 1\d\/main needs reload due to parameter changes: log_statement (re)
|
|
373
374
|
INFO reloading PostgreSQL configuration for 1\d\/main (re)
|
|
@@ -380,6 +381,14 @@ PostgreSQL configuration
|
|
|
380
381
|
log_statement: ddl -> None
|
|
381
382
|
$ pglift pgconf show log_statement
|
|
382
383
|
# log_statement = 'ddl'
|
|
384
|
+
$ pglift --non-interactive pgconf set port=1234 --dry-run
|
|
385
|
+
WARNING failed to get data_checksums status: 'Data page checksum version' not found in controldata
|
|
386
|
+
INFO configuring PostgreSQL
|
|
387
|
+
INFO reconfiguring Prometheus postgres_exporter 1\d-main (re)
|
|
388
|
+
INFO restarting Prometheus postgres_exporter 1\d-main (re)
|
|
389
|
+
INFO configuring pgBackRest stanza 'main' for pg1-path=\$TMPDIR\/srv\/pgsql\/1\d\/main\/data (re)
|
|
390
|
+
port: \d+ -> 1234 (re)
|
|
391
|
+
DRY RUN: no changes made
|
|
383
392
|
|
|
384
393
|
pg_hba.conf management:
|
|
385
394
|
|
|
@@ -478,6 +487,11 @@ Roles
|
|
|
478
487
|
|
|
479
488
|
Add and manipulate roles:
|
|
480
489
|
|
|
490
|
+
$ pglift role -i main create dba --login --pgpass --password=qwerty --in-role=pg_read_all_stats --dry-run
|
|
491
|
+
INFO creating role 'dba'
|
|
492
|
+
INFO adding an entry for 'dba' in \$TMPDIR\/.pgpass \(port=\d+\) (re)
|
|
493
|
+
DRY RUN: no changes made
|
|
494
|
+
|
|
481
495
|
$ pglift role -i main create dba --login --pgpass --password=qwerty --in-role=pg_read_all_stats
|
|
482
496
|
INFO creating role 'dba'
|
|
483
497
|
INFO adding an entry for 'dba' in \$TMPDIR\/.pgpass \(port=\d+\) (re)
|
|
@@ -489,6 +503,10 @@ Add and manipulate roles:
|
|
|
489
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)
|
|
490
504
|
dba True True True False False False Fal\xe2\x80\xa6 pg_r\xe2\x80\xa6 True (esc)
|
|
491
505
|
|
|
506
|
+
$ pglift role -i main create dba --dry-run
|
|
507
|
+
Error: role already exists
|
|
508
|
+
[1]
|
|
509
|
+
|
|
492
510
|
$ pglift role -i main create dba
|
|
493
511
|
Error: role already exists
|
|
494
512
|
[1]
|
|
@@ -500,6 +518,11 @@ Add and manipulate roles:
|
|
|
500
518
|
dba
|
|
501
519
|
(1 row)
|
|
502
520
|
|
|
521
|
+
$ pglift role alter dba --connection-limit=10 --inherit --no-pgpass --no-login --revoke=pg_read_all_stats --grant=pg_monitor --valid-until=2026-01-01 --dry-run
|
|
522
|
+
INFO altering role 'dba'
|
|
523
|
+
INFO removing entry for 'dba' in \$TMPDIR\/.pgpass \(port=\d+\) (re)
|
|
524
|
+
INFO removing now empty $TMPDIR/.pgpass
|
|
525
|
+
DRY RUN: no changes made
|
|
503
526
|
|
|
504
527
|
$ pglift role alter dba --connection-limit=10 --inherit --no-pgpass --no-login --revoke=pg_read_all_stats --grant=pg_monitor --valid-until=2026-01-01
|
|
505
528
|
INFO altering role 'dba'
|
|
@@ -551,6 +574,16 @@ Add and manipulate roles:
|
|
|
551
574
|
> database: otherdb
|
|
552
575
|
> method: trust
|
|
553
576
|
> EOF
|
|
577
|
+
$ pglift role apply -f $TMPDIR/role.yaml --dry-run
|
|
578
|
+
INFO creating role 'test'
|
|
579
|
+
INFO pg_hba.conf updated
|
|
580
|
+
INFO reloading PostgreSQL configuration for 1\d\/main (re)
|
|
581
|
+
DRY RUN: no changes made
|
|
582
|
+
$ grep 'test' $TMPDIR/srv/pgsql/${version}/main/data/pg_hba.conf
|
|
583
|
+
[1]
|
|
584
|
+
$ pglift role get test
|
|
585
|
+
Error: role 'test' not found
|
|
586
|
+
[1]
|
|
554
587
|
$ pglift role apply -f $TMPDIR/role.yaml --diff
|
|
555
588
|
INFO creating role 'test'
|
|
556
589
|
INFO pg_hba.conf updated
|
|
@@ -760,20 +793,40 @@ Add and manipulate roles:
|
|
|
760
793
|
|
|
761
794
|
Databases
|
|
762
795
|
|
|
796
|
+
$ pglift database create test --dry-run
|
|
797
|
+
INFO creating 'test' database in 1\d\/main (re)
|
|
798
|
+
DRY RUN: no changes made
|
|
763
799
|
$ pglift database create test --owner test
|
|
764
800
|
INFO creating 'test' database in 1\d\/main (re)
|
|
765
801
|
$ pglift database create myapp --owner dba --schema app
|
|
766
802
|
INFO creating 'myapp' database in 1\d\/main (re)
|
|
767
803
|
INFO creating schema 'app' in database myapp with owner 'dba'
|
|
768
804
|
|
|
769
|
-
$ pglift database create other
|
|
770
|
-
INFO creating 'other' database in 1\d/main (re)
|
|
771
805
|
$ cat > $TMPDIR/other.yaml <<EOF
|
|
772
806
|
> name: other
|
|
773
807
|
> extensions:
|
|
774
808
|
> - name: pg_stat_statements
|
|
775
809
|
> EOF
|
|
776
810
|
$ pglift database apply -f $TMPDIR/other.yaml --dry-run
|
|
811
|
+
INFO creating 'other' database in 1\d/main (re)
|
|
812
|
+
INFO creating extension 'pg_stat_statements' in database other
|
|
813
|
+
DRY RUN: no changes made
|
|
814
|
+
$ pglift database create other
|
|
815
|
+
INFO creating 'other' database in 1\d/main (re)
|
|
816
|
+
$ pglift database apply -f $TMPDIR/other.yaml --dry-run
|
|
817
|
+
INFO altering 'other' database on instance 1\d\/main (re)
|
|
818
|
+
INFO creating extension 'pg_stat_statements' in database other
|
|
819
|
+
DRY RUN: no changes made
|
|
820
|
+
$ pglift database run -d other "SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_stat_statements')"
|
|
821
|
+
INFO running "SELECT EXISTS \(SELECT 1 FROM pg_extension WHERE extname = 'pg_stat_statements'\)" on other database of 1\d\/main (re)
|
|
822
|
+
INFO SELECT 1
|
|
823
|
+
Database
|
|
824
|
+
other
|
|
825
|
+
┏━━━━━━━━┓
|
|
826
|
+
┃ exists ┃
|
|
827
|
+
┡━━━━━━━━┩
|
|
828
|
+
│ False │
|
|
829
|
+
└────────┘
|
|
777
830
|
$ pglift database apply -f $TMPDIR/other.yaml -o json --diff
|
|
778
831
|
INFO altering 'other' database on instance 1\d\/main (re)
|
|
779
832
|
INFO creating extension 'pg_stat_statements' in database other
|
|
@@ -781,6 +834,26 @@ Databases
|
|
|
781
834
|
"change_state": "changed",
|
|
782
835
|
"diff": []
|
|
783
836
|
}
|
|
837
|
+
$ pglift database run -d other "SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_stat_statements')"
|
|
838
|
+
INFO running "SELECT EXISTS \(SELECT 1 FROM pg_extension WHERE extname = 'pg_stat_statements'\)" on other database of 1\d\/main (re)
|
|
839
|
+
INFO SELECT 1
|
|
840
|
+
Database
|
|
841
|
+
other
|
|
842
|
+
┏━━━━━━━━┓
|
|
843
|
+
┃ exists ┃
|
|
844
|
+
┡━━━━━━━━┩
|
|
845
|
+
│ True │
|
|
846
|
+
└────────┘
|
|
847
|
+
$ cat > $TMPDIR/other.yaml <<EOF
|
|
848
|
+
> name: other
|
|
849
|
+
> state: absent
|
|
850
|
+
> EOF
|
|
851
|
+
$ pglift database apply -f $TMPDIR/other.yaml --dry-run
|
|
852
|
+
INFO dropping 'other' database
|
|
853
|
+
DRY RUN: no changes made
|
|
854
|
+
$ pglift database drop other --dry-run
|
|
855
|
+
INFO dropping 'other' database
|
|
856
|
+
DRY RUN: no changes made
|
|
784
857
|
$ pglift database get other
|
|
785
858
|
name owner settin… schemas extensi… locale public… subscri… tables… (esc)
|
|
786
859
|
other postgr… public pg_stat… C pg_def… (esc)
|
|
@@ -788,6 +861,12 @@ Databases
|
|
|
788
861
|
Error: database already exists
|
|
789
862
|
[1]
|
|
790
863
|
|
|
864
|
+
$ pglift database alter other --owner=test --add-extension=unaccent --add-schema=myschema --dry-run
|
|
865
|
+
INFO altering 'other' database on instance 1\d\/main (re)
|
|
866
|
+
INFO creating schema 'myschema' in database other with owner 'test'
|
|
867
|
+
INFO creating extension 'unaccent' in database other
|
|
868
|
+
DRY RUN: no changes made
|
|
869
|
+
|
|
791
870
|
$ pglift database alter other --owner=test --add-extension=unaccent --add-schema=myschema
|
|
792
871
|
INFO altering 'other' database on instance 1\d\/main (re)
|
|
793
872
|
INFO creating schema 'myschema' in database other with owner 'test'
|
|
@@ -933,6 +1012,9 @@ Databases
|
|
|
933
1012
|
INFO backing up database 'nosuchdb' on instance 1\d\/main (re)
|
|
934
1013
|
Error: .+ database "nosuchdb" does not exist (re)
|
|
935
1014
|
[1]
|
|
1015
|
+
$ pglift database dump postgres --dry-run
|
|
1016
|
+
INFO backing up database 'postgres' on instance 1\d/main (re)
|
|
1017
|
+
DRY RUN: no changes made
|
|
936
1018
|
$ pglift database dump postgres
|
|
937
1019
|
INFO backing up database 'postgres' on instance 1\d/main (re)
|
|
938
1020
|
$ ls $TMPDIR/srv/dumps/*-main
|
|
@@ -996,6 +1078,10 @@ Databases
|
|
|
996
1078
|
$ pglift database drop myapp
|
|
997
1079
|
INFO dropping 'myapp' database
|
|
998
1080
|
|
|
1081
|
+
$ pglift role drop test --dry-run
|
|
1082
|
+
INFO dropping role 'test'
|
|
1083
|
+
INFO removing entries from pg_hba.conf
|
|
1084
|
+
DRY RUN: no changes made
|
|
999
1085
|
$ pglift role drop test
|
|
1000
1086
|
INFO dropping role 'test'
|
|
1001
1087
|
Error: role "test" cannot be dropped because some objects depend on it (detail: owner of database test
|
|
@@ -1007,6 +1093,10 @@ Databases
|
|
|
1007
1093
|
|
|
1008
1094
|
Error: Invalid value for '--reassign-owned': Value error, field is mutually exclusive with 'drop_owned'
|
|
1009
1095
|
[2]
|
|
1096
|
+
$ pglift role drop test --drop-owned --dry-run
|
|
1097
|
+
INFO dropping role 'test'
|
|
1098
|
+
INFO removing entries from pg_hba.conf
|
|
1099
|
+
DRY RUN: no changes made
|
|
1010
1100
|
$ pglift role drop test --drop-owned
|
|
1011
1101
|
INFO dropping role 'test'
|
|
1012
1102
|
INFO removing entries from pg_hba.conf
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2021 Dalibo
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
Site settings and configuration
|
|
6
|
+
|
|
7
|
+
$ PASSWORDS=$TMPDIR/passwords.json
|
|
8
|
+
$ cat > $PASSWORDS << 'EOF'
|
|
9
|
+
> {
|
|
10
|
+
> "postgres": "s3per",
|
|
11
|
+
> "replication": "r3pl"
|
|
12
|
+
> }
|
|
13
|
+
> EOF
|
|
14
|
+
|
|
15
|
+
$ export PGLIFT_CLI__LOG_FORMAT="%(levelname)-4s %(message)s"
|
|
16
|
+
$ export PGLIFT_POSTGRESQL__AUTH__LOCAL=md5
|
|
17
|
+
$ export PGLIFT_POSTGRESQL__AUTH__PASSFILE=null
|
|
18
|
+
$ export PGLIFT_POSTGRESQL__AUTH__PASSWORD_COMMAND='["jq", "-r", ".{role}", "'$PASSWORDS'"]'
|
|
19
|
+
$ export PGLIFT_POSTGRESQL__REPLROLE=replication
|
|
20
|
+
|
|
21
|
+
$ export PGLIFT_CONFIG_DIR="$TMPDIR/$TESTFILE.conf.d"
|
|
22
|
+
$ mkdir -p $PGLIFT_CONFIG_DIR/postgresql
|
|
23
|
+
$ cat > $PGLIFT_CONFIG_DIR/postgresql/pg_hba.conf << 'EOF'
|
|
24
|
+
> local all {surole} {auth.local}
|
|
25
|
+
> local all all {auth.local}
|
|
26
|
+
> host all all 127.0.0.1/32 {auth.host}
|
|
27
|
+
> host all all ::1/128 {auth.host}
|
|
28
|
+
> local replication all {auth.local}
|
|
29
|
+
> host replication {replrole} 127.0.0.1/32 {auth.host}
|
|
30
|
+
> host replication {replrole} ::1/128 {auth.host}
|
|
31
|
+
> EOF
|
|
32
|
+
|
|
33
|
+
$ PREFIX1=$TMPDIR/1
|
|
34
|
+
$ PREFIX2=$TMPDIR/2
|
|
35
|
+
$ RUN_PREFIX1=$PREFIX1/run
|
|
36
|
+
$ RUN_PREFIX2=$PREFIX2/run
|
|
37
|
+
$ PG1PORT=$(port-for pg1)
|
|
38
|
+
$ PG2PORT=$(port-for pg2)
|
|
39
|
+
$ alias pglift1="env PGLIFT_PREFIX=$PREFIX1 PGLIFT_RUN_PREFIX=$RUN_PREFIX1 pglift --log-level=INFO --non-interactive"
|
|
40
|
+
$ alias pglift2="env PGLIFT_PREFIX=$PREFIX2 PGLIFT_RUN_PREFIX=$RUN_PREFIX2 pglift --log-level=INFO --non-interactive"
|
|
41
|
+
|
|
42
|
+
$ pglift1 site-configure install
|
|
43
|
+
INFO creating PostgreSQL log directory: $TMPDIR/1/log/postgresql
|
|
44
|
+
$ pglift2 site-configure install
|
|
45
|
+
INFO creating PostgreSQL log directory: $TMPDIR/2/log/postgresql
|
|
46
|
+
|
|
47
|
+
(Cleanup steps)
|
|
48
|
+
|
|
49
|
+
$ trap "
|
|
50
|
+
> pglift1 instance drop; \
|
|
51
|
+
> pglift2 instance drop; \
|
|
52
|
+
> pglift1 site-configure uninstall; \
|
|
53
|
+
> pglift2 site-configure uninstall; \
|
|
54
|
+
> port-for -u pg1; port-for -u pg2" \
|
|
55
|
+
> EXIT
|
|
56
|
+
|
|
57
|
+
Create a primary instance
|
|
58
|
+
|
|
59
|
+
$ cat > $TMPDIR/primary.yaml <<EOF
|
|
60
|
+
> name: pg1
|
|
61
|
+
> port: $PG1PORT
|
|
62
|
+
> surole_password: s3per
|
|
63
|
+
> replrole_password: r3pl
|
|
64
|
+
> data_checksums: true
|
|
65
|
+
> settings:
|
|
66
|
+
> log_line_prefix: ' ~ '
|
|
67
|
+
> EOF
|
|
68
|
+
$ pglift1 instance apply -f $TMPDIR/primary.yaml
|
|
69
|
+
INFO initializing PostgreSQL
|
|
70
|
+
INFO configuring PostgreSQL authentication
|
|
71
|
+
INFO configuring PostgreSQL
|
|
72
|
+
INFO creating PostgreSQL socket directory directory: $TMPDIR/1/run/postgresql
|
|
73
|
+
INFO starting PostgreSQL 1\d\/pg1 (re)
|
|
74
|
+
INFO creating role 'replication'
|
|
75
|
+
INFO creating instance dumps directory: \$TMPDIR\/1\/srv\/dumps\/1\d-pg1 (re)
|
|
76
|
+
|
|
77
|
+
Create a standby instance
|
|
78
|
+
|
|
79
|
+
$ pglift2 instance create pg2 \
|
|
80
|
+
> --data-checksums \
|
|
81
|
+
> --standby-for="host=$RUN_PREFIX1/postgresql port=$PG1PORT user=replication" \
|
|
82
|
+
> --standby-password=r3pl \
|
|
83
|
+
> --port=$PG2PORT \
|
|
84
|
+
> --surole-password=s3per
|
|
85
|
+
INFO initializing PostgreSQL
|
|
86
|
+
INFO configuring PostgreSQL authentication
|
|
87
|
+
INFO configuring PostgreSQL
|
|
88
|
+
INFO creating PostgreSQL socket directory directory: $TMPDIR/2/run/postgresql
|
|
89
|
+
INFO starting PostgreSQL 1\d\/pg2 (re)
|
|
90
|
+
INFO creating instance dumps directory: \$TMPDIR\/2\/srv\/dumps\/1\d-pg2 (re)
|
|
91
|
+
|
|
92
|
+
Add some data to the primary, check replication
|
|
93
|
+
|
|
94
|
+
$ pglift1 database run -d postgres \
|
|
95
|
+
> "CREATE TABLE t AS (SELECT generate_series(1, 3) i)"
|
|
96
|
+
INFO running "CREATE TABLE t AS \(SELECT generate_series\(1, 3\) i\)" on postgres database of 1\d\/pg1 (re)
|
|
97
|
+
INFO SELECT 3
|
|
98
|
+
$ pglift2 -Lerror database run -o json -d postgres "SELECT i FROM t"
|
|
99
|
+
{
|
|
100
|
+
"postgres": [
|
|
101
|
+
{
|
|
102
|
+
"i": 1
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"i": 2
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"i": 3
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
$ pglift2 instance get -o json \
|
|
113
|
+
> | jq '.standby.slot, .standby.replication_lag, .standby.wal_sender_state, .state'
|
|
114
|
+
null
|
|
115
|
+
"0"
|
|
116
|
+
"streaming"
|
|
117
|
+
"started"
|
|
118
|
+
|
|
119
|
+
Prepare the primary for rewind connection by creating a rewind user (and resp.
|
|
120
|
+
connection database), on the primary (to be demoted)
|
|
121
|
+
|
|
122
|
+
$ pglift1 -Lerror role create rwd --login --replication --password=rwdpw
|
|
123
|
+
$ pglift1 -Lerror database create rwdb --owner rwd
|
|
124
|
+
$ pglift1 -Lerror database run -d rwdb \
|
|
125
|
+
> "GRANT EXECUTE ON function pg_catalog.pg_ls_dir(text, boolean, boolean) TO rwd"
|
|
126
|
+
$ pglift1 -Lerror database run -d rwdb \
|
|
127
|
+
> "GRANT EXECUTE ON function pg_catalog.pg_stat_file(text, boolean) TO rwd"
|
|
128
|
+
$ pglift1 -Lerror database run -d rwdb \
|
|
129
|
+
> "GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text) TO rwd"
|
|
130
|
+
$ pglift1 -Lerror database run -d rwdb \
|
|
131
|
+
> "GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean) TO rwd"
|
|
132
|
+
|
|
133
|
+
Check this role from the standby
|
|
134
|
+
$ pglift2 -Lerror role get rwd -o json
|
|
135
|
+
{
|
|
136
|
+
"name": "rwd",
|
|
137
|
+
"has_password": true,
|
|
138
|
+
"inherit": true,
|
|
139
|
+
"login": true,
|
|
140
|
+
"superuser": false,
|
|
141
|
+
"createdb": false,
|
|
142
|
+
"createrole": false,
|
|
143
|
+
"replication": true,
|
|
144
|
+
"connection_limit": null,
|
|
145
|
+
"valid_until": null,
|
|
146
|
+
"validity": null,
|
|
147
|
+
"memberships": [],
|
|
148
|
+
"hba_records": []
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
Promote the standby
|
|
152
|
+
|
|
153
|
+
$ pglift1 -Lerror instance stop
|
|
154
|
+
$ pglift2 instance status
|
|
155
|
+
PostgreSQL: running
|
|
156
|
+
$ pglift2 instance promote
|
|
157
|
+
INFO promoting PostgreSQL instance
|
|
158
|
+
|
|
159
|
+
Check connection to the new primary
|
|
160
|
+
|
|
161
|
+
$ psql -t -d "host=$RUN_PREFIX2/postgresql port=$PG2PORT user=rwd password=rwdpw dbname=rwdb" -c 'show cluster_name'
|
|
162
|
+
pg2
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
Demote (rewind) the primary as a standby
|
|
166
|
+
|
|
167
|
+
$ pglift1 instance demote pg1 \
|
|
168
|
+
> --from="host=$RUN_PREFIX2/postgresql port=$PG2PORT user=rwd dbname=rwdb" \
|
|
169
|
+
> --password=rwdpw \
|
|
170
|
+
> -- --no-ensure-shutdown
|
|
171
|
+
INFO demoting PostgreSQL instance
|
|
172
|
+
INFO configuring PostgreSQL
|
|
173
|
+
INFO starting PostgreSQL 1\d\/pg1 (re)
|
|
174
|
+
|
|
175
|
+
Check we can connect to the standby with the replication user
|
|
176
|
+
|
|
177
|
+
$ psql -t -d "host=$RUN_PREFIX1/postgresql port=$PG1PORT user=replication password=r3pl dbname=postgres" -c 'show cluster_name'
|
|
178
|
+
pg1
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
Check that the instance is really a standby
|
|
182
|
+
|
|
183
|
+
$ ls $PREFIX1/srv/pgsql/1*/pg1/data/standby.signal
|
|
184
|
+
$TMPDIR/1/srv/pgsql/1*/pg1/data/standby.signal (glob)
|
|
185
|
+
$ pglift1 -Lerror database run -d postgres 'SHOW primary_conninfo' -o json
|
|
186
|
+
{
|
|
187
|
+
"postgres": [
|
|
188
|
+
{
|
|
189
|
+
"primary_conninfo": "user=rwd password=rwdpw channel_binding=prefer host='$TMPDIR/2/run/postgresql' port=* sslmode=prefer *" (glob)
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
}
|
|
193
|
+
$ pglift1 instance get -o json \
|
|
194
|
+
> | jq '.standby.slot, .standby.replication_lag, .standby.wal_sender_state, .state'
|
|
195
|
+
null
|
|
196
|
+
"0"
|
|
197
|
+
null
|
|
198
|
+
"started"
|
|
199
|
+
|
|
200
|
+
And finally check replication is functional
|
|
201
|
+
|
|
202
|
+
$ pglift2 -Lerror database create rewindme
|
|
203
|
+
$ pglift2 -Lerror database run -d rewindme "CREATE TABLE t AS (SELECT * FROM generate_series(1, 3) v)"
|
|
204
|
+
|
|
205
|
+
$ pglift1 -Lerror database run -d rewindme "TABLE t" -o json
|
|
206
|
+
{
|
|
207
|
+
"rewindme": [
|
|
208
|
+
{
|
|
209
|
+
"v": 1
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"v": 2
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"v": 3
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
(Cleanup)
|
|
221
|
+
|
|
222
|
+
INFO dropping instance 1\d\/pg1 (re)
|
|
223
|
+
INFO stopping PostgreSQL 1\d\/pg1 (re)
|
|
224
|
+
INFO deleting PostgreSQL data and WAL directories
|
|
225
|
+
INFO dropping instance 1\d\/pg2 (re)
|
|
226
|
+
INFO stopping PostgreSQL 1\d\/pg2 (re)
|
|
227
|
+
INFO deleting PostgreSQL data and WAL directories
|
|
228
|
+
INFO deleting PostgreSQL log directory
|
|
229
|
+
INFO deleting PostgreSQL socket directory
|
|
230
|
+
INFO deleting PostgreSQL log directory
|
|
231
|
+
INFO deleting PostgreSQL socket directory (no-eol)
|
|
@@ -117,6 +117,7 @@ Instance commands
|
|
|
117
117
|
backup Back up PostgreSQL INSTANCE
|
|
118
118
|
backups List available backups for INSTANCE
|
|
119
119
|
create Initialize a PostgreSQL instance
|
|
120
|
+
demote Demote PostgreSQL INSTANCE as standby of specified source...
|
|
120
121
|
drop Drop PostgreSQL INSTANCE
|
|
121
122
|
env Output environment variables suitable to handle to...
|
|
122
123
|
exec Execute command in the libpq environment for PostgreSQL...
|
|
@@ -403,6 +404,30 @@ Instance commands
|
|
|
403
404
|
|
|
404
405
|
Options:
|
|
405
406
|
--help Show this message and exit.
|
|
407
|
+
$ pglift instance demote --help
|
|
408
|
+
Usage: pglift instance demote [OPTIONS] [INSTANCE] [REWIND_OPTS]...
|
|
409
|
+
|
|
410
|
+
Demote PostgreSQL INSTANCE as standby of specified source server using
|
|
411
|
+
pg_rewind.
|
|
412
|
+
|
|
413
|
+
The instance must not be running and it may be started at the end of the
|
|
414
|
+
"demotion" process.
|
|
415
|
+
|
|
416
|
+
Extra options can be passed to the pg_rewind command. They may need to be
|
|
417
|
+
prefixed with -- to separate them from the current command options when
|
|
418
|
+
confusion arises. When using extra options, providing the instance
|
|
419
|
+
identifier is required.
|
|
420
|
+
|
|
421
|
+
INSTANCE identifies target instance as <version>/<name> where the <version>/
|
|
422
|
+
prefix may be omitted if there is only one instance matching <name>.
|
|
423
|
+
Required if there is more than one instance on system.
|
|
424
|
+
|
|
425
|
+
Options:
|
|
426
|
+
--from TEXT DSN of source server to synchronize from. [required]
|
|
427
|
+
--password PASSWORD Password for the rewind user.
|
|
428
|
+
--start / --no-start Start the instance at the end of the demotion process
|
|
429
|
+
[default: start]
|
|
430
|
+
--help Show this message and exit.
|
|
406
431
|
$ pglift instance reload --help
|
|
407
432
|
Usage: pglift instance reload [OPTIONS] [INSTANCE]...
|
|
408
433
|
|
|
@@ -578,6 +603,7 @@ Role commands
|
|
|
578
603
|
used multiple times.)
|
|
579
604
|
--revoke ROLE Revoke membership of the given role. (Can be
|
|
580
605
|
used multiple times.)
|
|
606
|
+
--dry-run Simulate change operations.
|
|
581
607
|
--help Show this message and exit.
|
|
582
608
|
$ pglift role create --help
|
|
583
609
|
Usage: pglift role create [OPTIONS] NAME
|
|
@@ -604,6 +630,7 @@ Role commands
|
|
|
604
630
|
--validity VALIDITY DEPRECATED. Use 'valid_until' instead.
|
|
605
631
|
--in-role ROLE Roles which this role should be a member of.
|
|
606
632
|
(Can be used multiple times.)
|
|
633
|
+
--dry-run Simulate change operations.
|
|
607
634
|
--help Show this message and exit.
|
|
608
635
|
$ pglift role drop --help
|
|
609
636
|
Usage: pglift role drop [OPTIONS] NAME
|
|
@@ -617,6 +644,7 @@ Role commands
|
|
|
617
644
|
Reassign all PostgreSQL's objects owned by
|
|
618
645
|
the role being dropped to the specified role
|
|
619
646
|
name.
|
|
647
|
+
--dry-run Simulate change operations.
|
|
620
648
|
--help Show this message and exit.
|
|
621
649
|
$ pglift role get --help
|
|
622
650
|
Usage: pglift role get [OPTIONS] NAME
|
|
@@ -691,6 +719,7 @@ Database commands
|
|
|
691
719
|
be used multiple times.)
|
|
692
720
|
--tablespace TABLESPACE The name of the tablespace that will be
|
|
693
721
|
associated with the database.
|
|
722
|
+
--dry-run Simulate change operations.
|
|
694
723
|
--help Show this message and exit.
|
|
695
724
|
$ pglift database create --help
|
|
696
725
|
Usage: pglift database create [OPTIONS] NAME
|
|
@@ -714,6 +743,7 @@ Database commands
|
|
|
714
743
|
Only restore the schema (data definitions).
|
|
715
744
|
--tablespace TABLESPACE The name of the tablespace that will be
|
|
716
745
|
associated with the database.
|
|
746
|
+
--dry-run Simulate change operations.
|
|
717
747
|
--help Show this message and exit.
|
|
718
748
|
$ pglift database drop --help
|
|
719
749
|
Usage: pglift database drop [OPTIONS] NAME
|
|
@@ -722,6 +752,7 @@ Database commands
|
|
|
722
752
|
|
|
723
753
|
Options:
|
|
724
754
|
--force / --no-force Force the drop.
|
|
755
|
+
--dry-run Simulate change operations.
|
|
725
756
|
--help Show this message and exit.
|
|
726
757
|
$ pglift database dump --help
|
|
727
758
|
Usage: pglift database dump [OPTIONS] DBNAME
|
|
@@ -731,6 +762,7 @@ Database commands
|
|
|
731
762
|
Options:
|
|
732
763
|
-o, --output DIRECTORY Write dump file(s) to DIRECTORY instead of default
|
|
733
764
|
dumps directory.
|
|
765
|
+
--dry-run Simulate change operations.
|
|
734
766
|
--help Show this message and exit.
|
|
735
767
|
$ pglift database dumps --help
|
|
736
768
|
Usage: pglift database dumps [OPTIONS] [DBNAME]...
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Dalibo
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
Tests for transactions (revert of created objects in case of operational errors).
|
|
6
|
+
|
|
7
|
+
$ export PGLIFT_CLI__LOG_FORMAT="%(levelname)-4s %(message)s"
|
|
8
|
+
$ export PGLIFT_PREFIX=$TMPDIR
|
|
9
|
+
$ export PGLIFT_RUN_PREFIX=$TMPDIR/run
|
|
10
|
+
$ export PGLIFT_POSTGRESQL='{
|
|
11
|
+
> "datadir": "pgsql/{name}/data",
|
|
12
|
+
> "waldir": "pgsql/{name}/wal"
|
|
13
|
+
> }'
|
|
14
|
+
$ export PGLIFT_CONFIG_DIR=/dev/null
|
|
15
|
+
|
|
16
|
+
$ trap "
|
|
17
|
+
> pglift --non-interactive instance drop; \
|
|
18
|
+
> pglift site-configure uninstall; \
|
|
19
|
+
> port-for -u pg" \
|
|
20
|
+
> EXIT
|
|
21
|
+
|
|
22
|
+
$ PGPORT=$(port-for pg)
|
|
23
|
+
|
|
24
|
+
$ pglift -Linfo site-configure install
|
|
25
|
+
INFO creating PostgreSQL log directory: $TMPDIR/log/postgresql
|
|
26
|
+
|
|
27
|
+
Create a working instance for later use (this also checks that creating an instance "stopped" works)
|
|
28
|
+
|
|
29
|
+
$ pglift -Linfo instance create main --port=$PGPORT --state=stopped
|
|
30
|
+
INFO initializing PostgreSQL
|
|
31
|
+
INFO configuring PostgreSQL authentication
|
|
32
|
+
INFO configuring PostgreSQL
|
|
33
|
+
INFO creating PostgreSQL socket directory directory: $TMPDIR/run/postgresql
|
|
34
|
+
INFO starting PostgreSQL 1\d\/main (re)
|
|
35
|
+
INFO stopping PostgreSQL 1\d/main (re)
|
|
36
|
+
INFO creating instance dumps directory: $TMPDIR/srv/dumps/1*-main (glob)
|
|
37
|
+
|
|
38
|
+
Try to create an instance with a non-existing encoding, triggering a failure in 'initdb'
|
|
39
|
+
|
|
40
|
+
$ pglift -Linfo instance create test --encoding=notanencoding --port=$PGPORT
|
|
41
|
+
INFO initializing PostgreSQL
|
|
42
|
+
WARNING Command '['*/bin/pg_ctl', 'init', '-D', '$TMPDIR/srv/pgsql/test/data', '-o', '--auth-host=trust --auth-local=trust --encoding=notanencoding --locale=C --username=postgres --waldir=$TMPDIR/srv/pgsql/test/wal']' returned non-zero exit status 1. (glob)
|
|
43
|
+
WARNING reverting: initializing PostgreSQL
|
|
44
|
+
INFO deleting PostgreSQL data and WAL directories
|
|
45
|
+
Error: Command '['*/bin/pg_ctl', 'init', '-D', '$TMPDIR/srv/pgsql/test/data', '-o', '--auth-host=trust --auth-local=trust --encoding=notanencoding --locale=C --username=postgres --waldir=$TMPDIR/srv/pgsql/test/wal']' returned non-zero exit status 1. (glob)
|
|
46
|
+
initdb: error: "notanencoding" is not a valid server encoding name
|
|
47
|
+
pg_ctl: database system initialization failed
|
|
48
|
+
|
|
49
|
+
The files belonging to this database system will be owned by user "*". (glob)
|
|
50
|
+
This user must also own the server process.
|
|
51
|
+
|
|
52
|
+
The database cluster will be initialized with locale "C".
|
|
53
|
+
|
|
54
|
+
[1]
|
|
55
|
+
|
|
56
|
+
Try to create a database with a non-existing tablespace
|
|
57
|
+
(XXX the final error message is wrong, probably a bug here)
|
|
58
|
+
|
|
59
|
+
$ pglift -Linfo database create db --tablespace=nosuchtbspc
|
|
60
|
+
INFO starting PostgreSQL 1\d\/main (re)
|
|
61
|
+
INFO creating 'db' database in 1\d\/main (re)
|
|
62
|
+
WARNING tablespace "nosuchtbspc" does not exist
|
|
63
|
+
WARNING reverting: creating 'db' database in 1\d\/main (re)
|
|
64
|
+
INFO dropping 'db' database
|
|
65
|
+
INFO stopping PostgreSQL 1\d\/main (re)
|
|
66
|
+
Error: database "db" does not exist
|
|
67
|
+
[1]
|
|
68
|
+
|
|
69
|
+
(cleanup)
|
|
70
|
+
WARNING instance 1*/main is already stopped (no-eol) (glob)
|
|
@@ -17,29 +17,25 @@ def test_site_settings(
|
|
|
17
17
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, prefix_dir: str
|
|
18
18
|
) -> None:
|
|
19
19
|
prefix = tmp_path / prefix_dir
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
assert s.prefix == prefix
|
|
20
|
+
monkeypatch.setenv("PGLIFT_PREFIX", str(prefix))
|
|
21
|
+
s = _site.SETTINGS
|
|
22
|
+
assert s.prefix == prefix
|
|
24
23
|
|
|
25
24
|
|
|
26
25
|
@pytest.mark.usefixtures("cache_clear")
|
|
27
26
|
def test_settings_global(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
_ = _site.SETTINGS
|
|
27
|
+
monkeypatch.setenv("pglift_postgresql", json.dumps({"invalid": None}))
|
|
28
|
+
with pytest.raises(click.ClickException, match="invalid site settings"):
|
|
29
|
+
_ = _site.SETTINGS
|
|
32
30
|
|
|
33
31
|
|
|
34
32
|
@pytest.mark.usefixtures("cache_clear")
|
|
35
|
-
def test_yaml_site_settings_error(
|
|
36
|
-
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
37
|
-
) -> None:
|
|
33
|
+
def test_yaml_site_settings_error(tmp_path: Path) -> None:
|
|
38
34
|
configdir = tmp_path / "pglift"
|
|
39
35
|
configdir.mkdir()
|
|
40
36
|
settings_fpath = configdir / "settings.yaml"
|
|
41
37
|
settings_fpath.write_text("this is not yaml")
|
|
42
|
-
with
|
|
43
|
-
|
|
38
|
+
with pytest.MonkeyPatch.context() as mp:
|
|
39
|
+
mp.setattr(_site.SiteSettings, "yaml_file", settings_fpath)
|
|
44
40
|
with pytest.raises(click.ClickException, match="invalid site settings"):
|
|
45
41
|
_ = _site.SETTINGS
|
|
@@ -70,15 +70,13 @@ def settings(settings: BaseSettings) -> Settings:
|
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
@pytest.fixture
|
|
73
|
-
def site_settings(
|
|
74
|
-
monkeypatch: pytest.MonkeyPatch, settings: Settings, tmp_path: Path
|
|
75
|
-
) -> Iterator[Settings]:
|
|
73
|
+
def site_settings(settings: Settings, tmp_path: Path) -> Iterator[Settings]:
|
|
76
74
|
"""Make _site.SETTINGS filled with the value of 'settings' fixture."""
|
|
77
75
|
sf = tmp_path / "settings.yaml"
|
|
78
76
|
sf.write_text(yaml.dump(settings.model_dump(mode="json")))
|
|
79
77
|
_site.clear_caches()
|
|
80
|
-
with
|
|
81
|
-
|
|
78
|
+
with pytest.MonkeyPatch.context() as mp:
|
|
79
|
+
mp.setattr(_site.SiteSettings, "yaml_file", sf)
|
|
82
80
|
yield _site.SETTINGS
|
|
83
81
|
_site.clear_caches()
|
|
84
82
|
|
|
@@ -283,6 +281,7 @@ def test_instance_commands_completion(obj: Obj) -> None:
|
|
|
283
281
|
"backup",
|
|
284
282
|
"backups",
|
|
285
283
|
"create",
|
|
284
|
+
"demote",
|
|
286
285
|
"drop",
|
|
287
286
|
"env",
|
|
288
287
|
"exec",
|
|
@@ -321,14 +320,12 @@ def test_site_settings_json(runner: CliRunner, settings: Settings, obj: Obj) ->
|
|
|
321
320
|
|
|
322
321
|
|
|
323
322
|
@pytest.mark.usefixtures("site_settings")
|
|
324
|
-
def test_site_settings_defaults(
|
|
325
|
-
runner: CliRunner, obj: Obj, monkeypatch: pytest.MonkeyPatch
|
|
326
|
-
) -> None:
|
|
323
|
+
def test_site_settings_defaults(runner: CliRunner, obj: Obj) -> None:
|
|
327
324
|
"""site-settings --defaults returns default settings, not accounting for
|
|
328
325
|
settings.yaml or env vars.
|
|
329
326
|
"""
|
|
330
|
-
with
|
|
331
|
-
|
|
327
|
+
with pytest.MonkeyPatch.context() as mp:
|
|
328
|
+
mp.setenv("PGLIFT_PREFIX", "/srv/pglift")
|
|
332
329
|
result = runner.invoke(
|
|
333
330
|
cli, ["site-settings", "-o", "json", "--defaults"], obj=obj
|
|
334
331
|
)
|
|
@@ -343,12 +340,11 @@ def test_site_settings_no_defaults(
|
|
|
343
340
|
runner: CliRunner,
|
|
344
341
|
settings: Settings,
|
|
345
342
|
obj: Obj,
|
|
346
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
347
343
|
) -> None:
|
|
348
344
|
s = settings.model_dump(mode="json")
|
|
349
345
|
assert s["powa"] is not None
|
|
350
|
-
with
|
|
351
|
-
|
|
346
|
+
with pytest.MonkeyPatch.context() as mp:
|
|
347
|
+
mp.setenv("PGLIFT_PREFIX", "/srv/pglift")
|
|
352
348
|
result = runner.invoke(
|
|
353
349
|
cli, ["site-settings", "-o", "json", "--no-defaults"], obj=obj
|
|
354
350
|
)
|
|
@@ -419,8 +415,8 @@ def test_instance_schema(runner: CliRunner, obj: Obj) -> None:
|
|
|
419
415
|
def test_instance_shell_var_missing(
|
|
420
416
|
runner: CliRunner, instance: Instance, obj: Obj, monkeypatch: pytest.MonkeyPatch
|
|
421
417
|
) -> None:
|
|
422
|
-
with patch("os.execle", autospec=True) as execle
|
|
423
|
-
|
|
418
|
+
with patch("os.execle", autospec=True) as execle:
|
|
419
|
+
monkeypatch.delenv("SHELL", raising=False)
|
|
424
420
|
r = runner.invoke(
|
|
425
421
|
cli,
|
|
426
422
|
["instance", "shell", instance.name],
|
|
@@ -438,8 +434,8 @@ def test_instance_shell_var_missing(
|
|
|
438
434
|
def test_instance_shell(
|
|
439
435
|
runner: CliRunner, instance: Instance, obj: Obj, monkeypatch: pytest.MonkeyPatch
|
|
440
436
|
) -> None:
|
|
441
|
-
with patch("os.execle", autospec=True) as execle
|
|
442
|
-
|
|
437
|
+
with patch("os.execle", autospec=True) as execle:
|
|
438
|
+
monkeypatch.setenv("SHELL", "fooshell")
|
|
443
439
|
runner.invoke(
|
|
444
440
|
cli,
|
|
445
441
|
["instance", "shell", instance.name],
|
|
@@ -55,17 +55,15 @@ def test_completion(runner: CliRunner, shell: str) -> None:
|
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
@pytest.mark.usefixtures("cache_clear")
|
|
58
|
-
def test_log_level(
|
|
59
|
-
runner: CliRunner, obj: Obj, monkeypatch: pytest.MonkeyPatch
|
|
60
|
-
) -> None:
|
|
58
|
+
def test_log_level(runner: CliRunner, obj: Obj) -> None:
|
|
61
59
|
@main.cli.command("foo")
|
|
62
60
|
def foo() -> None:
|
|
63
61
|
logger = logging.getLogger("pglift")
|
|
64
62
|
logger.debug("debug message")
|
|
65
63
|
print("something")
|
|
66
64
|
|
|
67
|
-
with
|
|
68
|
-
|
|
65
|
+
with pytest.MonkeyPatch.context() as mp:
|
|
66
|
+
mp.setenv("PGLIFT_CLI__LOG_LEVEL", "invalid")
|
|
69
67
|
result = runner.invoke(main.cli, ["foo"], obj=obj)
|
|
70
68
|
assert result.exit_code == 1
|
|
71
69
|
assert (
|
|
@@ -73,15 +71,15 @@ def test_log_level(
|
|
|
73
71
|
in result.stderr
|
|
74
72
|
)
|
|
75
73
|
|
|
76
|
-
with
|
|
77
|
-
|
|
74
|
+
with pytest.MonkeyPatch.context() as mp:
|
|
75
|
+
mp.setenv("PGLIFT_CLI__LOG_LEVEL", "debug")
|
|
78
76
|
result = runner.invoke(main.cli, ["foo"])
|
|
79
77
|
assert result.exit_code == 0 and "something" in result.stdout, result.stderr
|
|
80
78
|
assert "debug message" in result.stderr
|
|
81
79
|
|
|
82
|
-
with
|
|
80
|
+
with pytest.MonkeyPatch.context() as mp:
|
|
83
81
|
# -L option takes precedence
|
|
84
|
-
|
|
82
|
+
mp.setenv("PGLIFT_CLI__LOG_LEVEL", "debug")
|
|
85
83
|
result = runner.invoke(main.cli, ["-Linfo", "foo"], obj=obj)
|
|
86
84
|
assert result.exit_code == 0 and "something" in result.stdout, result.stderr
|
|
87
85
|
assert "debug message" not in result.stderr
|
|
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
|
|
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
|