postgresql-charms-single-kernel 16.1.5__tar.gz → 16.1.7__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.
- {postgresql_charms_single_kernel-16.1.5 → postgresql_charms_single_kernel-16.1.7}/PKG-INFO +4 -1
- {postgresql_charms_single_kernel-16.1.5 → postgresql_charms_single_kernel-16.1.7}/pyproject.toml +16 -19
- postgresql_charms_single_kernel-16.1.7/single_kernel_postgresql/events/tls_transfer.py +89 -0
- postgresql_charms_single_kernel-16.1.7/single_kernel_postgresql/utils/__init__.py +3 -0
- {postgresql_charms_single_kernel-16.1.5 → postgresql_charms_single_kernel-16.1.7}/single_kernel_postgresql/utils/postgresql.py +17 -13
- {postgresql_charms_single_kernel-16.1.5 → postgresql_charms_single_kernel-16.1.7}/LICENSE +0 -0
- {postgresql_charms_single_kernel-16.1.5 → postgresql_charms_single_kernel-16.1.7}/README.md +0 -0
- {postgresql_charms_single_kernel-16.1.5 → postgresql_charms_single_kernel-16.1.7}/single_kernel_postgresql/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.1.5 → postgresql_charms_single_kernel-16.1.7}/single_kernel_postgresql/abstract_charm.py +0 -0
- {postgresql_charms_single_kernel-16.1.5 → postgresql_charms_single_kernel-16.1.7}/single_kernel_postgresql/charmcraft.yaml +0 -0
- {postgresql_charms_single_kernel-16.1.5 → postgresql_charms_single_kernel-16.1.7}/single_kernel_postgresql/config/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.1.5 → postgresql_charms_single_kernel-16.1.7}/single_kernel_postgresql/config/literals.py +0 -0
- {postgresql_charms_single_kernel-16.1.5/single_kernel_postgresql/utils → postgresql_charms_single_kernel-16.1.7/single_kernel_postgresql/events}/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.1.5 → postgresql_charms_single_kernel-16.1.7}/single_kernel_postgresql/utils/filesystem.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: postgresql-charms-single-kernel
|
|
3
|
-
Version: 16.1.
|
|
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
|
|
{postgresql_charms_single_kernel-16.1.5 → postgresql_charms_single_kernel-16.1.7}/pyproject.toml
RENAMED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[project]
|
|
5
5
|
name = "postgresql-charms-single-kernel"
|
|
6
6
|
description = "Shared and reusable code for PostgreSQL-related charms"
|
|
7
|
-
version = "16.1.
|
|
7
|
+
version = "16.1.7"
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
license = {file = "LICENSE"}
|
|
10
10
|
authors = [
|
|
@@ -16,6 +16,11 @@ classifiers = [
|
|
|
16
16
|
"Operating System :: POSIX :: Linux",
|
|
17
17
|
]
|
|
18
18
|
requires-python = ">=3.8,<4.0"
|
|
19
|
+
dependencies = [
|
|
20
|
+
"ops>=2.0.0",
|
|
21
|
+
"psycopg2>=2.9.10",
|
|
22
|
+
"tenacity>=9.0.0",
|
|
23
|
+
]
|
|
19
24
|
|
|
20
25
|
[build-system]
|
|
21
26
|
requires = ["uv_build>=0.9.9,<0.10.0"]
|
|
@@ -26,20 +31,16 @@ module-name = "single_kernel_postgresql"
|
|
|
26
31
|
module-root = ""
|
|
27
32
|
|
|
28
33
|
[dependency-groups]
|
|
29
|
-
lib = [
|
|
30
|
-
"ops>=2.0.0",
|
|
31
|
-
"psycopg2>=2.9.10",
|
|
32
|
-
]
|
|
33
34
|
format = [
|
|
34
|
-
"ruff==0.14.
|
|
35
|
+
"ruff==0.14.14; python_version >= '3.12'"
|
|
35
36
|
]
|
|
36
37
|
lint = [
|
|
37
|
-
"codespell==2.4.1",
|
|
38
|
-
"
|
|
38
|
+
"codespell==2.4.1; python_version >= '3.12'",
|
|
39
|
+
"ty==0.0.14; python_version >= '3.12'"
|
|
39
40
|
]
|
|
40
41
|
unit = [
|
|
41
|
-
"coverage[toml]==7.13.
|
|
42
|
-
"pytest==9.0.2; python_version >= '3.
|
|
42
|
+
"coverage[toml]==7.13.2; python_version >= '3.12'",
|
|
43
|
+
"pytest==9.0.2; python_version >= '3.12'"
|
|
43
44
|
]
|
|
44
45
|
|
|
45
46
|
# Testing tools configuration
|
|
@@ -102,12 +103,8 @@ max-complexity = 10
|
|
|
102
103
|
[tool.ruff.lint.pydocstyle]
|
|
103
104
|
convention = "google"
|
|
104
105
|
|
|
105
|
-
[tool.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
reportIncompatibleMethodOverride = false
|
|
111
|
-
reportImportCycles = false
|
|
112
|
-
reportMissingModuleSource = true
|
|
113
|
-
stubPath = ""
|
|
106
|
+
[tool.ty.environment]
|
|
107
|
+
python = ".tox/lint/"
|
|
108
|
+
|
|
109
|
+
[tool.ty.src]
|
|
110
|
+
exclude = ["tests"]
|
|
@@ -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
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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[
|
|
502
|
+
) -> Tuple[List[str], Set[str]]:
|
|
502
503
|
# Separate roles and privileges from the provided extra user roles.
|
|
503
|
-
roles =
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|