24
24
from typing import Dict , List , Optional , Set , Tuple
25
25
26
26
import psycopg2
27
- from psycopg2 .sql import SQL , Identifier , Literal
28
-
29
27
from constants import BACKUP_USER , SYSTEM_USERS
28
+ from ops import ConfigData
29
+ from psycopg2 .sql import SQL , Identifier , Literal
30
30
31
31
# The unique Charmhub library identifier, never change it
32
32
LIBID = "24ee217a54e840a598ff21a079c3e678"
82
82
logger = logging .getLogger (__name__ )
83
83
84
84
85
- class PostgreSQLAssignGroupError (Exception ):
85
+ class PostgreSQLBaseError (Exception ):
86
+ """Base lib exception."""
87
+
88
+ message = None
89
+
90
+
91
+ class PostgreSQLAssignGroupError (PostgreSQLBaseError ):
86
92
"""Exception raised when assigning to a group fails."""
87
93
88
94
89
- class PostgreSQLCreateDatabaseError (Exception ):
95
+ class PostgreSQLCreateDatabaseError (PostgreSQLBaseError ):
90
96
"""Exception raised when creating a database fails."""
91
97
92
98
def __init__ (self , message : Optional [str ] = None ):
93
99
super ().__init__ (message )
94
100
self .message = message
95
101
96
102
97
- class PostgreSQLCreateGroupError (Exception ):
103
+ class PostgreSQLCreateGroupError (PostgreSQLBaseError ):
98
104
"""Exception raised when creating a group fails."""
99
105
100
106
101
- class PostgreSQLCreateUserError (Exception ):
107
+ class PostgreSQLCreateUserError (PostgreSQLBaseError ):
102
108
"""Exception raised when creating a user fails."""
103
109
104
110
def __init__ (self , message : Optional [str ] = None ):
105
111
super ().__init__ (message )
106
112
self .message = message
107
113
108
114
109
- class PostgreSQLDatabasesSetupError (Exception ):
115
+ class PostgreSQLUndefinedHostError (PostgreSQLBaseError ):
116
+ """Exception when host is not set."""
117
+
118
+
119
+ class PostgreSQLUndefinedPasswordError (PostgreSQLBaseError ):
120
+ """Exception when password is not set."""
121
+
122
+
123
+ class PostgreSQLDatabasesSetupError (PostgreSQLBaseError ):
110
124
"""Exception raised when the databases setup fails."""
111
125
112
126
113
- class PostgreSQLDeleteUserError (Exception ):
127
+ class PostgreSQLDeleteUserError (PostgreSQLBaseError ):
114
128
"""Exception raised when deleting a user fails."""
115
129
116
130
117
- class PostgreSQLEnableDisableExtensionError (Exception ):
131
+ class PostgreSQLEnableDisableExtensionError (PostgreSQLBaseError ):
118
132
"""Exception raised when enabling/disabling an extension fails."""
119
133
120
134
121
- class PostgreSQLGetLastArchivedWALError (Exception ):
135
+ class PostgreSQLGetLastArchivedWALError (PostgreSQLBaseError ):
122
136
"""Exception raised when retrieving last archived WAL fails."""
123
137
124
138
125
- class PostgreSQLGetCurrentTimelineError (Exception ):
139
+ class PostgreSQLGetCurrentTimelineError (PostgreSQLBaseError ):
126
140
"""Exception raised when retrieving current timeline id for the PostgreSQL unit fails."""
127
141
128
142
129
- class PostgreSQLGetPostgreSQLVersionError (Exception ):
143
+ class PostgreSQLGetPostgreSQLVersionError (PostgreSQLBaseError ):
130
144
"""Exception raised when retrieving PostgreSQL version fails."""
131
145
132
146
133
- class PostgreSQLListAccessibleDatabasesForUserError (Exception ):
147
+ class PostgreSQLListAccessibleDatabasesForUserError (PostgreSQLBaseError ):
134
148
"""Exception raised when retrieving the accessible databases for a user fails."""
135
149
136
150
137
- class PostgreSQLListGroupsError (Exception ):
151
+ class PostgreSQLListGroupsError (PostgreSQLBaseError ):
138
152
"""Exception raised when retrieving PostgreSQL groups list fails."""
139
153
140
154
141
- class PostgreSQLListUsersError (Exception ):
155
+ class PostgreSQLListUsersError (PostgreSQLBaseError ):
142
156
"""Exception raised when retrieving PostgreSQL users list fails."""
143
157
144
158
145
- class PostgreSQLUpdateUserPasswordError (Exception ):
159
+ class PostgreSQLUpdateUserPasswordError (PostgreSQLBaseError ):
146
160
"""Exception raised when updating a user password fails."""
147
161
148
162
149
- class PostgreSQLDatabaseExistsError (Exception ):
163
+ class PostgreSQLCreatePredefinedRolesError (PostgreSQLBaseError ):
164
+ """Exception raised when creating predefined roles."""
165
+
166
+
167
+ class PostgreSQLDatabaseExistsError (PostgreSQLBaseError ):
150
168
"""Exception raised during database existence check."""
151
169
152
170
153
- class PostgreSQLTableExistsError (Exception ):
171
+ class PostgreSQLTableExistsError (PostgreSQLBaseError ):
154
172
"""Exception raised during table existence check."""
155
173
156
174
157
- class PostgreSQLIsTableEmptyError (Exception ):
175
+ class PostgreSQLIsTableEmptyError (PostgreSQLBaseError ):
158
176
"""Exception raised during table emptiness check."""
159
177
160
178
161
- class PostgreSQLCreatePublicationError (Exception ):
179
+ class PostgreSQLCreatePublicationError (PostgreSQLBaseError ):
162
180
"""Exception raised when creating PostgreSQL publication."""
163
181
164
182
165
- class PostgreSQLPublicationExistsError (Exception ):
183
+ class PostgreSQLPublicationExistsError (PostgreSQLBaseError ):
166
184
"""Exception raised during PostgreSQL publication existence check."""
167
185
168
186
169
- class PostgreSQLAlterPublicationError (Exception ):
187
+ class PostgreSQLAlterPublicationError (PostgreSQLBaseError ):
170
188
"""Exception raised when altering PostgreSQL publication."""
171
189
172
190
173
- class PostgreSQLDropPublicationError (Exception ):
191
+ class PostgreSQLDropPublicationError (PostgreSQLBaseError ):
174
192
"""Exception raised when dropping PostgreSQL publication."""
175
193
176
194
177
- class PostgreSQLCreateSubscriptionError (Exception ):
195
+ class PostgreSQLCreateSubscriptionError (PostgreSQLBaseError ):
178
196
"""Exception raised when creating PostgreSQL subscription."""
179
197
180
198
181
- class PostgreSQLSubscriptionExistsError (Exception ):
199
+ class PostgreSQLSubscriptionExistsError (PostgreSQLBaseError ):
182
200
"""Exception raised during PostgreSQL subscription existence check."""
183
201
184
202
185
- class PostgreSQLUpdateSubscriptionError (Exception ):
203
+ class PostgreSQLUpdateSubscriptionError (PostgreSQLBaseError ):
186
204
"""Exception raised when updating PostgreSQL subscription."""
187
205
188
206
189
- class PostgreSQLRefreshSubscriptionError (Exception ):
207
+ class PostgreSQLRefreshSubscriptionError (PostgreSQLBaseError ):
190
208
"""Exception raised when refreshing PostgreSQL subscription."""
191
209
192
210
193
- class PostgreSQLDropSubscriptionError (Exception ):
211
+ class PostgreSQLDropSubscriptionError (PostgreSQLBaseError ):
194
212
"""Exception raised when dropping PostgreSQL subscription."""
195
213
196
214
197
- class PostgreSQLCreatePredefinedRolesError (Exception ):
198
- """Exception raised when creating predefined roles."""
199
-
200
-
201
- class PostgreSQLGrantDatabasePrivilegesToUserError (Exception ):
215
+ class PostgreSQLGrantDatabasePrivilegesToUserError (PostgreSQLBaseError ):
202
216
"""Exception raised when granting database privileges to user."""
203
217
204
218
@@ -207,10 +221,10 @@ class PostgreSQL:
207
221
208
222
def __init__ (
209
223
self ,
210
- primary_host : str ,
211
- current_host : str ,
224
+ primary_host : Optional [ str ] ,
225
+ current_host : Optional [ str ] ,
212
226
user : str ,
213
- password : str ,
227
+ password : Optional [ str ] ,
214
228
database : str ,
215
229
system_users : Optional [List [str ]] = None ,
216
230
):
@@ -255,6 +269,10 @@ def _connect_to_database(
255
269
psycopg2 connection object.
256
270
"""
257
271
host = database_host if database_host is not None else self .primary_host
272
+ if not host :
273
+ raise PostgreSQLUndefinedHostError ("Host not set" )
274
+ if not self .password :
275
+ raise PostgreSQLUndefinedPasswordError ("Password not set" )
258
276
connection = psycopg2 .connect (
259
277
f"dbname='{ database if database else self .database } ' user='{ self .user } ' host='{ host } '"
260
278
f"password='{ self .password } ' connect_timeout=1"
@@ -358,7 +376,11 @@ def create_user(
358
376
# Separate roles and privileges from the provided extra user roles.
359
377
roles = privileges = None
360
378
if extra_user_roles :
361
- if len (extra_user_roles ) > 2 and sorted (extra_user_roles ) != [ROLE_ADMIN , "createdb" , ACCESS_GROUP_RELATION ]:
379
+ if len (extra_user_roles ) > 2 and sorted (extra_user_roles ) != [
380
+ ROLE_ADMIN ,
381
+ "createdb" ,
382
+ ACCESS_GROUP_RELATION ,
383
+ ]:
362
384
extra_user_roles .remove (ACCESS_GROUP_RELATION )
363
385
logger .error (
364
386
"Invalid extra user roles: "
@@ -367,7 +389,17 @@ def create_user(
367
389
)
368
390
raise PostgreSQLCreateUserError (INVALID_EXTRA_USER_ROLE_BLOCKING_MESSAGE )
369
391
valid_privileges , valid_roles = self .list_valid_privileges_and_roles ()
370
- roles = [role for role in extra_user_roles if (user == BACKUP_USER or user in SYSTEM_USERS or role in valid_roles or role == ACCESS_GROUP_RELATION or role == "createdb" )]
392
+ roles = [
393
+ role
394
+ for role in extra_user_roles
395
+ if (
396
+ user == BACKUP_USER
397
+ or user in SYSTEM_USERS
398
+ or role in valid_roles
399
+ or role == ACCESS_GROUP_RELATION
400
+ or role == "createdb"
401
+ )
402
+ ]
371
403
if "createdb" in extra_user_roles :
372
404
extra_user_roles .remove ("createdb" )
373
405
roles .remove ("createdb" )
@@ -394,22 +426,43 @@ def create_user(
394
426
user_definition = "ALTER ROLE {} "
395
427
else :
396
428
user_definition = "CREATE ROLE {} "
397
- user_definition += (
398
- f"WITH LOGIN{ ' SUPERUSER' if admin else '' } { ' REPLICATION' if replication else '' } ENCRYPTED PASSWORD '{ password } '"
399
- )
429
+ user_definition += f"WITH LOGIN{ ' SUPERUSER' if admin else '' } { ' REPLICATION' if replication else '' } ENCRYPTED PASSWORD '{ password } '"
400
430
connect_statements = []
401
431
if database :
402
- if roles is not None and not any (True for role in roles if role in [ROLE_STATS , ROLE_READ , ROLE_DML , ROLE_BACKUP , ROLE_DBA ]):
403
- user_definition += f' IN ROLE "charmed_{ database } _admin", "charmed_{ database } _dml"'
432
+ if roles is not None and not any (
433
+ True
434
+ for role in roles
435
+ if role in [ROLE_STATS , ROLE_READ , ROLE_DML , ROLE_BACKUP , ROLE_DBA ]
436
+ ):
437
+ user_definition += (
438
+ f' IN ROLE "charmed_{ database } _admin", "charmed_{ database } _dml"'
439
+ )
404
440
else :
405
- connect_statements .append (SQL ("GRANT CONNECT ON DATABASE {} TO {};" ).format (
406
- Identifier (database ), Identifier (user )
407
- ))
408
- if roles is not None and any (True for role in roles if role in [ROLE_STATS , ROLE_READ , ROLE_DML , ROLE_BACKUP , ROLE_DBA , ROLE_ADMIN , ROLE_DATABASES_OWNER ]):
441
+ connect_statements .append (
442
+ SQL ("GRANT CONNECT ON DATABASE {} TO {};" ).format (
443
+ Identifier (database ), Identifier (user )
444
+ )
445
+ )
446
+ if roles is not None and any (
447
+ True
448
+ for role in roles
449
+ if role
450
+ in [
451
+ ROLE_STATS ,
452
+ ROLE_READ ,
453
+ ROLE_DML ,
454
+ ROLE_BACKUP ,
455
+ ROLE_DBA ,
456
+ ROLE_ADMIN ,
457
+ ROLE_DATABASES_OWNER ,
458
+ ]
459
+ ):
409
460
for system_database in ["postgres" , "template1" ]:
410
- connect_statements .append (SQL ("GRANT CONNECT ON DATABASE {} TO {};" ).format (
411
- Identifier (system_database ), Identifier (user )
412
- ))
461
+ connect_statements .append (
462
+ SQL ("GRANT CONNECT ON DATABASE {} TO {};" ).format (
463
+ Identifier (system_database ), Identifier (user )
464
+ )
465
+ )
413
466
if can_create_database :
414
467
user_definition += " CREATEDB"
415
468
if privileges :
@@ -470,7 +523,7 @@ def create_predefined_instance_roles(self) -> None:
470
523
],
471
524
ROLE_ADMIN : [
472
525
f"CREATE ROLE { ROLE_ADMIN } NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOLOGIN IN ROLE { ROLE_DML } " ,
473
- ]
526
+ ],
474
527
}
475
528
476
529
try :
@@ -961,7 +1014,6 @@ def list_existing_roles(self) -> Set[str]:
961
1014
cursor .execute ("SELECT rolname FROM pg_roles;" )
962
1015
return {role [0 ] for role in cursor .fetchall () if role [0 ]}
963
1016
964
-
965
1017
def list_valid_privileges_and_roles (self ) -> Tuple [Set [str ], Set [str ]]:
966
1018
"""Returns two sets with valid privileges and roles.
967
1019
@@ -1208,24 +1260,26 @@ def set_up_predefined_catalog_roles_function(self) -> None:
1208
1260
try :
1209
1261
for database in self ._get_existing_databases ():
1210
1262
with self ._connect_to_database (
1211
- database = database
1263
+ database = database
1212
1264
) as connection , connection .cursor () as cursor :
1213
1265
cursor .execute (SQL (function_creation_statement ))
1214
1266
cursor .execute (
1215
1267
SQL ("ALTER FUNCTION set_up_predefined_catalog_roles OWNER TO operator;" )
1216
1268
)
1217
1269
cursor .execute (
1218
- SQL ("REVOKE EXECUTE ON FUNCTION set_up_predefined_catalog_roles FROM PUBLIC;" )
1270
+ SQL (
1271
+ "REVOKE EXECUTE ON FUNCTION set_up_predefined_catalog_roles FROM PUBLIC;"
1272
+ )
1219
1273
)
1220
1274
cursor .execute (
1221
1275
SQL (
1222
1276
"GRANT EXECUTE ON FUNCTION set_up_predefined_catalog_roles TO {};"
1223
1277
).format (Identifier (ROLE_DATABASES_OWNER ))
1224
1278
)
1225
1279
cursor .execute (
1226
- SQL (
1227
- "REVOKE CREATE ON DATABASE {} FROM {};"
1228
- ). format ( Identifier ( "template1" ), Identifier ( ROLE_DATABASES_OWNER ))
1280
+ SQL ("REVOKE CREATE ON DATABASE {} FROM {};" ). format (
1281
+ Identifier ( "template1" ), Identifier ( ROLE_DATABASES_OWNER )
1282
+ )
1229
1283
)
1230
1284
except psycopg2 .Error as e :
1231
1285
logger .error (f"Failed to set up predefined catalog roles function: { e } " )
@@ -1571,7 +1625,7 @@ def build_postgresql_group_map(group_map: Optional[str]) -> List[Tuple]:
1571
1625
1572
1626
@staticmethod
1573
1627
def build_postgresql_parameters (
1574
- config_options : dict , available_memory : int , limit_memory : Optional [int ] = None
1628
+ config_options : ConfigData , available_memory : int , limit_memory : Optional [int ] = None
1575
1629
) -> Optional [dict ]:
1576
1630
"""Builds the PostgreSQL parameters.
1577
1631
0 commit comments