postgresql-charms-single-kernel 16.1.5__py3-none-any.whl → 16.1.7__py3-none-any.whl

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,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: postgresql-charms-single-kernel
3
- Version: 16.1.5
3
+ Version: 16.1.7
4
4
  Summary: Shared and reusable code for PostgreSQL-related charms
5
5
  Author: Canonical Data Platform
6
6
  Author-email: Canonical Data Platform <data-platform@lists.launchpad.net>
@@ -209,6 +209,9 @@ License:
209
209
  Classifier: Development Status :: 3 - Alpha
210
210
  Classifier: Intended Audience :: Developers
211
211
  Classifier: Operating System :: POSIX :: Linux
212
+ Requires-Dist: ops>=2.0.0
213
+ Requires-Dist: psycopg2>=2.9.10
214
+ Requires-Dist: tenacity>=9.0.0
212
215
  Requires-Python: >=3.8, <4.0
213
216
  Description-Content-Type: text/markdown
214
217
 
@@ -3,9 +3,11 @@ single_kernel_postgresql/abstract_charm.py,sha256=jWYV7nTZLiwxCZEedMZwyXzU3iNDRU
3
3
  single_kernel_postgresql/charmcraft.yaml,sha256=TbqNxQtjeAB-xPLpZXOnolxEnP75Rmgb7066k6HaOkU,454
4
4
  single_kernel_postgresql/config/__init__.py,sha256=k9Ud5ZZNd3l0nn8xi8AIlT45oZk8hJ542BjAFGDJzuc,140
5
5
  single_kernel_postgresql/config/literals.py,sha256=88r33TBX7SXqgpV6EEXtZmSJrfOtsFiVJ_omop0RuBo,643
6
+ single_kernel_postgresql/events/__init__.py,sha256=VwAEW3wYjs99q38bid47aS6Os4s3catK4H5HfdPkp94,121
7
+ single_kernel_postgresql/events/tls_transfer.py,sha256=Z1u2Y7d1rKyve4WGVTjlmNkCOy7P3MEgi8Fqv3mYtX0,3404
6
8
  single_kernel_postgresql/utils/__init__.py,sha256=VwAEW3wYjs99q38bid47aS6Os4s3catK4H5HfdPkp94,121
7
9
  single_kernel_postgresql/utils/filesystem.py,sha256=7sMR64DavtuIKlwf8p5UWG5l1x0B3NgCSV8Yg52mpb4,1832
8
- single_kernel_postgresql/utils/postgresql.py,sha256=JkthsMDB9kzF-UNQuR3ShxeNWKwxgcEaQKPLJpXizGE,83334
9
- postgresql_charms_single_kernel-16.1.5.dist-info/WHEEL,sha256=xDCZ-UyfvkGuEHPeI7BcJzYKIZzdqN8A8o1M5Om8IyA,79
10
- postgresql_charms_single_kernel-16.1.5.dist-info/METADATA,sha256=LXXeKnVtFD0n55PYaThWI7iuK4jqqD6dQhDUdGLBU5M,13662
11
- postgresql_charms_single_kernel-16.1.5.dist-info/RECORD,,
10
+ single_kernel_postgresql/utils/postgresql.py,sha256=M59s3KbyDYdQHT3u1g1L1o6lKN4ZXGsGIRw08DTvw-c,83409
11
+ postgresql_charms_single_kernel-16.1.7.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
12
+ postgresql_charms_single_kernel-16.1.7.dist-info/METADATA,sha256=zxSiilN6bgr45B1CPIpfOy-sJMlmNPZbErM-EDVuwYQ,13751
13
+ postgresql_charms_single_kernel-16.1.7.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.17
2
+ Generator: uv 0.9.28
3
3
  Root-Is-Purelib: true
4
- Tag: py3-none-any
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ # Copyright 2025 Canonical Ltd.
2
+ # See LICENSE file for licensing details.
3
+ """Utils and helpers for PostgreSQL charms."""
@@ -0,0 +1,89 @@
1
+ # Copyright 2022 Canonical Ltd.
2
+ # See LICENSE file for licensing details.
3
+
4
+ """TLS Transfer Handler."""
5
+
6
+ import logging
7
+ from collections.abc import Iterator
8
+
9
+ # Charmlib import. Should be made available in the charm itself
10
+ from charms.certificate_transfer_interface.v0.certificate_transfer import ( # type: ignore
11
+ CertificateAvailableEvent,
12
+ CertificateRemovedEvent,
13
+ CertificateTransferRequires,
14
+ )
15
+ from ops.framework import Object
16
+ from ops.pebble import ConnectionError as PebbleConnectionError
17
+ from ops.pebble import PathError, ProtocolError
18
+ from tenacity import RetryError
19
+
20
+ logger = logging.getLogger(__name__)
21
+ SCOPE = "unit"
22
+ TLS_TRANSFER_RELATION = "receive-ca-cert"
23
+
24
+
25
+ class TLSTransfer(Object):
26
+ """In this class we manage certificate transfer relation."""
27
+
28
+ def __init__(self, charm, peer_relation: str):
29
+ super().__init__(charm, "client-relations")
30
+ self.charm = charm
31
+ self.peer_relation = peer_relation
32
+ self.certs_transfer = CertificateTransferRequires(self.charm, TLS_TRANSFER_RELATION)
33
+ self.framework.observe(
34
+ self.certs_transfer.on.certificate_available, self._on_certificate_available
35
+ )
36
+ self.framework.observe(
37
+ self.certs_transfer.on.certificate_removed, self._on_certificate_removed
38
+ )
39
+
40
+ def _on_certificate_available(self, event: CertificateAvailableEvent) -> None:
41
+ """Enable TLS when TLS certificate is added."""
42
+ relation = self.charm.model.get_relation(TLS_TRANSFER_RELATION, event.relation_id)
43
+ if relation is None:
44
+ logger.error("Relationship not established anymore.")
45
+ return
46
+
47
+ secret_name = f"ca-{relation.app.name}"
48
+ self.charm.set_secret(SCOPE, secret_name, event.ca)
49
+
50
+ try:
51
+ if not self.charm.push_ca_file_into_workload(secret_name):
52
+ logger.debug("Cannot push TLS certificates at this moment")
53
+ event.defer()
54
+ return
55
+ except (PebbleConnectionError, PathError, ProtocolError, RetryError) as e:
56
+ logger.error("Cannot push TLS certificates: %r", e)
57
+ event.defer()
58
+ return
59
+
60
+ def _on_certificate_removed(self, event: CertificateRemovedEvent) -> None:
61
+ """Disable TLS when TLS certificate is removed."""
62
+ relation = self.charm.model.get_relation(TLS_TRANSFER_RELATION, event.relation_id)
63
+ if relation is None:
64
+ logger.error("Relationship not established anymore.")
65
+ return
66
+
67
+ secret_name = f"ca-{relation.app.name}"
68
+ self.charm.set_secret(SCOPE, secret_name, None)
69
+
70
+ try:
71
+ if not self.charm.clean_ca_file_from_workload(secret_name):
72
+ logger.debug("Cannot clean CA certificates at this moment")
73
+ event.defer()
74
+ return
75
+ except (PebbleConnectionError, PathError, ProtocolError, RetryError) as e:
76
+ logger.error("Cannot clean CA certificates: %r", e)
77
+ event.defer()
78
+ return
79
+
80
+ def get_ca_secret_names(self) -> Iterator[str]:
81
+ """Get a secret-name for each relation fulfilling the CA transfer interface.
82
+
83
+ Returns:
84
+ Secret name for a CA transfer fulfilled interface.
85
+ """
86
+ relations = self.charm.model.relations.get(TLS_TRANSFER_RELATION, [])
87
+
88
+ for relation in relations:
89
+ yield f"ca-{relation.app.name}"
@@ -27,6 +27,8 @@ from datetime import datetime, timezone
27
27
  from typing import Dict, List, Optional, Set, Tuple
28
28
 
29
29
  import psycopg2
30
+ import psycopg2.errors
31
+ import psycopg2.extensions
30
32
  from ops import ConfigData
31
33
  from psycopg2.sql import SQL, Identifier, Literal
32
34
 
@@ -430,11 +432,10 @@ class PostgreSQL:
430
432
  cursor.execute(connect_statement)
431
433
 
432
434
  # Add extra user roles to the new user.
433
- if roles:
434
- for role in roles:
435
- cursor.execute(
436
- SQL("GRANT {} TO {};").format(Identifier(role), Identifier(user))
437
- )
435
+ for role in roles:
436
+ cursor.execute(
437
+ SQL("GRANT {} TO {};").format(Identifier(role), Identifier(user))
438
+ )
438
439
  except psycopg2.Error as e:
439
440
  logger.error(f"Failed to create user: {e}")
440
441
  raise PostgreSQLCreateUserError() from e
@@ -498,9 +499,10 @@ class PostgreSQL:
498
499
 
499
500
  def _process_extra_user_roles(
500
501
  self, user: str, extra_user_roles: Optional[List[str]] = None
501
- ) -> Tuple[Optional[List[str]], Optional[Set[str]]]:
502
+ ) -> Tuple[List[str], Set[str]]:
502
503
  # Separate roles and privileges from the provided extra user roles.
503
- roles = privileges = None
504
+ roles = []
505
+ privileges = set()
504
506
  if extra_user_roles:
505
507
  if len(extra_user_roles) > 2 and sorted(extra_user_roles) != [
506
508
  ROLE_ADMIN,
@@ -644,6 +646,7 @@ class PostgreSQL:
644
646
  with self._connect_to_database(
645
647
  database
646
648
  ) as connection, connection.cursor() as cursor:
649
+ cursor.execute(SQL("RESET ROLE;"))
647
650
  cursor.execute(
648
651
  SQL("REASSIGN OWNED BY {} TO {};").format(
649
652
  Identifier(user), Identifier(self.user)
@@ -653,6 +656,7 @@ class PostgreSQL:
653
656
 
654
657
  # Delete the user.
655
658
  with self._connect_to_database() as connection, connection.cursor() as cursor:
659
+ cursor.execute(SQL("RESET ROLE;"))
656
660
  cursor.execute(SQL("DROP ROLE {};").format(Identifier(user)))
657
661
  except psycopg2.Error as e:
658
662
  logger.error(f"Failed to delete user: {e}")
@@ -823,9 +827,9 @@ class PostgreSQL:
823
827
  else f"DROP EXTENSION IF EXISTS {extension};"
824
828
  )
825
829
  self._configure_pgaudit(ordered_extensions.get("pgaudit", False))
826
- except psycopg2.errors.UniqueViolation:
830
+ except psycopg2.errors.UniqueViolation: # type: ignore
827
831
  pass
828
- except psycopg2.errors.DependentObjectsStillExist:
832
+ except psycopg2.errors.DependentObjectsStillExist: # type: ignore
829
833
  raise
830
834
  except psycopg2.Error as e:
831
835
  raise PostgreSQLEnableDisableExtensionError() from e
@@ -839,7 +843,7 @@ class PostgreSQL:
839
843
  with self._connect_to_database() as connection, connection.cursor() as cursor:
840
844
  # Should always be present
841
845
  cursor.execute("SELECT last_archived_wal FROM pg_stat_archiver;")
842
- return cursor.fetchone()[0] # type: ignore
846
+ return cursor.fetchone()[0]
843
847
  except psycopg2.Error as e:
844
848
  logger.error(f"Failed to get PostgreSQL last archived WAL: {e}")
845
849
  raise PostgreSQLGetLastArchivedWALError() from e
@@ -850,7 +854,7 @@ class PostgreSQL:
850
854
  with self._connect_to_database() as connection, connection.cursor() as cursor:
851
855
  cursor.execute("SELECT timeline_id FROM pg_control_checkpoint();")
852
856
  # There should always be a timeline
853
- return cursor.fetchone()[0] # type: ignore
857
+ return cursor.fetchone()[0]
854
858
  except psycopg2.Error as e:
855
859
  logger.error(f"Failed to get PostgreSQL current timeline id: {e}")
856
860
  raise PostgreSQLGetCurrentTimelineError() from e
@@ -907,7 +911,7 @@ class PostgreSQL:
907
911
  ) as connection, connection.cursor() as cursor:
908
912
  cursor.execute("SELECT version();")
909
913
  # Split to get only the version number. There should always be a version.
910
- return cursor.fetchone()[0].split(" ")[1] # type:ignore
914
+ return cursor.fetchone()[0].split(" ")[1]
911
915
  except psycopg2.Error as e:
912
916
  logger.error(f"Failed to get PostgreSQL version: {e}")
913
917
  raise PostgreSQLGetPostgreSQLVersionError() from e
@@ -928,7 +932,7 @@ class PostgreSQL:
928
932
  ) as connection, connection.cursor() as cursor:
929
933
  cursor.execute("SHOW ssl;")
930
934
  # SSL state should always be set
931
- return "on" in cursor.fetchone()[0] # type: ignore
935
+ return "on" in cursor.fetchone()[0]
932
936
  except psycopg2.Error:
933
937
  # Connection errors happen when PostgreSQL has not started yet.
934
938
  return False