Skip to content

Commit 0a1c149

Browse files
authored
[DPE-5324] Increase linting rules (#740)
* Increase linting rules * Tweak quotes * Use literals * Literal database * Fix linter tweaks * Bump timeout * Raise on false * Fix generation * Restore statement * Bump patch version * Renamed rules * Bump libpatch
1 parent 7da9c8c commit 0a1c149

38 files changed

+410
-385
lines changed

lib/charms/postgresql_k8s/v0/postgresql.py

Lines changed: 99 additions & 96 deletions
Large diffs are not rendered by default.

lib/charms/postgresql_k8s/v0/postgresql_tls.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545

4646
# Increment this PATCH version before using `charmcraft publish-lib` or reset
4747
# to 0 if you are raising the major API version.
48-
LIBPATCH = 10
48+
LIBPATCH = 11
4949

5050
logger = logging.getLogger(__name__)
5151
SCOPE = "unit"
@@ -82,10 +82,7 @@ def _on_set_tls_private_key(self, event: ActionEvent) -> None:
8282

8383
def _request_certificate(self, param: Optional[str]):
8484
"""Request a certificate to TLS Certificates Operator."""
85-
if param is None:
86-
key = generate_private_key()
87-
else:
88-
key = self._parse_tls_file(param)
85+
key = generate_private_key() if param is None else self._parse_tls_file(param)
8986

9087
csr = generate_csr(
9188
private_key=key,

pyproject.toml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ line-length = 99
111111

112112
[tool.ruff.lint]
113113
explicit-preview-rules = true
114-
select = ["A", "E", "W", "F", "C", "N", "D", "I001", "CPY001"]
114+
select = ["A", "E", "W", "F", "C", "N", "D", "I001", "B", "CPY", "RUF", "S", "SIM", "UP", "TC"]
115115
extend-ignore = [
116116
"D203",
117117
"D204",
@@ -130,12 +130,19 @@ extend-ignore = [
130130
ignore = ["E501", "D107"]
131131

132132
[tool.ruff.lint.per-file-ignores]
133-
"tests/*" = ["D100", "D101", "D102", "D103", "D104"]
133+
"tests/*" = [
134+
"D100", "D101", "D102", "D103", "D104",
135+
# Asserts
136+
"B011",
137+
# Disable security checks for tests
138+
"S",
139+
]
134140

135141
[tool.ruff.lint.flake8-copyright]
136142
# Check for properly formatted copyright header in each file
137143
author = "Canonical Ltd."
138144
notice-rgx = "Copyright\\s\\d{4}([-,]\\d{4})*\\s+"
145+
min-file-size = 1
139146

140147
[tool.ruff.lint.mccabe]
141148
max-complexity = 10

src/arch_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def is_wrong_architecture() -> bool:
3333
)
3434
return False
3535

36-
with open(manifest_file_path, "r") as file:
36+
with open(manifest_file_path) as file:
3737
manifest = file.read()
3838
hw_arch = os.uname().machine
3939
if ("amd64" in manifest and hw_arch == "x86_64") or (

src/backups.py

Lines changed: 31 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -109,15 +109,16 @@ def _can_initialise_stanza(self) -> bool:
109109
# Don't allow stanza initialisation if this unit hasn't started the database
110110
# yet and either hasn't joined the peer relation yet or hasn't configured TLS
111111
# yet while other unit already has TLS enabled.
112-
if not self.charm._patroni.member_started and (
113-
(len(self.charm._peers.data.keys()) == 2)
114-
or (
115-
"tls" not in self.charm.unit_peer_data
116-
and any("tls" in unit_data for _, unit_data in self.charm._peers.data.items())
112+
return not (
113+
not self.charm._patroni.member_started
114+
and (
115+
(len(self.charm._peers.data.keys()) == 2)
116+
or (
117+
"tls" not in self.charm.unit_peer_data
118+
and any("tls" in unit_data for _, unit_data in self.charm._peers.data.items())
119+
)
117120
)
118-
):
119-
return False
120-
return True
121+
)
121122

122123
def _can_unit_perform_backup(self) -> Tuple[bool, Optional[str]]:
123124
"""Validates whether this unit can perform a backup."""
@@ -182,18 +183,17 @@ def can_use_s3_repository(self) -> Tuple[bool, Optional[str]]:
182183
])
183184
if error != "":
184185
raise Exception(error)
185-
system_identifier_from_instance = [
186+
system_identifier_from_instance = next(
186187
line
187188
for line in system_identifier_from_instance.splitlines()
188189
if "Database system identifier" in line
189-
][0].split(" ")[-1]
190+
).split(" ")[-1]
190191
system_identifier_from_stanza = str(stanza.get("db")[0]["system-id"])
191192
if system_identifier_from_instance != system_identifier_from_stanza:
192193
logger.debug(
193194
f"can_use_s3_repository: incompatible system identifier s3={system_identifier_from_stanza}, local={system_identifier_from_instance}"
194195
)
195196
return False, ANOTHER_CLUSTER_REPOSITORY_ERROR_MESSAGE
196-
197197
return True, None
198198

199199
def _construct_endpoint(self, s3_parameters: Dict) -> str:
@@ -278,7 +278,7 @@ def _change_connectivity_to_database(self, connectivity: bool) -> None:
278278
self.charm.update_config(is_creating_backup=True)
279279

280280
def _execute_command(
281-
self, command: List[str], timeout: float = None, stream: bool = False
281+
self, command: List[str], timeout: Optional[float] = None, stream: bool = False
282282
) -> Tuple[Optional[str], Optional[str]]:
283283
"""Execute a command in the workload container."""
284284
try:
@@ -338,17 +338,7 @@ def _format_backup_list(self, backup_list) -> str:
338338
path,
339339
) in backup_list:
340340
backups.append(
341-
"{:<20s} | {:<19s} | {:<8s} | {:<20s} | {:<23s} | {:<20s} | {:<20s} | {:<8s} | {:s}".format(
342-
backup_id,
343-
backup_action,
344-
backup_status,
345-
reference,
346-
lsn_start_stop,
347-
start,
348-
stop,
349-
backup_timeline,
350-
path,
351-
)
341+
f"{backup_id:<20s} | {backup_action:<19s} | {backup_status:<8s} | {reference:<20s} | {lsn_start_stop:<23s} | {start:<20s} | {stop:<20s} | {backup_timeline:<8s} | {path:s}"
352342
)
353343
return "\n".join(backups)
354344

@@ -395,7 +385,7 @@ def _generate_backup_list_output(self) -> str:
395385
backup_path,
396386
))
397387

398-
for timeline, (timeline_stanza, timeline_id) in self._list_timelines().items():
388+
for timeline, (_, timeline_id) in self._list_timelines().items():
399389
backup_list.append((
400390
timeline,
401391
"restore",
@@ -648,7 +638,7 @@ def _is_primary_pgbackrest_service_running(self) -> bool:
648638
try:
649639
primary = self.charm._patroni.get_primary()
650640
except (RetryError, ConnectionError) as e:
651-
logger.error(f"failed to get primary with error {str(e)}")
641+
logger.error(f"failed to get primary with error {e!s}")
652642
return False
653643

654644
if primary is None:
@@ -666,7 +656,7 @@ def _is_primary_pgbackrest_service_running(self) -> bool:
666656
])
667657
except ExecError as e:
668658
logger.warning(
669-
f"Failed to contact pgBackRest TLS server on {primary_endpoint} with error {str(e)}"
659+
f"Failed to contact pgBackRest TLS server on {primary_endpoint} with error {e!s}"
670660
)
671661
return False
672662

@@ -798,7 +788,7 @@ def _on_create_backup_action(self, event) -> None: # noqa: C901
798788
Model Name: {self.model.name}
799789
Application Name: {self.model.app.name}
800790
Unit Name: {self.charm.unit.name}
801-
Juju Version: {str(juju_version)}
791+
Juju Version: {juju_version!s}
802792
"""
803793
if not self._upload_content_to_s3(
804794
metadata,
@@ -862,7 +852,7 @@ def _on_create_backup_action(self, event) -> None: # noqa: C901
862852
f"backup/{self.stanza_name}/{backup_id}/backup.log",
863853
s3_parameters,
864854
)
865-
error_message = f"Failed to backup PostgreSQL with error: {str(e)}"
855+
error_message = f"Failed to backup PostgreSQL with error: {e!s}"
866856
logger.error(f"Backup failed: {error_message}")
867857
event.fail(error_message)
868858
else:
@@ -924,7 +914,7 @@ def _on_list_backups_action(self, event) -> None:
924914
event.set_results({"backups": formatted_list})
925915
except ExecError as e:
926916
logger.exception(e)
927-
event.fail(f"Failed to list PostgreSQL backups with error: {str(e)}")
917+
event.fail(f"Failed to list PostgreSQL backups with error: {e!s}")
928918

929919
def _on_restore_action(self, event): # noqa: C901
930920
"""Request that pgBackRest restores a backup."""
@@ -943,10 +933,8 @@ def _on_restore_action(self, event): # noqa: C901
943933
logger.info("Validating provided backup-id and restore-to-time")
944934
backups = self._list_backups(show_failed=False)
945935
timelines = self._list_timelines()
946-
is_backup_id_real = backup_id and backup_id in backups.keys()
947-
is_backup_id_timeline = (
948-
backup_id and not is_backup_id_real and backup_id in timelines.keys()
949-
)
936+
is_backup_id_real = backup_id and backup_id in backups
937+
is_backup_id_timeline = backup_id and not is_backup_id_real and backup_id in timelines
950938
if backup_id and not is_backup_id_real and not is_backup_id_timeline:
951939
error_message = f"Invalid backup-id: {backup_id}"
952940
logger.error(f"Restore failed: {error_message}")
@@ -1001,7 +989,7 @@ def _on_restore_action(self, event): # noqa: C901
1001989
try:
1002990
self.container.stop(self.charm._postgresql_service)
1003991
except ChangeError as e:
1004-
error_message = f"Failed to stop database service with error: {str(e)}"
992+
error_message = f"Failed to stop database service with error: {e!s}"
1005993
logger.error(f"Restore failed: {error_message}")
1006994
event.fail(error_message)
1007995
return
@@ -1025,9 +1013,7 @@ def _on_restore_action(self, event): # noqa: C901
10251013
except ApiError as e:
10261014
# If previous PITR restore was unsuccessful, there are no such endpoints.
10271015
if "restore-to-time" not in self.charm.app_peer_data:
1028-
error_message = (
1029-
f"Failed to remove previous cluster information with error: {str(e)}"
1030-
)
1016+
error_message = f"Failed to remove previous cluster information with error: {e!s}"
10311017
logger.error(f"Restore failed: {error_message}")
10321018
event.fail(error_message)
10331019
self._restart_database()
@@ -1037,7 +1023,7 @@ def _on_restore_action(self, event): # noqa: C901
10371023
try:
10381024
self._empty_data_files()
10391025
except ExecError as e:
1040-
error_message = f"Failed to remove contents of the data directory with error: {str(e)}"
1026+
error_message = f"Failed to remove contents of the data directory with error: {e!s}"
10411027
logger.error(f"Restore failed: {error_message}")
10421028
event.fail(error_message)
10431029
self._restart_database()
@@ -1188,7 +1174,7 @@ def _render_pgbackrest_conf_file(self) -> bool:
11881174
)
11891175

11901176
# Open the template pgbackrest.conf file.
1191-
with open("templates/pgbackrest.conf.j2", "r") as file:
1177+
with open("templates/pgbackrest.conf.j2") as file:
11921178
template = Template(file.read())
11931179
# Render the template file with the correct values.
11941180
rendered = template.render(
@@ -1217,13 +1203,14 @@ def _render_pgbackrest_conf_file(self) -> bool:
12171203
)
12181204

12191205
# Render the logrotate configuration file.
1220-
with open("templates/pgbackrest.logrotate.j2", "r") as file:
1206+
with open("templates/pgbackrest.logrotate.j2") as file:
12211207
template = Template(file.read())
12221208
self.container.push(PGBACKREST_LOGROTATE_FILE, template.render())
1223-
self.container.push(
1224-
"/home/postgres/rotate_logs.py",
1225-
open("src/rotate_logs.py", "r").read(),
1226-
)
1209+
with open("src/rotate_logs.py") as f:
1210+
self.container.push(
1211+
"/home/postgres/rotate_logs.py",
1212+
f.read(),
1213+
)
12271214
self.container.start(self.charm.rotate_logs_service)
12281215

12291216
return True

src/charm.py

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@
141141
PASSWORD_USERS = [*SYSTEM_USERS, "patroni"]
142142

143143

144+
class CannotConnectError(Exception):
145+
"""Cannot run smoke check on connected Database."""
146+
147+
144148
@trace_charm(
145149
tracing_endpoint="tracing_endpoint",
146150
extra_types=(
@@ -261,7 +265,7 @@ def _pebble_log_forwarding_supported(self) -> bool:
261265
from ops.jujuversion import JujuVersion
262266

263267
juju_version = JujuVersion.from_environ()
264-
return juju_version > JujuVersion(version=str("3.3"))
268+
return juju_version > JujuVersion(version="3.3")
265269

266270
def _generate_metrics_jobs(self, enable_tls: bool) -> Dict:
267271
"""Generate spec for Prometheus scraping."""
@@ -679,7 +683,7 @@ def _on_config_changed(self, event) -> None:
679683
)
680684
return
681685

682-
def enable_disable_extensions(self, database: str = None) -> None:
686+
def enable_disable_extensions(self, database: Optional[str] = None) -> None:
683687
"""Enable/disable PostgreSQL extensions set through config options.
684688
685689
Args:
@@ -1223,11 +1227,11 @@ def _on_set_password(self, event: ActionEvent) -> None:
12231227
other_cluster_primary = self._patroni.get_primary(
12241228
alternative_endpoints=other_cluster_endpoints
12251229
)
1226-
other_cluster_primary_ip = [
1230+
other_cluster_primary_ip = next(
12271231
replication_offer_relation.data[unit].get("private-address")
12281232
for unit in replication_offer_relation.units
12291233
if unit.name.replace("/", "-") == other_cluster_primary
1230-
][0]
1234+
)
12311235
try:
12321236
self.postgresql.update_user_password(
12331237
username, password, database_host=other_cluster_primary_ip
@@ -1417,7 +1421,7 @@ def _on_update_status(self, _) -> None:
14171421
and services[0].current != ServiceStatus.ACTIVE
14181422
):
14191423
logger.warning(
1420-
"%s pebble service inactive, restarting service" % self._postgresql_service
1424+
f"{self._postgresql_service} pebble service inactive, restarting service"
14211425
)
14221426
try:
14231427
container.restart(self._postgresql_service)
@@ -1622,7 +1626,9 @@ def _remove_from_endpoints(self, endpoints: List[str]) -> None:
16221626
self._update_endpoints(endpoints_to_remove=endpoints)
16231627

16241628
def _update_endpoints(
1625-
self, endpoint_to_add: str = None, endpoints_to_remove: List[str] = None
1629+
self,
1630+
endpoint_to_add: Optional[str] = None,
1631+
endpoints_to_remove: Optional[List[str]] = None,
16261632
) -> None:
16271633
"""Update members IPs."""
16281634
# Allow leader to reset which members are part of the cluster.
@@ -1796,7 +1802,7 @@ def _restart(self, event: RunWithLock) -> None:
17961802
for attempt in Retrying(wait=wait_fixed(3), stop=stop_after_delay(300)):
17971803
with attempt:
17981804
if not self._can_connect_to_postgresql:
1799-
assert False
1805+
raise CannotConnectError
18001806
except Exception:
18011807
logger.exception("Unable to reconnect to postgresql")
18021808

@@ -1821,7 +1827,8 @@ def _can_connect_to_postgresql(self) -> bool:
18211827
try:
18221828
for attempt in Retrying(stop=stop_after_delay(30), wait=wait_fixed(3)):
18231829
with attempt:
1824-
assert self.postgresql.get_postgresql_timezones()
1830+
if not self.postgresql.get_postgresql_timezones():
1831+
raise CannotConnectError
18251832
except RetryError:
18261833
logger.debug("Cannot connect to database")
18271834
return False
@@ -1895,16 +1902,17 @@ def update_config(self, is_creating_backup: bool = False) -> bool:
18951902
# Restart the monitoring service if the password was rotated
18961903
container = self.unit.get_container("postgresql")
18971904
current_layer = container.get_plan()
1898-
if metrics_service := current_layer.services[self._metrics_service]:
1899-
if not metrics_service.environment.get("DATA_SOURCE_NAME", "").startswith(
1900-
f"user={MONITORING_USER} password={self.get_secret('app', MONITORING_PASSWORD_KEY)} "
1901-
):
1902-
container.add_layer(
1903-
self._metrics_service,
1904-
Layer({"services": {self._metrics_service: self._generate_metrics_service()}}),
1905-
combine=True,
1906-
)
1907-
container.restart(self._metrics_service)
1905+
if (
1906+
metrics_service := current_layer.services[self._metrics_service]
1907+
) and not metrics_service.environment.get("DATA_SOURCE_NAME", "").startswith(
1908+
f"user={MONITORING_USER} password={self.get_secret('app', MONITORING_PASSWORD_KEY)} "
1909+
):
1910+
container.add_layer(
1911+
self._metrics_service,
1912+
Layer({"services": {self._metrics_service: self._generate_metrics_service()}}),
1913+
combine=True,
1914+
)
1915+
container.restart(self._metrics_service)
19081916

19091917
return True
19101918

0 commit comments

Comments
 (0)