lcdp-postgres-utils 1.0.4.dev36__tar.gz → 1.5.8__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.
@@ -1,14 +1,17 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: lcdp-postgres-utils
3
- Version: 1.0.4.dev36
3
+ Version: 1.5.8
4
4
  Summary: Postgres Utils to create users, databases, functions, ...
5
5
  Author: Le Comptoir Des Pharmacies
6
6
  Author-email: webmaster@lecomptoirdespharmacies.fr
7
- Requires-Python: >=3.8,<4.0
7
+ Requires-Python: >=3.8
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Programming Language :: Python :: 3.8
10
10
  Classifier: Programming Language :: Python :: 3.9
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
13
16
  Requires-Dist: boto3 (>=1.26.55,<2.0.0)
14
17
  Requires-Dist: pg8000 (>=1.29.4,<2.0.0)
@@ -5,7 +5,7 @@ class ExecutorFactory:
5
5
  def __init__(self, engine_version="13"):
6
6
  self.engine_version = engine_version.split('.')[0]
7
7
 
8
- def build(self, database_name, endpoint, user_name, db_password, decrypt_func):
8
+ def build(self, database_name, endpoint, user_name, db_password, decrypt_func=None):
9
9
  try:
10
10
  klass = globals()["Executor{0}".format(self.engine_version)]
11
11
  return klass(database_name, endpoint, user_name, db_password, decrypt_func)
@@ -1,2 +1,3 @@
1
1
  from .executor13 import Executor13
2
2
  from .executor14 import Executor14
3
+ from .executor16 import Executor16
@@ -3,11 +3,15 @@ from ..utils import get_connection
3
3
 
4
4
  # Executor for postgres 13
5
5
  class Executor13:
6
- dbz_publication_name = "dbz_publication"
6
+ dbz_publication_prefix = "dbz"
7
7
  dbz_signal_tablename = "debezium_signal"
8
+ dbz_heartbeat_tablename = "debezium_heartbeat"
9
+
10
+ ROLE_READ_ONLY = "role_read_only"
8
11
 
9
12
  def __init__(self, database_name, endpoint, user_name, db_password, decrypt_func):
10
13
  self.database_name = database_name
14
+ self.connected_user_name = user_name
11
15
  self.__connection = get_connection(database_name, endpoint, user_name, db_password, decrypt_func)
12
16
  self.cursor = self.__connection.cursor()
13
17
  self.logs = []
@@ -32,9 +36,19 @@ class Executor13:
32
36
  type varchar(32) NOT NULL,
33
37
  data varchar(2048)
34
38
  );
35
- GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE ON \"{1}\" TO \"{2}\";
39
+ GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE ON \"{0}\" TO \"{1}\";
36
40
  """.format(Executor13.dbz_signal_tablename,
37
- Executor13.dbz_signal_tablename,
41
+ cdc_user_name)
42
+
43
+ def __create_debezium_heartbeat_table_sql(self, cdc_user_name):
44
+ return """
45
+ CREATE TABLE \"{0}\" (
46
+ id uuid DEFAULT gen_random_uuid() CONSTRAINT debezium_heartbeat_pk PRIMARY KEY,
47
+ last_update timestamp not null
48
+ );
49
+ INSERT INTO \"{0}\" (last_update) VALUES (now());
50
+ GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE ON \"{0}\" TO \"{1}\";
51
+ """.format(Executor13.dbz_heartbeat_tablename,
38
52
  cdc_user_name)
39
53
 
40
54
  def __create_immutable_unaccent_function(self):
@@ -42,7 +56,7 @@ class Executor13:
42
56
  CREATE OR REPLACE FUNCTION immutable_unaccent(text)
43
57
  RETURNS text AS
44
58
  $func$
45
- SELECT unaccent('unaccent', $1)
59
+ SELECT public.unaccent($1)
46
60
  $func$ LANGUAGE sql IMMUTABLE PARALLEL SAFE STRICT;
47
61
  """
48
62
 
@@ -108,18 +122,54 @@ class Executor13:
108
122
  def __create_pg_trgm_extension(self):
109
123
  return "CREATE EXTENSION IF NOT EXISTS pg_trgm;"
110
124
 
125
+ def __create_hstore_extension(self):
126
+ return "CREATE EXTENSION IF NOT EXISTS hstore;"
127
+
128
+ def __create_role_sql(self, role):
129
+ return "CREATE ROLE \"{0}\";".format(role)
130
+
111
131
  def __create_publication(self, value):
112
132
  return "CREATE PUBLICATION \"{0}\" FOR ALL TABLES;".format(value)
113
133
 
114
134
  def __create_schema(self, schema_name):
115
135
  return "CREATE SCHEMA IF NOT EXISTS \"{0}\";".format(schema_name)
116
136
 
137
+ def __grant_create(self):
138
+ return "GRANT CREATE ON DATABASE \"{0}\" TO \"{1}\";".format(self.database_name, self.connected_user_name)
139
+
117
140
  def __create_user_sql(self, user_name, user_password):
118
141
  return "CREATE USER \"{0}\" WITH PASSWORD '{1}';".format(user_name, user_password)
119
142
 
120
143
  def __alter_database_owner_sql(self, database_name, user_name):
121
144
  return "ALTER DATABASE \"{0}\" OWNER TO \"{1}\";".format(database_name, user_name)
122
145
 
146
+ def __alter_grant_read_only_sql(self, role):
147
+ return """
148
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO \"{0}\";
149
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON SEQUENCES TO \"{0}\";
150
+ """.format(role)
151
+
152
+ def __grant_select_read_only_sql(self, reader):
153
+ return """
154
+ GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{0}\";
155
+ GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO \"{0}\";
156
+ """.format(reader)
157
+
158
+ def __grant_select_on_table(self, user_name, table_name):
159
+ return """
160
+ GRANT SELECT ON TABLE \"{1}\" TO \"{0}\";
161
+ """.format(user_name, table_name)
162
+
163
+ def __grant_write_on_table(self, user_name, table_name):
164
+ return """
165
+ GRANT INSERT, UPDATE, DELETE ON TABLE \"{1}\" TO \"{0}\";
166
+ """.format(user_name, table_name)
167
+
168
+ def __grant_usage_on_all_sequences(self, user_name):
169
+ return """
170
+ GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO \"{0}\";
171
+ """.format(user_name)
172
+
123
173
  def __alter_database_work_mem_sql(self, value):
124
174
  return "ALTER DATABASE \"{0}\" SET work_mem TO \"{1}\";".format(self.database_name, value)
125
175
 
@@ -127,24 +177,18 @@ class Executor13:
127
177
  return """
128
178
  GRANT CONNECT ON DATABASE \"{2}\" TO \"{1}\";
129
179
  GRANT USAGE ON SCHEMA \"{0}\" TO \"{1}\";
130
- GRANT SELECT ON ALL TABLES IN SCHEMA \"{0}\" TO \"{1}\";
131
- GRANT SELECT ON ALL SEQUENCES IN SCHEMA \"{0}\" TO \"{1}\";
132
- ALTER DEFAULT PRIVILEGES IN SCHEMA \"{0}\" GRANT SELECT ON TABLES TO \"{1}\";
133
- ALTER DEFAULT PRIVILEGES IN SCHEMA \"{0}\" GRANT SELECT ON SEQUENCES TO \"{1}\";
134
180
  """.format(schema_name, user_name, self.database_name)
135
181
 
136
182
  def __grant_create_table(self, user_name):
137
183
  return f"GRANT CREATE, TEMPORARY ON DATABASE \"{self.database_name}\" TO \"{user_name}\";"
138
184
 
139
- def __grant_replication_user_sql(self, user_name):
140
- return "GRANT rds_replication TO \"{0}\";".format(user_name)
141
-
142
- def __grant_superuser_user_sql(self, user_name):
143
- return "GRANT rds_superuser TO \"{0}\";".format(user_name)
185
+ def __grant_role_to_user_sql(self, role, user_name):
186
+ return "GRANT \"{0}\" TO \"{1}\";".format(role, user_name)
144
187
 
145
188
  def __grant_schema_all_access(self, user_name, schema_name):
146
189
  return """
147
190
  GRANT CONNECT ON DATABASE \"{2}\" TO \"{1}\";
191
+ GRANT CREATE ON DATABASE \"{2}\" TO \"{1}\";
148
192
  GRANT USAGE ON SCHEMA \"{0}\" TO \"{1}\";
149
193
  GRANT ALL ON ALL TABLES IN SCHEMA \"{0}\" TO \"{1}\";
150
194
  GRANT ALL ON ALL SEQUENCES IN SCHEMA \"{0}\" TO \"{1}\";
@@ -152,36 +196,51 @@ class Executor13:
152
196
  ALTER DEFAULT PRIVILEGES IN SCHEMA \"{0}\" GRANT ALL ON SEQUENCES TO \"{1}\";
153
197
  """.format(schema_name, user_name, self.database_name)
154
198
 
199
+ def __check_role_sql(self, role):
200
+ return "select * from pg_roles where rolname = '{0}';".format(role)
201
+
155
202
  def __check_user_sql(self, user_name):
156
203
  return "select * from pg_user where usename = '{0}';".format(user_name)
157
204
 
158
205
  def __check_database_sql(self, database_name):
159
206
  return "select * from pg_database where datname = '{0}';".format(database_name)
160
207
 
208
+ def __check_replication_slot_sql(self, slot_name):
209
+ return "select * from pg_replication_slots where slot_name = '{0}';".format(slot_name)
210
+
161
211
  def __check_table_sql(self, value):
162
212
  return "select * from pg_tables where tablename = '{0}';".format(value)
163
213
 
164
214
  def __check_debezium_signal_table_sql(self):
165
215
  return self.__check_table_sql(Executor13.dbz_signal_tablename)
166
216
 
217
+ def __check_debezium_heartbeat_table_sql(self):
218
+ return self.__check_table_sql(Executor13.dbz_heartbeat_tablename)
219
+
167
220
  def __check_database_unaccent_extension(self):
168
221
  return "select * from pg_extension where extname = 'unaccent';"
169
222
 
170
223
  def __check_database_pg_trgm_extension(self):
171
224
  return "select * from pg_extension where extname = 'pg_trgm';"
172
225
 
226
+ def __check_database_hstore_extension(self):
227
+ return "select * from pg_extension where extname = 'hstore';"
228
+
173
229
  def __check_publication_sql(self, value):
174
230
  return "select * from pg_publication where pubname = '{0}';".format(value)
175
231
 
176
232
  # ~~~~~ Log action ~~~~~ #
233
+ def __log_create_role(self, role):
234
+ return "Creation du role : {0} \n".format(role)
235
+
177
236
  def __log_create_user(self, user_name):
178
237
  return "Creation de l'utilisateur : {0} \n".format(user_name)
179
238
 
180
239
  def __log_create_database(self, database_name):
181
240
  return "Creation de la base de donnees : {0} \n".format(database_name)
182
241
 
183
- def __log_create_debezium_signal_table(self):
184
- return "Creation de la table {0} pour la base de donnees : {1} \n".format(Executor13.dbz_signal_tablename, self.database_name)
242
+ def __log_create_table(self, tablename):
243
+ return "Creation de la table {0} pour la base de donnees : {1} \n".format(tablename, self.database_name)
185
244
 
186
245
  def __log_create_unaccent_extension(self):
187
246
  return "Creation de l'extension unaccent pour la base de donnees : {0} \n".format(self.database_name)
@@ -201,6 +260,9 @@ class Executor13:
201
260
  def __log_create_pg_trgm_extension(self):
202
261
  return "Creation de l'extension pg_trgm pour la base de donnees : {0} \n".format(self.database_name)
203
262
 
263
+ def __log_create_hstore_extension(self):
264
+ return "Creation de l'extension hstore pour la base de donnees : {0} \n".format(self.database_name)
265
+
204
266
  def __log_create_publication(self, value):
205
267
  return "Creation de la publication {0} pour la base de donnees : {1} \n".format(value, self.database_name)
206
268
 
@@ -211,6 +273,26 @@ class Executor13:
211
273
  return "L'utilisateur {0} est proprietaire de la base {1} \n" \
212
274
  .format(user_name, database_name)
213
275
 
276
+ def __log_alter_database_read_to_role(self, role_owner, role_reader):
277
+ return "Le role {0} rend sa base accessible en lecture au role {1} \n" \
278
+ .format(role_owner, role_reader)
279
+
280
+ def __log_alter_grant_read_only(self, reader):
281
+ return "La base {0} est accessible en lecture pour role {1} \n" \
282
+ .format(self.database_name, reader)
283
+
284
+ def __log_grant_select_on_table(self, user_name, table_name):
285
+ return "L'utilisateur {0} a herite des droits select sur la table {1} \n".format(user_name, table_name)
286
+
287
+ def __log_grant_write_on_table(self, user_name, table_name):
288
+ return "L'utilisateur {0} a herite des droits insert, update, delete sur la table {1} \n".format(user_name, table_name)
289
+
290
+ def __log_grant_usage_on_all_sequences(self, user_name):
291
+ return "L'utilisateur {0} a herite des droits usage sur le schema public \n".format(user_name)
292
+
293
+ def __log_grant_select_read_only(self, reader):
294
+ return "{0} a herite des droits read only \n".format(reader)
295
+
214
296
  def __log_grant_schema_read_only_user(self, user_name, schema_name):
215
297
  return "L'utilisateur {0} a herite des droits read only sur le schema {1} \n".format(user_name, schema_name)
216
298
 
@@ -218,6 +300,9 @@ class Executor13:
218
300
  return "L'utilisateur {0} a herite des droits de creation de base de table sur la base de données {1} \n"\
219
301
  .format(user_name, self.database_name)
220
302
 
303
+ def __log_grant_role_to_user(self, role, user_name):
304
+ return "L'utilisateur {0} a herite du role {1} \n".format(user_name, role)
305
+
221
306
  def __log_grant_replication_user(self, user_name):
222
307
  return "L'utilisateur {0} a herite des droits replication \n".format(user_name)
223
308
 
@@ -235,6 +320,11 @@ class Executor13:
235
320
  self.cursor.execute(query)
236
321
  return self.cursor.fetchone()
237
322
 
323
+ def __execute_create_role(self, role):
324
+ if not self.__execute_select_query(self.__check_role_sql(role)):
325
+ self.cursor.execute(self.__create_role_sql(role))
326
+ return self.__log_create_role(role)
327
+
238
328
  def __execute_create_user(self, user_name, user_password):
239
329
  if not self.__execute_select_query(self.__check_user_sql(user_name)):
240
330
  self.cursor.execute(self.__create_user_sql(user_name, user_password))
@@ -271,20 +361,51 @@ class Executor13:
271
361
  self.cursor.execute(self.__create_pg_trgm_extension())
272
362
  return self.__log_create_pg_trgm_extension()
273
363
 
274
- def __execute_create_publication(self, value):
275
- if not self.__execute_select_query(self.__check_publication_sql(value)):
276
- self.cursor.execute(self.__create_publication(value))
277
- return self.__log_create_publication(value)
364
+ def __execute_create_hstore_extension(self):
365
+ if not self.__execute_select_query(self.__check_database_hstore_extension()):
366
+ self.cursor.execute(self.__create_hstore_extension())
367
+ return self.__log_create_hstore_extension()
368
+
369
+ def __execute_create_publication(self, prefix):
370
+ publication_name = '{0}_{1}'.format(prefix, 'publication')
371
+ if not self.__execute_select_query(self.__check_publication_sql(publication_name)):
372
+ self.cursor.execute(self.__create_publication(publication_name))
373
+ return self.__log_create_publication(publication_name)
278
374
 
279
375
  def __execute_create_debezium_signal_table(self, cdc_user_name):
280
376
  if not self.__execute_select_query(self.__check_debezium_signal_table_sql()):
281
377
  self.cursor.execute(self.__create_debezium_signal_table_sql(cdc_user_name))
282
- return self.__log_create_debezium_signal_table()
378
+ return self.__log_create_table(Executor13.dbz_signal_tablename)
379
+
380
+ def __execute_create_debezium_heartbeat_table(self, cdc_user_name):
381
+ if not self.__execute_select_query(self.__check_debezium_heartbeat_table_sql()):
382
+ self.cursor.execute(self.__create_debezium_heartbeat_table_sql(cdc_user_name))
383
+ return self.__log_create_table(Executor13.dbz_heartbeat_tablename)
283
384
 
284
385
  def __alter_database_owner(self, database_name, user_name):
285
386
  self.cursor.execute(self.__alter_database_owner_sql(database_name, user_name))
286
387
  return self.__log_alter_database_owner(database_name, user_name)
287
388
 
389
+ def __alter_grant_read_only(self, reader):
390
+ self.cursor.execute(self.__alter_grant_read_only_sql(reader))
391
+ return self.__log_alter_grant_read_only(reader)
392
+
393
+ def __execute_grant_read_on_table(self, user_name, table_name):
394
+ self.cursor.execute(self.__grant_select_on_table(user_name, table_name))
395
+ return self.__log_grant_select_on_table(user_name, table_name)
396
+
397
+ def __execute_grant_write_on_table(self, user_name, table_name):
398
+ self.cursor.execute(self.__grant_write_on_table(user_name, table_name))
399
+ return self.__log_grant_write_on_table(user_name, table_name)
400
+
401
+ def __execute_grant_usage_on_all_sequences(self, user_name):
402
+ self.cursor.execute(self.__grant_usage_on_all_sequences(user_name))
403
+ return self.__log_grant_usage_on_all_sequences(user_name)
404
+
405
+ def __grant_select_read_only(self, reader):
406
+ self.cursor.execute(self.__grant_select_read_only_sql(reader))
407
+ return self.__log_grant_select_read_only(reader)
408
+
288
409
  def __alter_database_work_mem(self, value):
289
410
  self.cursor.execute(self.__alter_database_work_mem_sql(value))
290
411
  return self.__log_alter_database_work_mem()
@@ -297,15 +418,20 @@ class Executor13:
297
418
  self.cursor.execute(self.__grant_create_table(user_name))
298
419
  return self.__log_grant_create_table(user_name)
299
420
 
421
+ def __execute_grant_read_only(self, user_name):
422
+ self.cursor.execute(self.__grant_role_to_user_sql(Executor13.ROLE_READ_ONLY, user_name))
423
+ return self.__log_grant_role_to_user(Executor13.ROLE_READ_ONLY, user_name)
424
+
300
425
  def __execute_grant_replication(self, user_name):
301
- self.cursor.execute(self.__grant_replication_user_sql(user_name))
426
+ self.cursor.execute(self.__grant_role_to_user_sql("rds_replication", user_name))
302
427
  return self.__log_grant_replication_user(user_name)
303
428
 
304
429
  def __execute_grant_superuser(self, user_name):
305
- self.cursor.execute(self.__grant_superuser_user_sql(user_name))
430
+ self.cursor.execute(self.__grant_role_to_user_sql("rds_superuser", user_name))
306
431
  return self.__log_grant_superuser_user(user_name)
307
432
 
308
433
  def __execute_create_schema(self, schema_name):
434
+ self.cursor.execute(self.__grant_create())
309
435
  self.cursor.execute(self.__create_schema(schema_name))
310
436
  return self.__log_create_schema(schema_name)
311
437
 
@@ -317,8 +443,25 @@ class Executor13:
317
443
  def create_service(self, database_name, user_name, user_password):
318
444
  log_user = self.__execute_create_user(user_name, user_password)
319
445
  log_database = self.__execute_create_database(database_name)
320
- log_alter = self.__alter_database_owner(database_name, user_name)
321
- self.logs.extend(list(filter(None, [log_user, log_database, log_alter])))
446
+ log_alter_owner = self.__alter_database_owner(database_name, user_name)
447
+ self.logs.extend(list(filter(None, [log_user, log_database, log_alter_owner])))
448
+
449
+ def create_role(self, role):
450
+ log = self.__execute_create_role(role)
451
+ self.logs.extend(list(filter(None, [log])))
452
+
453
+ def create_read_only_role(self):
454
+ log = self.__execute_create_role(Executor13.ROLE_READ_ONLY)
455
+ self.logs.extend(list(filter(None, [log])))
456
+
457
+ # Grant read capabilities to objects of the database for read only role
458
+ # If 'existing' = True then will give the read access to already existing objects
459
+ def grant_read_only_capabilities(self, existing=False):
460
+ log_alter_read = self.__alter_grant_read_only(Executor13.ROLE_READ_ONLY)
461
+ log_grant_read = None
462
+ if existing:
463
+ log_grant_read = self.__grant_select_read_only(Executor13.ROLE_READ_ONLY)
464
+ self.logs.extend(list(filter(None, [log_alter_read, log_grant_read])))
322
465
 
323
466
  def create_unaccent_extension(self):
324
467
  log_unaccent_extension = self.__execute_create_unaccent_extension()
@@ -344,6 +487,10 @@ class Executor13:
344
487
  log_pg_trgm_extension = self.__execute_create_pg_trgm_extension()
345
488
  self.logs.extend(list(filter(None, [log_pg_trgm_extension])))
346
489
 
490
+ def create_hstore_extension(self):
491
+ log_hstore_extension = self.__execute_create_hstore_extension()
492
+ self.logs.extend(list(filter(None, [log_hstore_extension])))
493
+
347
494
  def create_user(self, user_name, user_password):
348
495
  log_user = self.__execute_create_user(user_name, user_password)
349
496
  self.logs.extend(list(filter(None, [log_user])))
@@ -351,7 +498,39 @@ class Executor13:
351
498
  def create_cdc_user(self, user_name, user_password):
352
499
  log_user = self.__execute_create_user(user_name, user_password)
353
500
  log_grant_replication = self.__execute_grant_replication(user_name)
354
- self.logs.extend(list(filter(None, [log_user, log_grant_replication])))
501
+ log_grant_read_only = self.__execute_grant_read_only(user_name)
502
+ self.logs.extend(list(filter(None, [log_user, log_grant_replication, log_grant_read_only])))
503
+
504
+ def create_etl_user(self, user_name, user_password):
505
+ log_user = self.__execute_create_user(user_name, user_password)
506
+ log_grant_replication = self.__execute_grant_replication(user_name)
507
+ log_grant_read_only = self.__execute_grant_read_only(user_name)
508
+ self.logs.extend(list(filter(None, [log_user, log_grant_replication, log_grant_read_only])))
509
+
510
+ def create_read_only_user(self, user_name, user_password):
511
+ log_user = self.__execute_create_user(user_name, user_password)
512
+ log_grant_read_only = self.__execute_grant_read_only(user_name)
513
+ self.logs.extend(list(filter(None, [log_user, log_grant_read_only])))
514
+
515
+ def create_restricted_user(self, user_name, user_password):
516
+ log_user = self.__execute_create_user(user_name, user_password)
517
+ self.logs.extend(list(filter(None, [log_user])))
518
+
519
+ # permissions = { <table_name:string>: <'r' | 'w' | 'rw'> }
520
+ # permissions = { 'product': 'r', 'order': 'w', 'message': 'rw' }
521
+ def setup_restricted_user(self, user_name, permissions):
522
+ logs = []
523
+ for table_name, rights in permissions.items():
524
+ if self.__execute_select_query(self.__check_table_sql(table_name)) is not None:
525
+ if 'r' in rights:
526
+ logs.append(self.__execute_grant_read_on_table(user_name, table_name))
527
+ if 'w' in rights:
528
+ logs.append(self.__execute_grant_write_on_table(user_name, table_name))
529
+ if any('w' in value for value in permissions.values()):
530
+ # Because we will not enumerate permissions for every sequence,
531
+ # if user has write access, allow usage on all sequences
532
+ logs.append(self.__execute_grant_usage_on_all_sequences(user_name))
533
+ self.logs.extend(list(filter(None, logs)))
355
534
 
356
535
  def create_schema(self, schema_name):
357
536
  log_create_schema = self.__execute_create_schema(schema_name)
@@ -367,9 +546,29 @@ class Executor13:
367
546
 
368
547
  def setup_cdc(self, cdc_user_name):
369
548
  log_grant_read_only = self.__execute_grant_schema_read_only(cdc_user_name, "public")
370
- log_publication = self.__execute_create_publication(Executor13.dbz_publication_name)
549
+ log_publication = self.__execute_create_publication(Executor13.dbz_publication_prefix)
371
550
  log_debezium_signal_table = self.__execute_create_debezium_signal_table(cdc_user_name)
372
- self.logs.extend(list(filter(None, [log_grant_read_only, log_publication, log_debezium_signal_table])))
551
+ log_debezium_heartbeat_table = self.__execute_create_debezium_heartbeat_table(cdc_user_name)
552
+ self.logs.extend(list(filter(None, [log_grant_read_only, log_publication, log_debezium_signal_table, log_debezium_heartbeat_table])))
553
+
554
+ def setup_stitch(self):
555
+ log_create_replication_slot = self.__execute_create_database_replication_slot('stitch', slot_type='wal2json')
556
+ self.logs.extend(list(filter(None, [log_create_replication_slot])))
557
+
558
+ def setup_airbyte(self):
559
+ log_create_publication = self.__execute_create_publication('airbyte')
560
+ log_create_replication_slot = self.__execute_create_database_replication_slot('airbyte', slot_type='pgoutput')
561
+ self.logs.extend(list(filter(None, [log_create_publication, log_create_replication_slot])))
562
+
563
+ def setup_fivetran(self):
564
+ log_create_publication = self.__execute_create_publication('fivetran')
565
+ log_create_replication_slot = self.__execute_create_database_replication_slot('fivetran', slot_type='pgoutput')
566
+ log_grant_replication = self.__execute_grant_replication('user-fivetran')
567
+ self.logs.extend(list(filter(None, [log_create_publication, log_create_replication_slot, log_grant_replication])))
568
+
569
+ def setup_etl(self, etl_user_name):
570
+ log_grant_read_only = self.__execute_grant_schema_read_only(etl_user_name, "public")
571
+ self.logs.extend(list(filter(None, [log_grant_read_only])))
373
572
 
374
573
  def setup_schema_write_user(self, user_name, schema_name):
375
574
  log_grant_access = self.__execute_grant_schema_all_access(user_name, schema_name)
@@ -382,3 +581,70 @@ class Executor13:
382
581
  def setup_etl_user(self, user_name):
383
582
  log_grant_create_table = self.__execute_grant_create_table(user_name)
384
583
  self.logs.extend(list(filter(None, [log_grant_create_table])))
584
+
585
+
586
+ def create_database_publication(self, prefix):
587
+ log_create_publication = self.__execute_create_publication(prefix)
588
+ self.logs.extend(list(filter(None, [log_create_publication])))
589
+
590
+ def create_database_replication_slot(self, prefix, slot_type='pgoutput'):
591
+ log_create_slot = self.__execute_create_database_replication_slot(prefix, slot_type)
592
+ self.logs.extend(list(filter(None, [log_create_slot])))
593
+
594
+ def __execute_create_database_replication_slot(self, prefix, slot_type):
595
+ slot_name = '{0}_{1}'.format(prefix, self.database_name)
596
+ if not self.__execute_select_query(self.__check_replication_slot_sql(slot_name)):
597
+ self.cursor.execute(self.__create_database_replication_slot(slot_name, slot_type))
598
+ return self.__log_create_database_replication_slot(slot_name, slot_type)
599
+
600
+ def __create_database_replication_slot(self, slot_name, slot_type):
601
+ return "SELECT * FROM pg_create_logical_replication_slot('{0}', '{1}');"\
602
+ .format(slot_name, slot_type)
603
+
604
+ def __log_create_database_replication_slot(self, slot_name, slot_type):
605
+ return "Creation du slot de replication logique {0} {1} \n".format(slot_type, slot_name)
606
+
607
+ def change_schema_owner(self, schema_name, user_name):
608
+ log_change_schema_owner = self._change_schema_owner(schema_name, user_name)
609
+ self.logs.extend(list(filter(None, [log_change_schema_owner])))
610
+
611
+ def _change_schema_owner(self, schema_name, user_name):
612
+ self.cursor.execute("ALTER SCHEMA \"{0}\" OWNER TO \"{1}\"".format(schema_name, user_name))
613
+ return "Set db {0} schema owner: {1} ".format(schema_name, user_name)
614
+
615
+ def setup_datadog(self, datadog_user_name):
616
+ log_grant_datadog = self._setup_datadog(datadog_user_name)
617
+ self.logs.extend(list(filter(None, [log_grant_datadog])))
618
+
619
+ def _setup_datadog(self, datadog_user_name):
620
+ self.cursor.execute(
621
+ """
622
+ CREATE SCHEMA IF NOT EXISTS datadog;
623
+ GRANT USAGE ON SCHEMA public TO \"{0}\";
624
+ GRANT USAGE ON SCHEMA datadog TO \"{0}\";
625
+ GRANT pg_monitor TO \"{0}\";
626
+ CREATE EXTENSION IF NOT EXISTS pg_stat_statements schema public;
627
+
628
+ CREATE OR REPLACE FUNCTION datadog.explain_statement(
629
+ l_query TEXT,
630
+ OUT explain JSON
631
+ )
632
+ RETURNS SETOF JSON AS
633
+ $$
634
+ DECLARE
635
+ curs REFCURSOR;
636
+ plan JSON;
637
+
638
+ BEGIN
639
+ OPEN curs FOR EXECUTE pg_catalog.concat('EXPLAIN (FORMAT JSON) ', l_query);
640
+ FETCH curs INTO plan;
641
+ CLOSE curs;
642
+ RETURN QUERY SELECT plan;
643
+ END;
644
+ $$
645
+ LANGUAGE 'plpgsql'
646
+ RETURNS NULL ON NULL INPUT
647
+ SECURITY DEFINER;
648
+ """.format(datadog_user_name)
649
+ )
650
+ return "Setup datadog Database monitoring"
@@ -9,14 +9,29 @@ class Executor14(Executor13):
9
9
  def __grant_read_only_roles(self, user_name):
10
10
  return "GRANT pg_read_all_data TO \"{0}\";".format(user_name)
11
11
 
12
+ def __grant_write_all_data_role(self, user_name):
13
+ return f"GRANT pg_write_all_data TO \"{user_name}\";"
14
+
12
15
  def __log_grant_read_only_roles(self, user_name):
13
16
  return "L'utilisateur {0} a herite des droits read only sur toutes les tables, vues et sequences \n"\
14
17
  .format(user_name)
15
18
 
19
+ def __log_grant_write_all_data_role(self, user_name):
20
+ return "L'utilisateur {0} a herite des droits d'écriture sur toutes les tables, vues et sequences \n"\
21
+ .format(user_name)
22
+
16
23
  def __execute_grant_read_only_roles(self, user_name):
17
24
  self.cursor.execute(self.__grant_read_only_roles(user_name))
18
25
  return self.__log_grant_read_only_roles(user_name)
19
26
 
27
+ def __execute_grant_write_all_data_role(self, user_name):
28
+ self.cursor.execute(self.__grant_write_all_data_role(user_name))
29
+ return self.__log_grant_write_all_data_role(user_name)
30
+
31
+ def grant_write_all_data_role(self, user_name):
32
+ log = self.__execute_grant_write_all_data_role(user_name)
33
+ self.logs.extend(list(filter(None, [log])))
34
+
20
35
  def setup_hawking(self, hawking_user_name):
21
36
  log_grant_read_only = self.__execute_grant_read_only_roles(hawking_user_name)
22
37
  self.logs.extend(list(filter(None, [log_grant_read_only])))
@@ -0,0 +1,7 @@
1
+ from .executor14 import Executor14
2
+
3
+
4
+ # Executor for postgres 16
5
+ class Executor16(Executor14):
6
+ def __init__(self, database_name, endpoint, user_name, db_password, decrypt_func):
7
+ super().__init__(database_name, endpoint, user_name, db_password, decrypt_func)
@@ -0,0 +1,16 @@
1
+ from .user import User
2
+
3
+ class RestrictedUser(User):
4
+ database = None
5
+ permissions = {}
6
+
7
+ def __init__(self, credentials, decrypt_func=None):
8
+ super().__init__(credentials, decrypt_func)
9
+ self.database = credentials['database']
10
+ self.permissions = credentials['permissions']
11
+
12
+ def get_database(self):
13
+ return self.database
14
+
15
+ def get_permissions(self):
16
+ return self.permissions
@@ -2,7 +2,7 @@ class User:
2
2
  name = None
3
3
  password = None
4
4
 
5
- def __init__(self, credentials, decrypt_func):
5
+ def __init__(self, credentials, decrypt_func=None):
6
6
  self.name = credentials['user_name']
7
7
  self.password = decrypt_func(credentials["user_password"]) if decrypt_func else credentials["user_password"]
8
8
 
@@ -1,24 +1,24 @@
1
1
  [tool.poetry]
2
2
  name = "lcdp-postgres-utils"
3
3
  # https://github.com/python-poetry/poetry/issues/1208
4
- version = "1.0.4.dev36"
4
+ version = "1.5.8"
5
5
  description = "Postgres Utils to create users, databases, functions, ..."
6
6
  authors = ["Le Comptoir Des Pharmacies <webmaster@lecomptoirdespharmacies.fr>"]
7
7
 
8
8
  [tool.poetry-dynamic-versioning]
9
9
  enable = false
10
10
  vcs = "git"
11
- style = "pep440"
12
- format-jinja = "{% if distance == 0 %}{{ base }}{% else %}{{ base }}.dev{{ distance }}{% endif %}"
13
- bump = true
11
+
12
+ [tool.poetry.requires-plugins]
13
+ poetry-dynamic-versioning = { version = ">=1.0.0,<2.0.0", extras = ["plugin"] }
14
14
 
15
15
  [tool.poetry.dependencies]
16
- python = "^3.8"
16
+ python = ">=3.8"
17
17
  boto3 = "^1.26.55"
18
18
  pg8000 = "^1.29.4"
19
19
 
20
20
  [tool.poetry.dev-dependencies]
21
21
 
22
22
  [build-system]
23
- requires = ["poetry>=1.1.0", "poetry-dynamic-versioning"]
24
- build-backend = "poetry.core.masonry.api"
23
+ requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"]
24
+ build-backend = "poetry_dynamic_versioning.backend"
@@ -1,32 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from setuptools import setup
3
-
4
- packages = \
5
- ['lcdp_postgres_utils',
6
- 'lcdp_postgres_utils.executors',
7
- 'lcdp_postgres_utils.utils']
8
-
9
- package_data = \
10
- {'': ['*']}
11
-
12
- install_requires = \
13
- ['boto3>=1.26.55,<2.0.0', 'pg8000>=1.29.4,<2.0.0']
14
-
15
- setup_kwargs = {
16
- 'name': 'lcdp-postgres-utils',
17
- 'version': '1.0.4.dev36',
18
- 'description': 'Postgres Utils to create users, databases, functions, ...',
19
- 'long_description': 'None',
20
- 'author': 'Le Comptoir Des Pharmacies',
21
- 'author_email': 'webmaster@lecomptoirdespharmacies.fr',
22
- 'maintainer': 'None',
23
- 'maintainer_email': 'None',
24
- 'url': 'None',
25
- 'packages': packages,
26
- 'package_data': package_data,
27
- 'install_requires': install_requires,
28
- 'python_requires': '>=3.8,<4.0',
29
- }
30
-
31
-
32
- setup(**setup_kwargs)