postgresql-charms-single-kernel 16.1.4__tar.gz → 16.1.5__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.4 → postgresql_charms_single_kernel-16.1.5}/PKG-INFO +1 -1
- {postgresql_charms_single_kernel-16.1.4 → postgresql_charms_single_kernel-16.1.5}/pyproject.toml +4 -4
- postgresql_charms_single_kernel-16.1.5/single_kernel_postgresql/utils/filesystem.py +62 -0
- {postgresql_charms_single_kernel-16.1.4 → postgresql_charms_single_kernel-16.1.5}/single_kernel_postgresql/utils/postgresql.py +69 -20
- postgresql_charms_single_kernel-16.1.4/single_kernel_postgresql/utils/filesystem.py +0 -20
- {postgresql_charms_single_kernel-16.1.4 → postgresql_charms_single_kernel-16.1.5}/LICENSE +0 -0
- {postgresql_charms_single_kernel-16.1.4 → postgresql_charms_single_kernel-16.1.5}/README.md +0 -0
- {postgresql_charms_single_kernel-16.1.4 → postgresql_charms_single_kernel-16.1.5}/single_kernel_postgresql/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.1.4 → postgresql_charms_single_kernel-16.1.5}/single_kernel_postgresql/abstract_charm.py +0 -0
- {postgresql_charms_single_kernel-16.1.4 → postgresql_charms_single_kernel-16.1.5}/single_kernel_postgresql/charmcraft.yaml +0 -0
- {postgresql_charms_single_kernel-16.1.4 → postgresql_charms_single_kernel-16.1.5}/single_kernel_postgresql/config/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.1.4 → postgresql_charms_single_kernel-16.1.5}/single_kernel_postgresql/config/literals.py +0 -0
- {postgresql_charms_single_kernel-16.1.4 → postgresql_charms_single_kernel-16.1.5}/single_kernel_postgresql/utils/__init__.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.5
|
|
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>
|
{postgresql_charms_single_kernel-16.1.4 → postgresql_charms_single_kernel-16.1.5}/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.5"
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
license = {file = "LICENSE"}
|
|
10
10
|
authors = [
|
|
@@ -31,15 +31,15 @@ lib = [
|
|
|
31
31
|
"psycopg2>=2.9.10",
|
|
32
32
|
]
|
|
33
33
|
format = [
|
|
34
|
-
"ruff==0.14.
|
|
34
|
+
"ruff==0.14.9"
|
|
35
35
|
]
|
|
36
36
|
lint = [
|
|
37
37
|
"codespell==2.4.1",
|
|
38
38
|
"pyright==1.1.407"
|
|
39
39
|
]
|
|
40
40
|
unit = [
|
|
41
|
-
"coverage[toml]==7.
|
|
42
|
-
"pytest==9.0.
|
|
41
|
+
"coverage[toml]==7.13.0; python_version >= '3.10'",
|
|
42
|
+
"pytest==9.0.2; python_version >= '3.10'"
|
|
43
43
|
]
|
|
44
44
|
|
|
45
45
|
# Testing tools configuration
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Copyright 2025 Canonical Ltd.
|
|
2
|
+
# See LICENSE file for licensing details.
|
|
3
|
+
"""Filesystem utilities."""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import pwd
|
|
7
|
+
|
|
8
|
+
from ..config.literals import SNAP_USER
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def change_owner(path: str) -> None:
|
|
12
|
+
"""Change the ownership of a file or a directory to the snap user.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
path: path to a file or directory.
|
|
16
|
+
"""
|
|
17
|
+
# Get the uid/gid for the snap user.
|
|
18
|
+
user_database = pwd.getpwnam(SNAP_USER)
|
|
19
|
+
# Set the correct ownership for the file or directory.
|
|
20
|
+
os.chown(path, uid=user_database.pw_uid, gid=user_database.pw_gid)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def is_tmpfs(path: str) -> bool:
|
|
24
|
+
"""Check if a path is on a tmpfs filesystem.
|
|
25
|
+
|
|
26
|
+
This function reads /proc/mounts to determine the filesystem type of the
|
|
27
|
+
mount point containing the given path.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
path: Path to check.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
True if the path is on a tmpfs filesystem, False otherwise.
|
|
34
|
+
Returns False if /proc/mounts cannot be read (e.g., not on Linux).
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
with open("/proc/mounts") as f:
|
|
38
|
+
mounts = f.readlines()
|
|
39
|
+
except (FileNotFoundError, PermissionError):
|
|
40
|
+
# Not on Linux or /proc not available, assume persistent storage
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
# Get absolute path to handle relative paths and symlinks
|
|
44
|
+
abs_path = os.path.abspath(path)
|
|
45
|
+
|
|
46
|
+
# Find the longest matching mount point (most specific)
|
|
47
|
+
best_match_fs = None
|
|
48
|
+
best_match_len = 0
|
|
49
|
+
|
|
50
|
+
for line in mounts:
|
|
51
|
+
parts = line.split()
|
|
52
|
+
if len(parts) < 3:
|
|
53
|
+
continue
|
|
54
|
+
mount_point = parts[1]
|
|
55
|
+
fs_type = parts[2]
|
|
56
|
+
|
|
57
|
+
# Check if path is under this mount point and is more specific
|
|
58
|
+
if abs_path.startswith(mount_point) and len(mount_point) > best_match_len:
|
|
59
|
+
best_match_fs = fs_type
|
|
60
|
+
best_match_len = len(mount_point)
|
|
61
|
+
|
|
62
|
+
return best_match_fs == "tmpfs"
|
|
@@ -37,7 +37,7 @@ from ..config.literals import (
|
|
|
37
37
|
SYSTEM_USERS,
|
|
38
38
|
Substrates,
|
|
39
39
|
)
|
|
40
|
-
from .filesystem import change_owner
|
|
40
|
+
from .filesystem import change_owner, is_tmpfs
|
|
41
41
|
|
|
42
42
|
# Groups to distinguish HBA access
|
|
43
43
|
ACCESS_GROUP_IDENTITY = "identity_access"
|
|
@@ -1107,8 +1107,64 @@ class PostgreSQL:
|
|
|
1107
1107
|
connection.close()
|
|
1108
1108
|
return databases
|
|
1109
1109
|
|
|
1110
|
+
def _handle_temp_tablespace_on_reboot(
|
|
1111
|
+
self, cursor, temp_location: str, temp_tablespace_exists: bool
|
|
1112
|
+
) -> None:
|
|
1113
|
+
"""Handle temp tablespace when permissions need fixing after reboot.
|
|
1114
|
+
|
|
1115
|
+
Args:
|
|
1116
|
+
cursor: Database cursor.
|
|
1117
|
+
temp_location: Path to the temp tablespace location.
|
|
1118
|
+
temp_tablespace_exists: Whether the temp tablespace already exists.
|
|
1119
|
+
"""
|
|
1120
|
+
if not temp_tablespace_exists:
|
|
1121
|
+
return
|
|
1122
|
+
|
|
1123
|
+
# Different handling based on storage type
|
|
1124
|
+
if is_tmpfs(temp_location):
|
|
1125
|
+
# tmpfs: Directory is empty after reboot, safe to rename and recreate
|
|
1126
|
+
# Rename existing temp tablespace instead of dropping it.
|
|
1127
|
+
# Timestamp collision is not possible: the charm ensures this code runs leader-only,
|
|
1128
|
+
# and it executes within a single database transaction holding exclusive locks.
|
|
1129
|
+
new_name = f"temp_{datetime.now(timezone.utc).strftime('%Y%m%d%H%M%S')}"
|
|
1130
|
+
cursor.execute(f"ALTER TABLESPACE temp RENAME TO {new_name};")
|
|
1131
|
+
|
|
1132
|
+
# List temp tablespaces with suffix for operator follow-up cleanup
|
|
1133
|
+
cursor.execute("SELECT spcname FROM pg_tablespace WHERE spcname LIKE 'temp_%';")
|
|
1134
|
+
temp_tbls = sorted([row[0] for row in cursor.fetchall()])
|
|
1135
|
+
logger.info(
|
|
1136
|
+
"There are %d temp tablespaces that should be checked and removed: %s",
|
|
1137
|
+
len(temp_tbls),
|
|
1138
|
+
", ".join(temp_tbls),
|
|
1139
|
+
)
|
|
1140
|
+
else:
|
|
1141
|
+
# Persistent storage: Tablespace is still valid, permissions already fixed
|
|
1142
|
+
# Log that we fixed permissions but didn't recreate
|
|
1143
|
+
logger.info(
|
|
1144
|
+
"Fixed permissions on temp tablespace directory at %s (persistent storage), "
|
|
1145
|
+
"existing tablespace remains valid",
|
|
1146
|
+
temp_location,
|
|
1147
|
+
)
|
|
1148
|
+
|
|
1110
1149
|
def set_up_database(self, temp_location: Optional[str] = None) -> None:
|
|
1111
|
-
"""Set up postgres database with the right permissions.
|
|
1150
|
+
"""Set up postgres database with the right permissions.
|
|
1151
|
+
|
|
1152
|
+
This method configures the postgres database with appropriate permissions and
|
|
1153
|
+
optionally creates a temporary tablespace.
|
|
1154
|
+
|
|
1155
|
+
Args:
|
|
1156
|
+
temp_location: Optional path for the temp tablespace. If provided, the method
|
|
1157
|
+
will ensure proper permissions and create the tablespace if it doesn't exist.
|
|
1158
|
+
|
|
1159
|
+
Behavior on reboot:
|
|
1160
|
+
- For tmpfs storage: If permissions are incorrect after reboot, renames the old
|
|
1161
|
+
tablespace and creates a new one (tmpfs directory is empty after reboot).
|
|
1162
|
+
- For persistent storage: If permissions are incorrect after reboot, fixes
|
|
1163
|
+
permissions but keeps the existing tablespace (directory contents persist).
|
|
1164
|
+
|
|
1165
|
+
Raises:
|
|
1166
|
+
PostgreSQLDatabasesSetupError: If database setup fails.
|
|
1167
|
+
"""
|
|
1112
1168
|
connection = None
|
|
1113
1169
|
cursor = None
|
|
1114
1170
|
try:
|
|
@@ -1116,30 +1172,23 @@ class PostgreSQL:
|
|
|
1116
1172
|
cursor = connection.cursor()
|
|
1117
1173
|
|
|
1118
1174
|
if temp_location is not None:
|
|
1119
|
-
# Fix permissions on the temporary tablespace location when a reboot happens
|
|
1175
|
+
# Fix permissions on the temporary tablespace location when a reboot happens.
|
|
1120
1176
|
temp_location_stats = os.stat(temp_location)
|
|
1121
|
-
|
|
1177
|
+
permissions_need_fix = self.substrate == Substrates.VM and (
|
|
1122
1178
|
pwd.getpwuid(temp_location_stats.st_uid).pw_name != SNAP_USER
|
|
1123
1179
|
or int(temp_location_stats.st_mode & 0o777) != POSTGRESQL_STORAGE_PERMISSIONS
|
|
1124
|
-
)
|
|
1180
|
+
)
|
|
1181
|
+
|
|
1182
|
+
if permissions_need_fix:
|
|
1125
1183
|
change_owner(temp_location)
|
|
1126
1184
|
os.chmod(temp_location, POSTGRESQL_STORAGE_PERMISSIONS)
|
|
1127
|
-
# Rename existing temp tablespace if it exists, instead of dropping it.
|
|
1128
|
-
cursor.execute("SELECT TRUE FROM pg_tablespace WHERE spcname='temp';")
|
|
1129
|
-
if cursor.fetchone() is not None:
|
|
1130
|
-
new_name = f"temp_{datetime.now(timezone.utc).strftime('%Y%m%d%H%M%S')}"
|
|
1131
|
-
cursor.execute(f"ALTER TABLESPACE temp RENAME TO {new_name};")
|
|
1132
1185
|
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
"There are %d temp tablespaces that should be checked and removed: %s",
|
|
1140
|
-
len(temp_tbls),
|
|
1141
|
-
", ".join(temp_tbls),
|
|
1142
|
-
)
|
|
1186
|
+
# Check if temp tablespace exists and handle it appropriately
|
|
1187
|
+
cursor.execute("SELECT TRUE FROM pg_tablespace WHERE spcname='temp';")
|
|
1188
|
+
temp_tablespace_exists = cursor.fetchone() is not None
|
|
1189
|
+
self._handle_temp_tablespace_on_reboot(
|
|
1190
|
+
cursor, temp_location, temp_tablespace_exists
|
|
1191
|
+
)
|
|
1143
1192
|
|
|
1144
1193
|
# Ensure a fresh temp tablespace exists at the expected location.
|
|
1145
1194
|
cursor.execute("SELECT TRUE FROM pg_tablespace WHERE spcname='temp';")
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# Copyright 2025 Canonical Ltd.
|
|
2
|
-
# See LICENSE file for licensing details.
|
|
3
|
-
"""Filesystem utilities."""
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
import pwd
|
|
7
|
-
|
|
8
|
-
from ..config.literals import SNAP_USER
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def change_owner(path: str) -> None:
|
|
12
|
-
"""Change the ownership of a file or a directory to the snap user.
|
|
13
|
-
|
|
14
|
-
Args:
|
|
15
|
-
path: path to a file or directory.
|
|
16
|
-
"""
|
|
17
|
-
# Get the uid/gid for the snap user.
|
|
18
|
-
user_database = pwd.getpwnam(SNAP_USER)
|
|
19
|
-
# Set the correct ownership for the file or directory.
|
|
20
|
-
os.chown(path, uid=user_database.pw_uid, gid=user_database.pw_gid)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|