postgresql-charms-single-kernel 16.1.12__tar.gz → 16.2.2__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.12 → postgresql_charms_single_kernel-16.2.2}/PKG-INFO +3 -1
- {postgresql_charms_single_kernel-16.1.12 → postgresql_charms_single_kernel-16.2.2}/pyproject.toml +6 -3
- {postgresql_charms_single_kernel-16.1.12 → postgresql_charms_single_kernel-16.2.2}/single_kernel_postgresql/config/literals.py +2 -0
- postgresql_charms_single_kernel-16.2.2/single_kernel_postgresql/utils/__init__.py +197 -0
- {postgresql_charms_single_kernel-16.1.12 → postgresql_charms_single_kernel-16.2.2}/single_kernel_postgresql/utils/postgresql.py +2 -2
- postgresql_charms_single_kernel-16.1.12/single_kernel_postgresql/utils/__init__.py +0 -3
- {postgresql_charms_single_kernel-16.1.12 → postgresql_charms_single_kernel-16.2.2}/LICENSE +0 -0
- {postgresql_charms_single_kernel-16.1.12 → postgresql_charms_single_kernel-16.2.2}/README.md +0 -0
- {postgresql_charms_single_kernel-16.1.12 → postgresql_charms_single_kernel-16.2.2}/single_kernel_postgresql/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.1.12 → postgresql_charms_single_kernel-16.2.2}/single_kernel_postgresql/abstract_charm.py +0 -0
- {postgresql_charms_single_kernel-16.1.12 → postgresql_charms_single_kernel-16.2.2}/single_kernel_postgresql/charmcraft.yaml +0 -0
- {postgresql_charms_single_kernel-16.1.12 → postgresql_charms_single_kernel-16.2.2}/single_kernel_postgresql/compat/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.1.12 → postgresql_charms_single_kernel-16.2.2}/single_kernel_postgresql/compat/postgresql.py +0 -0
- {postgresql_charms_single_kernel-16.1.12 → postgresql_charms_single_kernel-16.2.2}/single_kernel_postgresql/compat/pyproject.toml +0 -0
- {postgresql_charms_single_kernel-16.1.12 → postgresql_charms_single_kernel-16.2.2}/single_kernel_postgresql/config/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.1.12 → postgresql_charms_single_kernel-16.2.2}/single_kernel_postgresql/config/pyproject.toml +0 -0
- {postgresql_charms_single_kernel-16.1.12 → postgresql_charms_single_kernel-16.2.2}/single_kernel_postgresql/events/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.1.12 → postgresql_charms_single_kernel-16.2.2}/single_kernel_postgresql/events/tls_transfer.py +0 -0
- {postgresql_charms_single_kernel-16.1.12 → postgresql_charms_single_kernel-16.2.2}/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.
|
|
3
|
+
Version: 16.2.2
|
|
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>
|
|
@@ -212,7 +212,9 @@ Classifier: Operating System :: POSIX :: Linux
|
|
|
212
212
|
Requires-Dist: ops>=2.0.0
|
|
213
213
|
Requires-Dist: psycopg2>=2.9.10
|
|
214
214
|
Requires-Dist: tenacity>=9.0.0
|
|
215
|
+
Requires-Dist: httpx ; python_full_version >= '3.12' and extra == 'postgresql'
|
|
215
216
|
Requires-Python: >=3.8, <4.0
|
|
217
|
+
Provides-Extra: postgresql
|
|
216
218
|
Description-Content-Type: text/markdown
|
|
217
219
|
|
|
218
220
|
# postgresql-single-kernel-library
|
{postgresql_charms_single_kernel-16.1.12 → postgresql_charms_single_kernel-16.2.2}/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.
|
|
7
|
+
version = "16.2.2"
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
license = {file = "LICENSE"}
|
|
10
10
|
authors = [
|
|
@@ -22,6 +22,9 @@ dependencies = [
|
|
|
22
22
|
"tenacity>=9.0.0",
|
|
23
23
|
]
|
|
24
24
|
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
postgresql = ["httpx; python_version >= '3.12'"]
|
|
27
|
+
|
|
25
28
|
[build-system]
|
|
26
29
|
requires = ["uv_build>=0.11.0,<0.12.0"]
|
|
27
30
|
build-backend = "uv_build"
|
|
@@ -32,11 +35,11 @@ module-root = ""
|
|
|
32
35
|
|
|
33
36
|
[dependency-groups]
|
|
34
37
|
format = [
|
|
35
|
-
"ruff==0.15.
|
|
38
|
+
"ruff==0.15.14"
|
|
36
39
|
]
|
|
37
40
|
lint = [
|
|
38
41
|
"codespell==2.4.2",
|
|
39
|
-
"ty==0.0.
|
|
42
|
+
"ty==0.0.39"
|
|
40
43
|
]
|
|
41
44
|
unit = [
|
|
42
45
|
"coverage[toml]==7.14.0",
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Copyright 2022 Canonical Ltd.
|
|
2
|
+
# See LICENSE file for licensing details.
|
|
3
|
+
|
|
4
|
+
"""A collection of utility functions that are used in the charm."""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import pwd
|
|
8
|
+
import re
|
|
9
|
+
import secrets
|
|
10
|
+
import string
|
|
11
|
+
from asyncio import as_completed, create_task, run, wait
|
|
12
|
+
from contextlib import suppress
|
|
13
|
+
from ssl import CERT_NONE, create_default_context
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from httpx import AsyncClient, BasicAuth, HTTPError
|
|
17
|
+
|
|
18
|
+
from ..config.literals import API_REQUEST_TIMEOUT, Substrates
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def new_password() -> str:
|
|
22
|
+
"""Generate a random password string.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
A random password string.
|
|
26
|
+
"""
|
|
27
|
+
choices = string.ascii_letters + string.digits
|
|
28
|
+
password = "".join([secrets.choice(choices) for _ in range(16)])
|
|
29
|
+
return password
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def split_mem(mem_str) -> tuple:
|
|
33
|
+
"""Split a memory string into a number and a unit.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
mem_str: a string representing a memory value, e.g. "1Gi"
|
|
37
|
+
"""
|
|
38
|
+
pattern = r"^(\d+)(\w+)$"
|
|
39
|
+
parts = re.match(pattern, mem_str)
|
|
40
|
+
if parts:
|
|
41
|
+
return parts.groups()
|
|
42
|
+
return None, "No unit found"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def any_memory_to_bytes(mem_str) -> int:
|
|
46
|
+
"""Convert a memory string to bytes.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
mem_str: a string representing a memory value, e.g. "1Gi"
|
|
50
|
+
"""
|
|
51
|
+
units = {
|
|
52
|
+
"KI": 1024,
|
|
53
|
+
"K": 10**3,
|
|
54
|
+
"MI": 1048576,
|
|
55
|
+
"M": 10**6,
|
|
56
|
+
"GI": 1073741824,
|
|
57
|
+
"G": 10**9,
|
|
58
|
+
"TI": 1099511627776,
|
|
59
|
+
"T": 10**12,
|
|
60
|
+
}
|
|
61
|
+
try:
|
|
62
|
+
num = int(mem_str)
|
|
63
|
+
return num
|
|
64
|
+
except ValueError as e:
|
|
65
|
+
memory, unit = split_mem(mem_str)
|
|
66
|
+
unit = unit.upper()
|
|
67
|
+
if unit not in units:
|
|
68
|
+
raise ValueError(f"Invalid memory definition in '{mem_str}'") from e
|
|
69
|
+
|
|
70
|
+
num = int(memory)
|
|
71
|
+
return int(num * units[unit])
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def any_cpu_to_cores(cpu_str) -> int:
|
|
75
|
+
"""Convert a CPU string to cores.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
cpu_str: a string representing a CPU value, as integer or millis
|
|
79
|
+
"""
|
|
80
|
+
if cpu_str.endswith("m"):
|
|
81
|
+
# convert millis to cores, undercommited
|
|
82
|
+
return int(cpu_str[:-1]) // 1000
|
|
83
|
+
return int(cpu_str)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def label2name(label: str) -> str:
|
|
87
|
+
"""Convert a unit label (with `-`) to a unit name (with `/`).
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
label: The label to convert.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
The converted name.
|
|
94
|
+
"""
|
|
95
|
+
return "/".join(label.rsplit("-", 1))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def render_file(
|
|
99
|
+
substrate: Substrates, path: str, content: str, mode: int, change_owner: bool = True
|
|
100
|
+
) -> None:
|
|
101
|
+
"""Write a content rendered from a template to a file.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
substrate: Charm substrate.
|
|
105
|
+
path: the path to the file.
|
|
106
|
+
content: the data to be written to the file.
|
|
107
|
+
mode: access permission mask applied to the
|
|
108
|
+
file using chmod (e.g. 0o640).
|
|
109
|
+
change_owner: whether to change the file owner
|
|
110
|
+
to the _daemon_ user.
|
|
111
|
+
"""
|
|
112
|
+
# TODO: keep this method to use it also for generating replication configuration files and
|
|
113
|
+
# move it to an utils / helpers file.
|
|
114
|
+
# Write the content to the file.
|
|
115
|
+
with open(path, "w+") as file:
|
|
116
|
+
file.write(content)
|
|
117
|
+
# Ensure correct permissions are set on the file.
|
|
118
|
+
os.chmod(path, mode)
|
|
119
|
+
if change_owner:
|
|
120
|
+
_change_owner(substrate, path)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def create_directory(substrate: Substrates, path: str, mode: int) -> None:
|
|
124
|
+
"""Creates a directory.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
substrate: Charm substrate.
|
|
128
|
+
path: the path of the directory that should be created.
|
|
129
|
+
mode: access permission mask applied to the
|
|
130
|
+
directory using chmod (e.g. 0o640).
|
|
131
|
+
"""
|
|
132
|
+
os.makedirs(path, mode=mode, exist_ok=True)
|
|
133
|
+
# Ensure correct permissions are set on the directory.
|
|
134
|
+
os.chmod(path, mode)
|
|
135
|
+
_change_owner(substrate, path)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _change_owner(substrate: Substrates, path: str) -> None:
|
|
139
|
+
"""Change the ownership of a file or a directory to the postgres user.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
substrate: Charm substrate.
|
|
143
|
+
path: path to a file or directory.
|
|
144
|
+
"""
|
|
145
|
+
try:
|
|
146
|
+
# Get the uid/gid for the _daemon_ user.
|
|
147
|
+
user_database = (
|
|
148
|
+
pwd.getpwnam("_daemon_") if substrate == Substrates.VM else pwd.getpwnam("postgres")
|
|
149
|
+
)
|
|
150
|
+
# Set the correct ownership for the file or directory.
|
|
151
|
+
os.chown(path, uid=user_database.pw_uid, gid=user_database.pw_gid)
|
|
152
|
+
except KeyError:
|
|
153
|
+
# Ignore non existing user error when it wasn't created yet.
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
async def _httpx_get_request(
|
|
158
|
+
url: str, cafile: str, auth: BasicAuth | None = None, verify: bool = True
|
|
159
|
+
) -> dict[str, Any] | None:
|
|
160
|
+
ssl_ctx = create_default_context()
|
|
161
|
+
if verify:
|
|
162
|
+
with suppress(FileNotFoundError):
|
|
163
|
+
ssl_ctx.load_verify_locations(cafile=cafile)
|
|
164
|
+
else:
|
|
165
|
+
ssl_ctx.check_hostname = False
|
|
166
|
+
ssl_ctx.verify_mode = CERT_NONE
|
|
167
|
+
async with AsyncClient(auth=auth, timeout=API_REQUEST_TIMEOUT, verify=ssl_ctx) as client:
|
|
168
|
+
try:
|
|
169
|
+
return (await client.get(url)).raise_for_status().json()
|
|
170
|
+
except (HTTPError, ValueError):
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
async def _async_get_request(
|
|
175
|
+
uri: str, endpoints: list[str], cafile: str, auth: BasicAuth | None, verify: bool = True
|
|
176
|
+
) -> dict[str, Any] | None:
|
|
177
|
+
tasks = [
|
|
178
|
+
create_task(_httpx_get_request(f"https://{ip}:8008{uri}", cafile, auth, verify))
|
|
179
|
+
for ip in endpoints
|
|
180
|
+
]
|
|
181
|
+
for task in as_completed(tasks):
|
|
182
|
+
if result := await task:
|
|
183
|
+
for task in tasks:
|
|
184
|
+
task.cancel()
|
|
185
|
+
await wait(tasks)
|
|
186
|
+
return result
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def parallel_patroni_get_request(
|
|
190
|
+
uri: str,
|
|
191
|
+
endpoints: list[str],
|
|
192
|
+
cafile: str,
|
|
193
|
+
auth: BasicAuth | None = None,
|
|
194
|
+
verify: bool = True,
|
|
195
|
+
) -> dict[str, Any] | None:
|
|
196
|
+
"""Call all possible patroni endpoints in parallel."""
|
|
197
|
+
return run(_async_get_request(uri, endpoints, cafile, auth, verify))
|
|
@@ -1381,14 +1381,14 @@ $$ LANGUAGE plpgsql security definer;""" # noqa: S608
|
|
|
1381
1381
|
parameters[parameter] = value
|
|
1382
1382
|
shared_buffers_max_value_in_mb = int(available_memory * 0.4 / 10**6)
|
|
1383
1383
|
shared_buffers_max_value = int(shared_buffers_max_value_in_mb * 10**3 / 8)
|
|
1384
|
-
if parameters.get("shared_buffers", 0) > shared_buffers_max_value:
|
|
1384
|
+
if int(parameters.get("shared_buffers", 0)) > shared_buffers_max_value:
|
|
1385
1385
|
raise Exception(
|
|
1386
1386
|
f"Shared buffers config option should be at most 40% of the available memory, which is {shared_buffers_max_value_in_mb}MB"
|
|
1387
1387
|
)
|
|
1388
1388
|
if profile == "production":
|
|
1389
1389
|
if "shared_buffers" in parameters:
|
|
1390
1390
|
# Convert to bytes to use in the calculation.
|
|
1391
|
-
shared_buffers = parameters["shared_buffers"] * 8 * 10**3
|
|
1391
|
+
shared_buffers = int(parameters["shared_buffers"]) * 8 * 10**3
|
|
1392
1392
|
else:
|
|
1393
1393
|
# Use 25% of the available memory for shared_buffers.
|
|
1394
1394
|
# and the remaining as cache memory.
|
|
File without changes
|
{postgresql_charms_single_kernel-16.1.12 → postgresql_charms_single_kernel-16.2.2}/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|