Skip to content

Commit 882e030

Browse files
authored
[MISC] Sync main to 16/edge and update libs
[MISC] Sync main to 16/edge and update libs
2 parents 3b05eb2 + 966620c commit 882e030

File tree

21 files changed

+753
-89
lines changed

21 files changed

+753
-89
lines changed

lib/charms/data_platform_libs/v0/data_interfaces.py

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ def _on_topic_requested(self, event: TopicRequestedEvent):
331331

332332
# Increment this PATCH version before using `charmcraft publish-lib` or reset
333333
# to 0 if you are raising the major API version
334-
LIBPATCH = 45
334+
LIBPATCH = 46
335335

336336
PYDEPS = ["ops>=2.0.0"]
337337

@@ -989,11 +989,7 @@ def __init__(
989989
@property
990990
def relations(self) -> List[Relation]:
991991
"""The list of Relation instances associated with this relation_name."""
992-
return [
993-
relation
994-
for relation in self._model.relations[self.relation_name]
995-
if self._is_relation_active(relation)
996-
]
992+
return self._model.relations[self.relation_name]
997993

998994
@property
999995
def secrets_enabled(self):
@@ -1271,15 +1267,6 @@ def _legacy_apply_on_delete(self, fields: List[str]) -> None:
12711267

12721268
# Internal helper methods
12731269

1274-
@staticmethod
1275-
def _is_relation_active(relation: Relation):
1276-
"""Whether the relation is active based on contained data."""
1277-
try:
1278-
_ = repr(relation.data)
1279-
return True
1280-
except (RuntimeError, ModelError):
1281-
return False
1282-
12831270
@staticmethod
12841271
def _is_secret_field(field: str) -> bool:
12851272
"""Is the field in question a secret reference (URI) field or not?"""

lib/charms/loki_k8s/v1/loki_push_api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ def __init__(self, ...):
546546

547547
# Increment this PATCH version before using `charmcraft publish-lib` or reset
548548
# to 0 if you are raising the major API version
549-
LIBPATCH = 16
549+
LIBPATCH = 17
550550

551551
PYDEPS = ["cosl"]
552552

@@ -1354,7 +1354,7 @@ def _url(self) -> str:
13541354
13551355
Return url to loki, including port number, but without the endpoint subpath.
13561356
"""
1357-
return "http://{}:{}".format(socket.getfqdn(), self.port)
1357+
return f"{self.scheme}://{socket.getfqdn()}:{self.port}"
13581358

13591359
def _endpoint(self, url) -> dict:
13601360
"""Get Loki push API endpoint for a given url.

lib/charms/postgresql_k8s/v0/postgresql.py

Lines changed: 177 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
# Increment this PATCH version before using `charmcraft publish-lib` or reset
3737
# to 0 if you are raising the major API version
38-
LIBPATCH = 52
38+
LIBPATCH = 53
3939

4040
# Groups to distinguish HBA access
4141
ACCESS_GROUP_IDENTITY = "identity_access"
@@ -113,6 +113,10 @@ class PostgreSQLGetPostgreSQLVersionError(Exception):
113113
"""Exception raised when retrieving PostgreSQL version fails."""
114114

115115

116+
class PostgreSQLListAccessibleDatabasesForUserError(Exception):
117+
"""Exception raised when retrieving the accessible databases for a user fails."""
118+
119+
116120
class PostgreSQLListGroupsError(Exception):
117121
"""Exception raised when retrieving PostgreSQL groups list fails."""
118122

@@ -190,6 +194,11 @@ def create_access_groups(self) -> None:
190194
try:
191195
with self._connect_to_database() as connection, connection.cursor() as cursor:
192196
for group in ACCESS_GROUPS:
197+
cursor.execute(
198+
SQL("SELECT TRUE FROM pg_roles WHERE rolname={};").format(Literal(group))
199+
)
200+
if cursor.fetchone() is not None:
201+
continue
193202
cursor.execute(
194203
SQL("CREATE ROLE {} NOLOGIN;").format(
195204
Identifier(group),
@@ -622,15 +631,22 @@ def is_tls_enabled(self, check_current_host: bool = False) -> bool:
622631
# Connection errors happen when PostgreSQL has not started yet.
623632
return False
624633

625-
def list_access_groups(self) -> Set[str]:
634+
def list_access_groups(self, current_host=False) -> Set[str]:
626635
"""Returns the list of PostgreSQL database access groups.
627636
637+
Args:
638+
current_host: whether to check the current host
639+
instead of the primary host.
640+
628641
Returns:
629642
List of PostgreSQL database access groups.
630643
"""
631644
connection = None
645+
host = self.current_host if current_host else None
632646
try:
633-
with self._connect_to_database() as connection, connection.cursor() as cursor:
647+
with self._connect_to_database(
648+
database_host=host
649+
) as connection, connection.cursor() as cursor:
634650
cursor.execute(
635651
"SELECT groname FROM pg_catalog.pg_group WHERE groname LIKE '%_access';"
636652
)
@@ -643,16 +659,69 @@ def list_access_groups(self) -> Set[str]:
643659
if connection is not None:
644660
connection.close()
645661

646-
def list_users(self) -> Set[str]:
662+
def list_accessible_databases_for_user(self, user: str, current_host=False) -> Set[str]:
663+
"""Returns the list of accessible databases for a specific user.
664+
665+
Args:
666+
user: the user to check.
667+
current_host: whether to check the current host
668+
instead of the primary host.
669+
670+
Returns:
671+
List of accessible database (the ones where
672+
the user has the CONNECT privilege).
673+
"""
674+
connection = None
675+
host = self.current_host if current_host else None
676+
try:
677+
with self._connect_to_database(
678+
database_host=host
679+
) as connection, connection.cursor() as cursor:
680+
cursor.execute(
681+
SQL(
682+
"SELECT TRUE FROM pg_catalog.pg_user WHERE usename = {} AND usesuper;"
683+
).format(Literal(user))
684+
)
685+
if cursor.fetchone() is not None:
686+
return {"all"}
687+
cursor.execute(
688+
SQL(
689+
"SELECT datname FROM pg_catalog.pg_database WHERE has_database_privilege({}, datname, 'CONNECT') AND NOT datistemplate;"
690+
).format(Literal(user))
691+
)
692+
databases = cursor.fetchall()
693+
return {database[0] for database in databases}
694+
except psycopg2.Error as e:
695+
logger.error(f"Failed to list accessible databases for user {user}: {e}")
696+
raise PostgreSQLListAccessibleDatabasesForUserError() from e
697+
finally:
698+
if connection is not None:
699+
connection.close()
700+
701+
def list_users(self, group: Optional[str] = None, current_host=False) -> Set[str]:
647702
"""Returns the list of PostgreSQL database users.
648703
704+
Args:
705+
group: optional group to filter the users.
706+
current_host: whether to check the current host
707+
instead of the primary host.
708+
649709
Returns:
650710
List of PostgreSQL database users.
651711
"""
652712
connection = None
713+
host = self.current_host if current_host else None
653714
try:
654-
with self._connect_to_database() as connection, connection.cursor() as cursor:
655-
cursor.execute("SELECT usename FROM pg_catalog.pg_user;")
715+
with self._connect_to_database(
716+
database_host=host
717+
) as connection, connection.cursor() as cursor:
718+
if group:
719+
query = SQL(
720+
"SELECT usename FROM (SELECT UNNEST(grolist) AS user_id FROM pg_catalog.pg_group WHERE groname = {}) AS g JOIN pg_catalog.pg_user AS u ON g.user_id = u.usesysid;"
721+
).format(Literal(group))
722+
else:
723+
query = "SELECT usename FROM pg_catalog.pg_user;"
724+
cursor.execute(query)
656725
usernames = cursor.fetchall()
657726
return {username[0] for username in usernames}
658727
except psycopg2.Error as e:
@@ -662,19 +731,27 @@ def list_users(self) -> Set[str]:
662731
if connection is not None:
663732
connection.close()
664733

665-
def list_users_from_relation(self) -> Set[str]:
734+
def list_users_from_relation(self, current_host=False) -> Set[str]:
666735
"""Returns the list of PostgreSQL database users that were created by a relation.
667736
737+
Args:
738+
current_host: whether to check the current host
739+
instead of the primary host.
740+
668741
Returns:
669742
List of PostgreSQL database users.
670743
"""
671744
connection = None
745+
host = self.current_host if current_host else None
672746
try:
673-
with self._connect_to_database() as connection, connection.cursor() as cursor:
747+
with self._connect_to_database(
748+
database_host=host
749+
) as connection, connection.cursor() as cursor:
674750
cursor.execute(
675751
"SELECT usename "
676752
"FROM pg_catalog.pg_user "
677-
"WHERE usename LIKE 'relation_id_%' OR usename LIKE 'relation-%';"
753+
"WHERE usename LIKE 'relation_id_%' OR usename LIKE 'relation-%' "
754+
"OR usename LIKE 'pgbouncer_auth_relation_id_%' OR usename LIKE '%_user_%_%';"
678755
)
679756
usernames = cursor.fetchall()
680757
return {username[0] for username in usernames}
@@ -705,31 +782,100 @@ def set_up_database(self, temp_location: Optional[str] = None) -> None:
705782
connection = None
706783
cursor = None
707784
try:
708-
connection = self._connect_to_database()
709-
cursor = connection.cursor()
710-
711-
if temp_location is not None:
712-
cursor.execute("SELECT TRUE FROM pg_tablespace WHERE spcname='temp';")
785+
with self._connect_to_database(
786+
database="template1"
787+
) as connection, connection.cursor() as cursor:
788+
# Create database function and event trigger to identify users created by PgBouncer.
789+
cursor.execute(
790+
"SELECT TRUE FROM pg_event_trigger WHERE evtname = 'update_pg_hba_on_create_schema';"
791+
)
713792
if cursor.fetchone() is None:
714-
cursor.execute(f"CREATE TABLESPACE temp LOCATION '{temp_location}';")
715-
cursor.execute("GRANT CREATE ON TABLESPACE temp TO public;")
716-
717-
cursor.execute("SELECT TRUE FROM pg_roles WHERE rolname='admin';")
718-
if cursor.fetchone() is None:
719-
# Allow access to the postgres database only to the system users.
720-
cursor.execute("REVOKE ALL PRIVILEGES ON DATABASE postgres FROM PUBLIC;")
721-
cursor.execute("REVOKE CREATE ON SCHEMA public FROM PUBLIC;")
722-
for user in self.system_users:
723-
cursor.execute(
724-
SQL("GRANT ALL PRIVILEGES ON DATABASE postgres TO {};").format(
725-
Identifier(user)
793+
cursor.execute("""
794+
CREATE OR REPLACE FUNCTION update_pg_hba()
795+
RETURNS event_trigger
796+
LANGUAGE plpgsql
797+
AS $$
798+
DECLARE
799+
hba_file TEXT;
800+
copy_command TEXT;
801+
connection_type TEXT;
802+
rec record;
803+
insert_value TEXT;
804+
changes INTEGER = 0;
805+
BEGIN
806+
-- Don't execute on replicas.
807+
IF NOT pg_is_in_recovery() THEN
808+
-- Load the current authorisation rules.
809+
DROP TABLE IF EXISTS pg_hba;
810+
CREATE TEMPORARY TABLE pg_hba (lines TEXT);
811+
SELECT setting INTO hba_file FROM pg_settings WHERE name = 'hba_file';
812+
IF hba_file IS NOT NULL THEN
813+
copy_command='COPY pg_hba FROM ''' || hba_file || '''' ;
814+
EXECUTE copy_command;
815+
-- Build a list of the relation users and the databases they can access.
816+
DROP TABLE IF EXISTS relation_users;
817+
CREATE TEMPORARY TABLE relation_users AS
818+
SELECT t.user, STRING_AGG(DISTINCT t.database, ',') AS databases FROM( SELECT u.usename AS user, CASE WHEN u.usesuper THEN 'all' ELSE d.datname END AS database FROM ( SELECT usename, usesuper FROM pg_catalog.pg_user WHERE usename NOT IN ('backup', 'monitoring', 'operator', 'postgres', 'replication', 'rewind')) AS u JOIN ( SELECT datname FROM pg_catalog.pg_database WHERE NOT datistemplate ) AS d ON has_database_privilege(u.usename, d.datname, 'CONNECT') ) AS t GROUP BY 1;
819+
IF (SELECT COUNT(lines) FROM pg_hba WHERE lines LIKE 'hostssl %') > 0 THEN
820+
connection_type := 'hostssl';
821+
ELSE
822+
connection_type := 'host';
823+
END IF;
824+
-- Add the new users to the pg_hba file.
825+
FOR rec IN SELECT * FROM relation_users
826+
LOOP
827+
insert_value := connection_type || ' ' || rec.databases || ' ' || rec.user || ' 0.0.0.0/0 md5';
828+
IF (SELECT COUNT(lines) FROM pg_hba WHERE lines = insert_value) = 0 THEN
829+
INSERT INTO pg_hba (lines) VALUES (insert_value);
830+
changes := changes + 1;
831+
END IF;
832+
END LOOP;
833+
-- Remove users that don't exist anymore from the pg_hba file.
834+
FOR rec IN SELECT h.lines FROM pg_hba AS h LEFT JOIN relation_users AS r ON SPLIT_PART(h.lines, ' ', 3) = r.user WHERE r.user IS NULL AND (SPLIT_PART(h.lines, ' ', 3) LIKE 'relation_id_%' OR SPLIT_PART(h.lines, ' ', 3) LIKE 'pgbouncer_auth_relation_id_%' OR SPLIT_PART(h.lines, ' ', 3) LIKE '%_user_%_%')
835+
LOOP
836+
DELETE FROM pg_hba WHERE lines = rec.lines;
837+
changes := changes + 1;
838+
END LOOP;
839+
-- Apply the changes to the pg_hba file.
840+
IF changes > 0 THEN
841+
copy_command='COPY pg_hba TO ''' || hba_file || '''' ;
842+
EXECUTE copy_command;
843+
PERFORM pg_reload_conf();
844+
END IF;
845+
END IF;
846+
END IF;
847+
END;
848+
$$;
849+
""")
850+
cursor.execute("""
851+
CREATE EVENT TRIGGER update_pg_hba_on_create_schema
852+
ON ddl_command_end
853+
WHEN TAG IN ('CREATE SCHEMA')
854+
EXECUTE FUNCTION update_pg_hba();
855+
""")
856+
cursor.execute("""
857+
CREATE EVENT TRIGGER update_pg_hba_on_drop_schema
858+
ON ddl_command_end
859+
WHEN TAG IN ('DROP SCHEMA')
860+
EXECUTE FUNCTION update_pg_hba();
861+
""")
862+
with self._connect_to_database() as connection, connection.cursor() as cursor:
863+
cursor.execute("SELECT TRUE FROM pg_roles WHERE rolname='admin';")
864+
if cursor.fetchone() is None:
865+
# Allow access to the postgres database only to the system users.
866+
cursor.execute("REVOKE ALL PRIVILEGES ON DATABASE postgres FROM PUBLIC;")
867+
cursor.execute("REVOKE CREATE ON SCHEMA public FROM PUBLIC;")
868+
for user in self.system_users:
869+
cursor.execute(
870+
SQL("GRANT ALL PRIVILEGES ON DATABASE postgres TO {};").format(
871+
Identifier(user)
872+
)
726873
)
874+
self.create_user(
875+
PERMISSIONS_GROUP_ADMIN,
876+
extra_user_roles=["pg_read_all_data", "pg_write_all_data"],
727877
)
728-
self.create_user(
729-
PERMISSIONS_GROUP_ADMIN,
730-
extra_user_roles=["pg_read_all_data", "pg_write_all_data"],
731-
)
732-
cursor.execute("GRANT CONNECT ON DATABASE postgres TO admin;")
878+
cursor.execute("GRANT CONNECT ON DATABASE postgres TO admin;")
733879
except psycopg2.Error as e:
734880
logger.error(f"Failed to set up databases: {e}")
735881
raise PostgreSQLDatabasesSetupError() from e

lib/charms/tempo_coordinator_k8s/v0/charm_tracing.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ def _remove_stale_otel_sdk_packages():
314314
import opentelemetry
315315
import ops
316316
from opentelemetry.exporter.otlp.proto.common._internal.trace_encoder import (
317-
encode_spans,
317+
encode_spans # type: ignore
318318
)
319319
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
320320
from opentelemetry.sdk.resources import Resource
@@ -348,7 +348,7 @@ def _remove_stale_otel_sdk_packages():
348348
# Increment this PATCH version before using `charmcraft publish-lib` or reset
349349
# to 0 if you are raising the major API version
350350

351-
LIBPATCH = 7
351+
LIBPATCH = 8
352352

353353
PYDEPS = ["opentelemetry-exporter-otlp-proto-http==1.21.0"]
354354

@@ -704,7 +704,14 @@ def _get_server_cert(
704704
f"{charm_type}.{server_cert_attr} is None; sending traces over INSECURE connection."
705705
)
706706
return
707-
elif not Path(server_cert).is_absolute():
707+
if not isinstance(server_cert, (str, Path)):
708+
logger.warning(
709+
f"{charm_type}.{server_cert_attr} has unexpected type {type(server_cert)}; "
710+
f"sending traces over INSECURE connection."
711+
)
712+
return
713+
path = Path(server_cert)
714+
if not path.is_absolute() or not path.exists():
708715
raise ValueError(
709716
f"{charm_type}.{server_cert_attr} should resolve to a valid tls cert absolute path (string | Path)); "
710717
f"got {server_cert} instead."

0 commit comments

Comments
 (0)