pgbelt 0.6.2__tar.gz → 0.7.1__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.
- {pgbelt-0.6.2 → pgbelt-0.7.1}/PKG-INFO +5 -1
- {pgbelt-0.6.2 → pgbelt-0.7.1}/README.md +4 -0
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/cmd/convenience.py +5 -7
- pgbelt-0.7.1/pgbelt/cmd/preflight.py +631 -0
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/cmd/setup.py +26 -7
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/cmd/status.py +36 -0
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/cmd/sync.py +40 -15
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/cmd/teardown.py +2 -2
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/config/models.py +5 -1
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/util/dump.py +9 -5
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/util/pglogical.py +27 -14
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/util/postgres.py +177 -43
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pyproject.toml +7 -7
- pgbelt-0.6.2/pgbelt/cmd/preflight.py +0 -238
- {pgbelt-0.6.2 → pgbelt-0.7.1}/LICENSE +0 -0
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/__init__.py +0 -0
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/cmd/__init__.py +0 -0
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/cmd/helpers.py +0 -0
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/cmd/login.py +0 -0
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/cmd/schema.py +0 -0
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/config/__init__.py +0 -0
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/config/config.py +0 -0
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/config/remote.py +0 -0
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/main.py +0 -0
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/util/__init__.py +0 -0
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/util/asyncfuncs.py +0 -0
- {pgbelt-0.6.2 → pgbelt-0.7.1}/pgbelt/util/logs.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pgbelt
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: A CLI tool used to manage Postgres data migrations from beginning to end, for a single database or a fleet, leveraging pglogical replication.
|
|
5
5
|
Author: Varjitt Jeeva
|
|
6
6
|
Author-email: varjitt.jeeva@autodesk.com
|
|
@@ -65,6 +65,10 @@ Install pgbelt locally:
|
|
|
65
65
|
|
|
66
66
|
See [this doc](docs/quickstart.md)!
|
|
67
67
|
|
|
68
|
+
## Playbook
|
|
69
|
+
|
|
70
|
+
This playbook gets updated actively. If you have any issues, solutions could be found in [this playbook](docs/playbook.md).
|
|
71
|
+
|
|
68
72
|
## Contributing
|
|
69
73
|
|
|
70
74
|
We welcome contributions! See [this doc](CONTRIBUTING.md) on how to do so, including setting up your local development environment.
|
|
@@ -46,6 +46,10 @@ Install pgbelt locally:
|
|
|
46
46
|
|
|
47
47
|
See [this doc](docs/quickstart.md)!
|
|
48
48
|
|
|
49
|
+
## Playbook
|
|
50
|
+
|
|
51
|
+
This playbook gets updated actively. If you have any issues, solutions could be found in [this playbook](docs/playbook.md).
|
|
52
|
+
|
|
49
53
|
## Contributing
|
|
50
54
|
|
|
51
55
|
We welcome contributions! See [this doc](CONTRIBUTING.md) on how to do so, including setting up your local development environment.
|
|
@@ -34,9 +34,7 @@ def src_dsn(
|
|
|
34
34
|
echo(
|
|
35
35
|
conf.src.owner_dsn
|
|
36
36
|
if owner
|
|
37
|
-
else conf.src.pglogical_dsn
|
|
38
|
-
if pglogical
|
|
39
|
-
else conf.src.root_dsn
|
|
37
|
+
else conf.src.pglogical_dsn if pglogical else conf.src.root_dsn
|
|
40
38
|
)
|
|
41
39
|
|
|
42
40
|
|
|
@@ -56,9 +54,7 @@ def dst_dsn(
|
|
|
56
54
|
echo(
|
|
57
55
|
conf.dst.owner_dsn
|
|
58
56
|
if owner
|
|
59
|
-
else conf.dst.pglogical_dsn
|
|
60
|
-
if pglogical
|
|
61
|
-
else conf.dst.root_dsn
|
|
57
|
+
else conf.dst.pglogical_dsn if pglogical else conf.dst.root_dsn
|
|
62
58
|
)
|
|
63
59
|
|
|
64
60
|
|
|
@@ -66,7 +62,9 @@ async def _check_pkeys(
|
|
|
66
62
|
conf: DbupgradeConfig, logger: Logger
|
|
67
63
|
) -> tuple[list[str], list[str]]:
|
|
68
64
|
async with create_pool(conf.src.root_uri, min_size=1) as pool:
|
|
69
|
-
pkey_tables, no_pkey_tables, _ = await analyze_table_pkeys(
|
|
65
|
+
pkey_tables, no_pkey_tables, _ = await analyze_table_pkeys(
|
|
66
|
+
pool, conf.schema_name, logger
|
|
67
|
+
)
|
|
70
68
|
return pkey_tables, no_pkey_tables
|
|
71
69
|
|
|
72
70
|
|
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
from asyncio import gather
|
|
2
|
+
from collections.abc import Awaitable
|
|
3
|
+
|
|
4
|
+
from asyncpg import create_pool
|
|
5
|
+
from pgbelt.cmd.helpers import run_with_configs
|
|
6
|
+
from pgbelt.config.models import DbupgradeConfig
|
|
7
|
+
from pgbelt.util.logs import get_logger
|
|
8
|
+
from pgbelt.util.postgres import analyze_table_pkeys
|
|
9
|
+
from pgbelt.util.postgres import precheck_info
|
|
10
|
+
from tabulate import tabulate
|
|
11
|
+
from typer import echo
|
|
12
|
+
from typer import style
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _summary_table(results: dict, compared_extensions: list[str] = None) -> list[list]:
|
|
16
|
+
"""
|
|
17
|
+
Takes a dict of precheck results for all databases and returns a summary table for echo.
|
|
18
|
+
|
|
19
|
+
The summary table alters slightly if the results are for a destination database.
|
|
20
|
+
|
|
21
|
+
results format:
|
|
22
|
+
[
|
|
23
|
+
{
|
|
24
|
+
"server_version": "9.6.20",
|
|
25
|
+
"max_replication_slots": "10",
|
|
26
|
+
"max_worker_processes": "10",
|
|
27
|
+
"max_wal_senders": "10",
|
|
28
|
+
"shared_preload_libraries": ["pg_stat_statements", ...],
|
|
29
|
+
"rds.logical_replication": "on",
|
|
30
|
+
"schema: "public",
|
|
31
|
+
"extensions": ["uuid-ossp", ...],
|
|
32
|
+
"users": { // See pgbelt.util.postgres.precheck_info results["users"] for more info.
|
|
33
|
+
"root": {
|
|
34
|
+
"rolname": "root",
|
|
35
|
+
"rolcanlogin": True,
|
|
36
|
+
"rolcreaterole": True,
|
|
37
|
+
"rolinherit": True,
|
|
38
|
+
"rolsuper": True,
|
|
39
|
+
"memberof": ["rds_superuser", ...]
|
|
40
|
+
},
|
|
41
|
+
"owner": {
|
|
42
|
+
"rolname": "owner",
|
|
43
|
+
"rolcanlogin": True,
|
|
44
|
+
"rolcreaterole": False,
|
|
45
|
+
"rolinherit": True,
|
|
46
|
+
"rolsuper": False,
|
|
47
|
+
"memberof": ["rds_superuser", ...]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
...
|
|
52
|
+
]
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
summary_table = [
|
|
56
|
+
[
|
|
57
|
+
style("database", "yellow"),
|
|
58
|
+
style("server_version", "yellow"),
|
|
59
|
+
style("max_replication_slots", "yellow"),
|
|
60
|
+
style("max_worker_processes", "yellow"),
|
|
61
|
+
style("max_wal_senders", "yellow"),
|
|
62
|
+
style("shared_preload_libraries", "yellow"),
|
|
63
|
+
style("rds.logical_replication", "yellow"),
|
|
64
|
+
style("root user ok", "yellow"),
|
|
65
|
+
style("owner user ok", "yellow"),
|
|
66
|
+
style("targeted schema", "yellow"),
|
|
67
|
+
style("extensions ok", "yellow"),
|
|
68
|
+
]
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
results.sort(key=lambda d: d["db"])
|
|
72
|
+
|
|
73
|
+
for r in results:
|
|
74
|
+
root_ok = (
|
|
75
|
+
r["users"]["root"]["rolcanlogin"]
|
|
76
|
+
and r["users"]["root"]["rolcreaterole"]
|
|
77
|
+
and r["users"]["root"]["rolinherit"]
|
|
78
|
+
) and (
|
|
79
|
+
"rds_superuser" in r["users"]["root"]["memberof"]
|
|
80
|
+
or r["users"]["root"]["rolsuper"]
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Interestingly enough, we can tell if this is being run for a destination database if the compared_extensions is not None.
|
|
84
|
+
# This is because it is only set when we are ensuring all source extensions are in the destination.
|
|
85
|
+
is_dest_db = compared_extensions is not None
|
|
86
|
+
|
|
87
|
+
# If this is a destination database, we need to check if the owner can create objects.
|
|
88
|
+
|
|
89
|
+
if is_dest_db:
|
|
90
|
+
owner_ok = (r["users"]["owner"]["rolcanlogin"]) and (
|
|
91
|
+
r["users"]["owner"]["can_create"]
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
owner_ok = r["users"]["owner"]["rolcanlogin"]
|
|
95
|
+
|
|
96
|
+
shared_preload_libraries = "ok"
|
|
97
|
+
missing = []
|
|
98
|
+
if "pg_stat_statements" not in r["shared_preload_libraries"]:
|
|
99
|
+
missing.append("pg_stat_statements")
|
|
100
|
+
if "pglogical" not in r["shared_preload_libraries"]:
|
|
101
|
+
missing.append("pglogical")
|
|
102
|
+
if missing:
|
|
103
|
+
shared_preload_libraries = ", ".join(missing) + " are missing!"
|
|
104
|
+
|
|
105
|
+
summary_table.append(
|
|
106
|
+
[
|
|
107
|
+
style(r["db"], "green"),
|
|
108
|
+
style(
|
|
109
|
+
r["server_version"],
|
|
110
|
+
(
|
|
111
|
+
"green"
|
|
112
|
+
if float(
|
|
113
|
+
r["server_version"].rsplit(" ", 1)[0].rsplit(".", 1)[0]
|
|
114
|
+
)
|
|
115
|
+
>= 9.6
|
|
116
|
+
else "red"
|
|
117
|
+
),
|
|
118
|
+
),
|
|
119
|
+
style(
|
|
120
|
+
r["max_replication_slots"],
|
|
121
|
+
"green" if int(r["max_replication_slots"]) >= 2 else "red",
|
|
122
|
+
),
|
|
123
|
+
style(
|
|
124
|
+
r["max_worker_processes"],
|
|
125
|
+
"green" if int(r["max_worker_processes"]) >= 2 else "red",
|
|
126
|
+
),
|
|
127
|
+
style(
|
|
128
|
+
r["max_wal_senders"],
|
|
129
|
+
"green" if int(r["max_wal_senders"]) >= 10 else "red",
|
|
130
|
+
),
|
|
131
|
+
style(
|
|
132
|
+
shared_preload_libraries,
|
|
133
|
+
"green" if shared_preload_libraries == "ok" else "red",
|
|
134
|
+
),
|
|
135
|
+
style(
|
|
136
|
+
r["rds.logical_replication"],
|
|
137
|
+
(
|
|
138
|
+
"green"
|
|
139
|
+
if r["rds.logical_replication"] in ["on", "Not Applicable"]
|
|
140
|
+
else "red"
|
|
141
|
+
),
|
|
142
|
+
),
|
|
143
|
+
style(root_ok, "green" if root_ok else "red"),
|
|
144
|
+
style(owner_ok, "green" if owner_ok else "red"),
|
|
145
|
+
style(r["schema"], "green"),
|
|
146
|
+
]
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# If this is a destinatino DB, we are ensuring all source extensions are in the destination.
|
|
150
|
+
# If not, we don't want this column in the table.
|
|
151
|
+
if is_dest_db:
|
|
152
|
+
extensions_ok = all(
|
|
153
|
+
[e in r["extensions"] for e in compared_extensions]
|
|
154
|
+
) and all([e in compared_extensions for e in r["extensions"]])
|
|
155
|
+
summary_table[-1].append(
|
|
156
|
+
style(extensions_ok, "green" if extensions_ok else "red")
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
return summary_table
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _users_table(users: dict, is_dest_db: bool = False) -> list[list]:
|
|
163
|
+
"""
|
|
164
|
+
Takes a dict of user info and returns a table of the users for echo.
|
|
165
|
+
|
|
166
|
+
The users table alters slightly if the results are for a destination database.
|
|
167
|
+
|
|
168
|
+
users format:
|
|
169
|
+
{
|
|
170
|
+
"root": {
|
|
171
|
+
"rolname": "root",
|
|
172
|
+
"rolcanlogin": True,
|
|
173
|
+
"rolcreaterole": True,
|
|
174
|
+
"rolinherit": True,
|
|
175
|
+
"rolsuper": True,
|
|
176
|
+
"memberof": ["rds_superuser", ...]
|
|
177
|
+
},
|
|
178
|
+
"owner": {
|
|
179
|
+
"rolname": "owner",
|
|
180
|
+
"rolcanlogin": True,
|
|
181
|
+
"rolcreaterole": False,
|
|
182
|
+
"rolinherit": True,
|
|
183
|
+
"rolsuper": False,
|
|
184
|
+
"memberof": ["rds_superuser", ...]
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
See pgbelt.util.postgres.precheck_info results["users"] for more info..
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
users_table = [
|
|
192
|
+
[
|
|
193
|
+
style("user", "yellow"),
|
|
194
|
+
style("name", "yellow"),
|
|
195
|
+
style("can log in", "yellow"),
|
|
196
|
+
style("can make roles", "yellow"),
|
|
197
|
+
style("is superuser", "yellow"),
|
|
198
|
+
]
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
if is_dest_db:
|
|
202
|
+
users_table[0].insert(
|
|
203
|
+
5, style("can create objects in targeted schema", "yellow")
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
root_in_superusers = (
|
|
207
|
+
"rds_superuser" in users["root"]["memberof"] and users["root"]["rolinherit"]
|
|
208
|
+
) or (users["root"]["rolsuper"])
|
|
209
|
+
|
|
210
|
+
users_table.append(
|
|
211
|
+
[
|
|
212
|
+
style("root", "green"),
|
|
213
|
+
style(users["root"]["rolname"], "green"),
|
|
214
|
+
style(
|
|
215
|
+
users["root"]["rolcanlogin"],
|
|
216
|
+
"green" if users["root"]["rolcanlogin"] else "red",
|
|
217
|
+
),
|
|
218
|
+
style(
|
|
219
|
+
users["root"]["rolcreaterole"],
|
|
220
|
+
"green" if users["root"]["rolcreaterole"] else "red",
|
|
221
|
+
),
|
|
222
|
+
style(root_in_superusers, "green" if root_in_superusers else "red"),
|
|
223
|
+
style("not required", "green"),
|
|
224
|
+
]
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
users_table.append(
|
|
228
|
+
[
|
|
229
|
+
style("owner", "green"),
|
|
230
|
+
style(users["owner"]["rolname"], "green"),
|
|
231
|
+
style(
|
|
232
|
+
users["owner"]["rolcanlogin"],
|
|
233
|
+
"green" if users["owner"]["rolcanlogin"] else "red",
|
|
234
|
+
),
|
|
235
|
+
style("not required", "green"),
|
|
236
|
+
style("not required", "green"),
|
|
237
|
+
style(
|
|
238
|
+
users["owner"]["can_create"],
|
|
239
|
+
"green" if users["owner"]["can_create"] else "red",
|
|
240
|
+
),
|
|
241
|
+
]
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
return users_table
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _tables_table(
|
|
248
|
+
tables: list[dict], pkeys: list[dict], owner_name: str, schema_name: str
|
|
249
|
+
) -> list[list]:
|
|
250
|
+
"""
|
|
251
|
+
Takes a list of table dicts and returns a table of the tables for echo.
|
|
252
|
+
|
|
253
|
+
tables format:
|
|
254
|
+
[
|
|
255
|
+
{
|
|
256
|
+
"Name": "table_name",
|
|
257
|
+
"Schema": "schema_name",
|
|
258
|
+
"Owner": "owner_name"
|
|
259
|
+
},
|
|
260
|
+
...
|
|
261
|
+
]
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
tables_table = [
|
|
265
|
+
[
|
|
266
|
+
style("table name", "yellow"),
|
|
267
|
+
style("can replicate", "yellow"),
|
|
268
|
+
style("replication type", "yellow"),
|
|
269
|
+
style("schema", "yellow"),
|
|
270
|
+
style("owner", "yellow"),
|
|
271
|
+
]
|
|
272
|
+
]
|
|
273
|
+
|
|
274
|
+
for t in tables:
|
|
275
|
+
can_replicate = t["Schema"] == schema_name and t["Owner"] == owner_name
|
|
276
|
+
replication = (
|
|
277
|
+
("pglogical" if t["Name"] in pkeys else "dump and load")
|
|
278
|
+
if can_replicate
|
|
279
|
+
else "unavailable"
|
|
280
|
+
)
|
|
281
|
+
tables_table.append(
|
|
282
|
+
[
|
|
283
|
+
style(t["Name"], "green"),
|
|
284
|
+
style(can_replicate, "green" if can_replicate else "red"),
|
|
285
|
+
style(replication, "green" if can_replicate else "red"),
|
|
286
|
+
style(t["Schema"], "green" if t["Schema"] == schema_name else "red"),
|
|
287
|
+
style(t["Owner"], "green" if t["Owner"] == owner_name else "red"),
|
|
288
|
+
]
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
return tables_table
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _sequences_table(
|
|
295
|
+
sequences: list[dict], owner_name: str, schema_name: str
|
|
296
|
+
) -> list[list]:
|
|
297
|
+
"""
|
|
298
|
+
Takes a list of sequence dicts and returns a table of the sequences for echo.
|
|
299
|
+
|
|
300
|
+
sequences format:
|
|
301
|
+
[
|
|
302
|
+
{
|
|
303
|
+
"Name": "sequence_name",
|
|
304
|
+
"Schema": "schema_name",
|
|
305
|
+
"Owner": "owner_name"
|
|
306
|
+
},
|
|
307
|
+
...
|
|
308
|
+
]
|
|
309
|
+
"""
|
|
310
|
+
|
|
311
|
+
sequences_table = [
|
|
312
|
+
[
|
|
313
|
+
style("sequence name", "yellow"),
|
|
314
|
+
style("can replicate", "yellow"),
|
|
315
|
+
style("schema", "yellow"),
|
|
316
|
+
style("owner", "yellow"),
|
|
317
|
+
]
|
|
318
|
+
]
|
|
319
|
+
|
|
320
|
+
for s in sequences:
|
|
321
|
+
can_replicate = s["Schema"] == schema_name and s["Owner"] == owner_name
|
|
322
|
+
sequences_table.append(
|
|
323
|
+
[
|
|
324
|
+
style(s["Name"], "green"),
|
|
325
|
+
style(can_replicate, "green" if can_replicate else "red"),
|
|
326
|
+
style(s["Schema"], "green" if s["Schema"] == schema_name else "red"),
|
|
327
|
+
style(s["Owner"], "green" if s["Owner"] == owner_name else "red"),
|
|
328
|
+
]
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
return sequences_table
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _extensions_table(
|
|
335
|
+
source_extensions: list[str], destination_extensions: list[str]
|
|
336
|
+
) -> list[list]:
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
Takes a list of source and destination extensions and returns a table of the extensions for echo.
|
|
340
|
+
It will flag any extensions that are not in the destination database but are in the source database.
|
|
341
|
+
|
|
342
|
+
<source/destination>_extensions format:
|
|
343
|
+
[
|
|
344
|
+
"uuid-ossp",
|
|
345
|
+
...
|
|
346
|
+
]
|
|
347
|
+
|
|
348
|
+
"""
|
|
349
|
+
|
|
350
|
+
extensions_table = [
|
|
351
|
+
[
|
|
352
|
+
style("extension in source DB", "yellow"),
|
|
353
|
+
style("is in destination", "yellow"),
|
|
354
|
+
]
|
|
355
|
+
]
|
|
356
|
+
|
|
357
|
+
for e in source_extensions:
|
|
358
|
+
extensions_table.append(
|
|
359
|
+
[
|
|
360
|
+
style(e["extname"], "green"),
|
|
361
|
+
style(
|
|
362
|
+
e in destination_extensions,
|
|
363
|
+
"green" if e in destination_extensions else "red",
|
|
364
|
+
),
|
|
365
|
+
]
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
return extensions_table
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
async def _print_prechecks(results: list[dict]) -> list[list]:
|
|
372
|
+
"""
|
|
373
|
+
Print out the results of the prechecks in a human readable format.
|
|
374
|
+
If there are multiple databases, only print the summary table.
|
|
375
|
+
If there is only one database, print the summary table and more detailed info.
|
|
376
|
+
|
|
377
|
+
results format:
|
|
378
|
+
[
|
|
379
|
+
{
|
|
380
|
+
"db": "db_name",
|
|
381
|
+
"src": {
|
|
382
|
+
"server_version": "9.6.20",
|
|
383
|
+
"max_replication_slots": "10",
|
|
384
|
+
"max_worker_processes": "10",
|
|
385
|
+
"max_wal_senders": "10",
|
|
386
|
+
"pg_stat_statements": "installed",
|
|
387
|
+
"pglogical": "installed",
|
|
388
|
+
"rds.logical_replication": "on",
|
|
389
|
+
"schema: "public",
|
|
390
|
+
"users": { // See pgbelt.util.postgres.precheck_info results["users"] for more info.
|
|
391
|
+
"root": {
|
|
392
|
+
"rolname": "root",
|
|
393
|
+
"rolcanlogin": True,
|
|
394
|
+
"rolcreaterole": True,
|
|
395
|
+
"rolinherit": True,
|
|
396
|
+
"rolsuper": True,
|
|
397
|
+
"memberof": ["rds_superuser", ...]
|
|
398
|
+
},
|
|
399
|
+
"owner": {
|
|
400
|
+
"rolname": "owner",
|
|
401
|
+
"rolcanlogin": True,
|
|
402
|
+
"rolcreaterole": False,
|
|
403
|
+
"rolinherit": True,
|
|
404
|
+
"rolsuper": False,
|
|
405
|
+
"memberof": ["rds_superuser", ...],
|
|
406
|
+
"can_create": True
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
"dst": {
|
|
411
|
+
"server_version": "9.6.20",
|
|
412
|
+
"max_replication_slots": "10",
|
|
413
|
+
"max_worker_processes": "10",
|
|
414
|
+
"max_wal_senders": "10",
|
|
415
|
+
"pg_stat_statements": "installed",
|
|
416
|
+
"pglogical": "installed",
|
|
417
|
+
"rds.logical_replication": "on",
|
|
418
|
+
"schema: "public",
|
|
419
|
+
"users": { // See pgbelt.util.postgres.precheck_info results["users"] for more info.
|
|
420
|
+
"root": {
|
|
421
|
+
"rolname": "root",
|
|
422
|
+
"rolcanlogin": True,
|
|
423
|
+
"rolcreaterole": True,
|
|
424
|
+
"rolinherit": True,
|
|
425
|
+
"rolsuper": True,
|
|
426
|
+
"memberof": ["rds_superuser", ...]
|
|
427
|
+
},
|
|
428
|
+
"owner": {
|
|
429
|
+
"rolname": "owner",
|
|
430
|
+
"rolcanlogin": True,
|
|
431
|
+
"rolcreaterole": False,
|
|
432
|
+
"rolinherit": True,
|
|
433
|
+
"rolsuper": False,
|
|
434
|
+
"memberof": ["rds_superuser", ...],
|
|
435
|
+
"can_create": True
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
...
|
|
441
|
+
]
|
|
442
|
+
"""
|
|
443
|
+
|
|
444
|
+
src_summaries = []
|
|
445
|
+
dst_summaries = []
|
|
446
|
+
for r in results:
|
|
447
|
+
src_summaries.append(r["src"])
|
|
448
|
+
dst_summaries.append(r["dst"])
|
|
449
|
+
|
|
450
|
+
src_summary_table = _summary_table(src_summaries)
|
|
451
|
+
dst_summary_table = _summary_table(
|
|
452
|
+
dst_summaries, compared_extensions=r["src"]["extensions"]
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
if len(results) != 1:
|
|
456
|
+
|
|
457
|
+
# For mulitple databases, we only print the summary table.
|
|
458
|
+
|
|
459
|
+
src_multi_display_string = (
|
|
460
|
+
style("\nSource DB Configuration Summary", "blue")
|
|
461
|
+
+ "\n"
|
|
462
|
+
+ tabulate(src_summary_table, headers="firstrow")
|
|
463
|
+
)
|
|
464
|
+
echo(src_multi_display_string)
|
|
465
|
+
dst_multi_display_string = (
|
|
466
|
+
style("\nDestination DB Configuration Summary", "blue")
|
|
467
|
+
+ "\n"
|
|
468
|
+
+ tabulate(dst_summary_table, headers="firstrow")
|
|
469
|
+
)
|
|
470
|
+
echo(dst_multi_display_string)
|
|
471
|
+
|
|
472
|
+
return src_multi_display_string, dst_multi_display_string
|
|
473
|
+
|
|
474
|
+
# If we ran only on one db print more detailed info
|
|
475
|
+
r = results[0]
|
|
476
|
+
|
|
477
|
+
# TODO: We should confirm the named schema exists in the database and alert the user if it does not (red in column if not found).
|
|
478
|
+
|
|
479
|
+
# Source DB Tables
|
|
480
|
+
|
|
481
|
+
src_users_table = _users_table(r["src"]["users"])
|
|
482
|
+
src_tables_table = _tables_table(
|
|
483
|
+
r["src"]["tables"],
|
|
484
|
+
r["src"]["pkeys"],
|
|
485
|
+
r["src"]["users"]["owner"]["rolname"],
|
|
486
|
+
r["src"]["schema"],
|
|
487
|
+
)
|
|
488
|
+
src_sequences_table = _sequences_table(
|
|
489
|
+
r["src"]["sequences"], r["src"]["users"]["owner"]["rolname"], r["src"]["schema"]
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
if len(src_tables_table) == 1:
|
|
493
|
+
src_tables_table = [
|
|
494
|
+
[
|
|
495
|
+
style(
|
|
496
|
+
"ALERT: Not able to find tables to replicate, check your config's 'schema_name'",
|
|
497
|
+
"red",
|
|
498
|
+
)
|
|
499
|
+
]
|
|
500
|
+
]
|
|
501
|
+
|
|
502
|
+
if len(src_sequences_table) == 1:
|
|
503
|
+
src_sequences_table = [
|
|
504
|
+
[
|
|
505
|
+
style(
|
|
506
|
+
"ALERT: Not able to find sequences to replicate, check your config's 'schema_name'",
|
|
507
|
+
"red",
|
|
508
|
+
)
|
|
509
|
+
]
|
|
510
|
+
]
|
|
511
|
+
|
|
512
|
+
source_display_string = (
|
|
513
|
+
style("\nSource DB Configuration Summary", "blue")
|
|
514
|
+
+ "\n"
|
|
515
|
+
+ "\n"
|
|
516
|
+
+ tabulate(src_summary_table, headers="firstrow")
|
|
517
|
+
+ "\n"
|
|
518
|
+
+ style("\nRequired Users Summary", "yellow")
|
|
519
|
+
+ "\n"
|
|
520
|
+
+ tabulate(src_users_table, headers="firstrow")
|
|
521
|
+
+ "\n"
|
|
522
|
+
+ style("\nTable Compatibility Summary", "yellow")
|
|
523
|
+
+ "\n"
|
|
524
|
+
+ tabulate(
|
|
525
|
+
src_tables_table, headers="firstrow" if len(src_tables_table) > 1 else ""
|
|
526
|
+
)
|
|
527
|
+
+ "\n"
|
|
528
|
+
+ style("\nSequence Compatibility Summary", "yellow")
|
|
529
|
+
+ "\n"
|
|
530
|
+
+ tabulate(
|
|
531
|
+
src_sequences_table,
|
|
532
|
+
headers="firstrow" if len(src_sequences_table) > 1 else "",
|
|
533
|
+
)
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
echo(source_display_string)
|
|
537
|
+
|
|
538
|
+
echo("\n" + "=" * 80)
|
|
539
|
+
|
|
540
|
+
# Destination DB Tables
|
|
541
|
+
|
|
542
|
+
dst_users_table = _users_table(r["dst"]["users"], is_dest_db=True)
|
|
543
|
+
extenstions_table = _extensions_table(
|
|
544
|
+
r["src"]["extensions"], r["dst"]["extensions"]
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
destination_display_string = (
|
|
548
|
+
style("\nDestination DB Configuration Summary", "blue")
|
|
549
|
+
+ "\n"
|
|
550
|
+
+ "\n"
|
|
551
|
+
+ tabulate(dst_summary_table, headers="firstrow")
|
|
552
|
+
+ "\n"
|
|
553
|
+
+ style("\nExtension Matchup Summary", "yellow")
|
|
554
|
+
+ "\n"
|
|
555
|
+
+ tabulate(extenstions_table, headers="firstrow")
|
|
556
|
+
+ "\n"
|
|
557
|
+
+ style("\nRequired Users Summary", "yellow")
|
|
558
|
+
+ "\n"
|
|
559
|
+
+ tabulate(dst_users_table, headers="firstrow")
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
echo(destination_display_string)
|
|
563
|
+
|
|
564
|
+
return src_summary_table, dst_summary_table
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
@run_with_configs(skip_dst=True, results_callback=_print_prechecks)
|
|
568
|
+
async def precheck(config_future: Awaitable[DbupgradeConfig]) -> dict:
|
|
569
|
+
"""
|
|
570
|
+
Report whether your source database meets the basic requirements for pgbelt.
|
|
571
|
+
Any red item in a row in the table indicates a requirement not satisfied by your db.
|
|
572
|
+
This command can not check network connectivity between your source and destination!
|
|
573
|
+
|
|
574
|
+
If a dbname is given this will also show whether the configuration of
|
|
575
|
+
the root and owner users seems ok and a summary of whether each
|
|
576
|
+
table and sequence in the database can be replicated.
|
|
577
|
+
If a row contains any red that sequence or table can not be replicated.
|
|
578
|
+
"""
|
|
579
|
+
|
|
580
|
+
conf = await config_future
|
|
581
|
+
pools = await gather(
|
|
582
|
+
create_pool(conf.src.root_uri, min_size=1),
|
|
583
|
+
create_pool(conf.src.owner_uri, min_size=1),
|
|
584
|
+
create_pool(conf.dst.root_uri, min_size=1),
|
|
585
|
+
)
|
|
586
|
+
src_root_pool, src_owner_pool, dst_root_pool = pools
|
|
587
|
+
|
|
588
|
+
try:
|
|
589
|
+
src_logger = get_logger(conf.db, conf.dc, "preflight.src")
|
|
590
|
+
dst_logger = get_logger(conf.db, conf.dc, "preflight.dst")
|
|
591
|
+
|
|
592
|
+
result = {}
|
|
593
|
+
|
|
594
|
+
# Source DB Data
|
|
595
|
+
result["src"] = await precheck_info(
|
|
596
|
+
src_root_pool,
|
|
597
|
+
conf.src.root_user.name,
|
|
598
|
+
conf.src.owner_user.name,
|
|
599
|
+
conf.tables,
|
|
600
|
+
conf.sequences,
|
|
601
|
+
conf.schema_name,
|
|
602
|
+
src_logger,
|
|
603
|
+
)
|
|
604
|
+
result["src"]["pkeys"], _, _ = await analyze_table_pkeys(
|
|
605
|
+
src_owner_pool, conf.schema_name, src_logger
|
|
606
|
+
)
|
|
607
|
+
result["src"]["schema"] = conf.schema_name
|
|
608
|
+
|
|
609
|
+
# Destination DB Data
|
|
610
|
+
result["dst"] = await precheck_info(
|
|
611
|
+
dst_root_pool,
|
|
612
|
+
conf.dst.root_user.name,
|
|
613
|
+
conf.dst.owner_user.name,
|
|
614
|
+
conf.tables,
|
|
615
|
+
conf.sequences,
|
|
616
|
+
conf.schema_name,
|
|
617
|
+
dst_logger,
|
|
618
|
+
)
|
|
619
|
+
# No need to analyze pkeys for the destination database (we use this to determine replication method in only the forward case).
|
|
620
|
+
result["dst"]["schema"] = conf.schema_name
|
|
621
|
+
|
|
622
|
+
# The precheck view code treats "db" as the name of the database pair, not the logical dbname of the database.
|
|
623
|
+
result["src"]["db"] = conf.db
|
|
624
|
+
result["dst"]["db"] = conf.db
|
|
625
|
+
|
|
626
|
+
return result
|
|
627
|
+
finally:
|
|
628
|
+
await gather(*[p.close() for p in pools])
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
COMMANDS = [precheck]
|