Skip to content

Commit 3413b60

Browse files
committed
Merge branch 'main' into sync-main-16
2 parents e287950 + 5d98a0f commit 3413b60

File tree

10 files changed

+163
-101
lines changed

10 files changed

+163
-101
lines changed

lib/charms/certificate_transfer_interface/v0/certificate_transfer.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,13 @@ def _on_certificate_removed(self, event: CertificateRemovedEvent):
102102

103103
from jsonschema import exceptions, validate # type: ignore[import-untyped]
104104
from ops import Relation
105-
from ops.charm import CharmBase, CharmEvents, RelationBrokenEvent, RelationChangedEvent
105+
from ops.charm import (
106+
CharmBase,
107+
CharmEvents,
108+
RelationBrokenEvent,
109+
RelationChangedEvent,
110+
RelationCreatedEvent,
111+
)
106112
from ops.framework import EventBase, EventSource, Handle, Object
107113

108114
# The unique Charmhub library identifier, never change it
@@ -113,7 +119,7 @@ def _on_certificate_removed(self, event: CertificateRemovedEvent):
113119

114120
# Increment this PATCH version before using `charmcraft publish-lib` or reset
115121
# to 0 if you are raising the major API version
116-
LIBPATCH = 9
122+
LIBPATCH = 11
117123

118124
PYDEPS = ["jsonschema"]
119125

@@ -136,6 +142,7 @@ def _on_certificate_removed(self, event: CertificateRemovedEvent):
136142
"-----BEGIN CERTIFICATE-----\nMIIC6DCCAdCgAwIBAgIUW42TU9LSjEZLMCclWrvSwAsgRtcwDQYJKoZIhvcNAQEL\nBQAwIDELMAkGA1UEBhMCVVMxETAPBgNVBAMMCHdoYXRldmVyMB4XDTIzMDMyNDE4\nNDMxOVoXDTI0MDMyMzE4NDMxOVowPDELMAkGA1UEAwwCb2sxLTArBgNVBC0MJGUw\nNjVmMWI3LTE2OWEtNDE5YS1iNmQyLTc3OWJkOGM4NzIwNjCCASIwDQYJKoZIhvcN\nAQEBBQADggEPADCCAQoCggEBAK42ixoklDH5K5i1NxXo/AFACDa956pE5RA57wlC\nBfgUYaIDRmv7TUVJh6zoMZSD6wjSZl3QgP7UTTZeHbvs3QE9HUwEkH1Lo3a8vD3z\neqsE2vSnOkpWWnPbfxiQyrTm77/LAWBt7lRLRLdfL6WcucD3wsGqm58sWXM3HG0f\nSN7PHCZUFqU6MpkHw8DiKmht5hBgWG+Vq3Zw8MNaqpwb/NgST3yYdcZwb58G2FTS\nZvDSdUfRmD/mY7TpciYV8EFylXNNFkth8oGNLunR9adgZ+9IunfRKj1a7S5GSwXU\nAZDaojw+8k5i3ikztsWH11wAVCiLj/3euIqq95z8xGycnKcCAwEAATANBgkqhkiG\n9w0BAQsFAAOCAQEAWMvcaozgBrZ/MAxzTJmp5gZyLxmMNV6iT9dcqbwzDtDtBvA/\n46ux6ytAQ+A7Bd3AubvozwCr1Id6g66ae0blWYRRZmF8fDdX/SBjIUkv7u9A3NVQ\nXN9gsEvK9pdpfN4ZiflfGSLdhM1STHycLmhG6H5s7HklbukMRhQi+ejbSzm/wiw1\nipcxuKhSUIVNkTLusN5b+HE2gwF1fn0K0z5jWABy08huLgbaEKXJEx5/FKLZGJga\nfpIzAdf25kMTu3gggseaAmzyX3AtT1i8A8nqYfe8fnnVMkvud89kq5jErv/hlMC9\n49g5yWQR2jilYYM3j9BHDuB+Rs+YS5BCep1JnQ==\n-----END CERTIFICATE-----\n",
137143
"-----BEGIN CERTIFICATE-----\nMIIC6DCCAdCgAwIBAgIUdiBwE/CtaBXJl3MArjZen6Y8kigwDQYJKoZIhvcNAQEL\nBQAwIDELMAkGA1UEBhMCVVMxETAPBgNVBAMMCHdoYXRldmVyMB4XDTIzMDMyNDE4\nNDg1OVoXDTI0MDMyMzE4NDg1OVowPDELMAkGA1UEAwwCb2sxLTArBgNVBC0MJDEw\nMDdjNDBhLWUwYzMtNDVlOS05YTAxLTVlYjY0NWQ0ZmEyZDCCASIwDQYJKoZIhvcN\nAQEBBQADggEPADCCAQoCggEBANOnUl6JDlXpLMRr/PxgtfE/E5Yk6E/TkPkPL/Kk\ntUGjEi42XZDg9zn3U6cjTDYu+rfKY2jiitfsduW6DQIkEpz3AvbuCMbbgnFpcjsB\nYysLSMTmuz/AVPrfnea/tQTALcONCSy1VhAjGSr81ZRSMB4khl9StSauZrbkpJ1P\nshqkFSUyAi31mKrnXz0Es/v0Yi0FzAlgWrZ4u1Ld+Bo2Xz7oK4mHf7/93Jc+tEaM\nIqG6ocD0q8bjPp0tlSxftVADNUzWlZfM6fue5EXzOsKqyDrxYOSchfU9dNzKsaBX\nkxbHEeSUPJeYYj7aVPEfAs/tlUGsoXQvwWfRie8grp2BoLECAwEAATANBgkqhkiG\n9w0BAQsFAAOCAQEACZARBpHYH6Gr2a1ka0mCWfBmOZqfDVan9rsI5TCThoylmaXW\nquEiZ2LObI+5faPzxSBhr9TjJlQamsd4ywout7pHKN8ZGqrCMRJ1jJbUfobu1n2k\nUOsY4+jzV1IRBXJzj64fLal4QhUNv341lAer6Vz3cAyRk7CK89b/DEY0x+jVpyZT\n1osx9JtsOmkDTgvdStGzq5kPKWOfjwHkmKQaZXliCgqbhzcCERppp1s/sX6K7nIh\n4lWiEmzUSD3Hngk51KGWlpZszO5KQ4cSZ3HUt/prg+tt0ROC3pY61k+m5dDUa9M8\nRtMI6iTjzSj/UV8DiAx0yeM+bKoy4jGeXmaL3g==\n-----END CERTIFICATE-----\n",
138144
],
145+
"version": 0,
139146
}
140147
],
141148
"properties": {
@@ -158,6 +165,13 @@ def _on_certificate_removed(self, event: CertificateRemovedEvent):
158165
"title": "CA public TLS certificate chain",
159166
"description": "CA public TLS certificate chain",
160167
},
168+
"version": {
169+
"$id": "#/properties/version",
170+
"type": "integer",
171+
"title": "Interface version",
172+
"minimum": 0,
173+
"description": "Highest supported version of this interface",
174+
},
161175
},
162176
"anyOf": [{"required": ["certificate"]}, {"required": ["ca"]}, {"required": ["chain"]}],
163177
"additionalProperties": True,
@@ -277,6 +291,7 @@ def set_certificate(
277291
relation.data[self.model.unit]["certificate"] = certificate
278292
relation.data[self.model.unit]["ca"] = ca
279293
relation.data[self.model.unit]["chain"] = json.dumps(chain)
294+
relation.data[self.model.unit]["version"] = str(LIBAPI)
280295

281296
def remove_certificate(self, relation_id: int) -> None:
282297
"""Remove a given certificate from relation data.
@@ -339,6 +354,9 @@ def __init__(
339354
self.framework.observe(
340355
charm.on[relationship_name].relation_broken, self._on_relation_broken
341356
)
357+
self.framework.observe(
358+
charm.on[relationship_name].relation_created, self._on_relation_created
359+
)
342360

343361
@staticmethod
344362
def _relation_data_is_valid(relation_data: dict) -> bool:
@@ -393,9 +411,21 @@ def _on_relation_broken(self, event: RelationBrokenEvent) -> None:
393411
"""
394412
self.on.certificate_removed.emit(relation_id=event.relation.id)
395413

414+
def _on_relation_created(self, event: RelationCreatedEvent) -> None:
415+
"""Handle relation created event.
416+
417+
Args:
418+
event: Juju event
419+
420+
Returns:
421+
None
422+
"""
423+
if self.model.unit.is_leader():
424+
event.relation.data[self.model.app]["version"] = str(LIBAPI)
425+
396426
def is_ready(self, relation: Relation) -> bool:
397427
"""Check if the relation is ready by checking that it has valid relation data."""
398-
relation_data = _load_relation_data(relation.data[relation.app])
428+
relation_data = _load_relation_data(relation.data[relation.units.pop()])
399429
if not self._relation_data_is_valid(relation_data):
400430
logger.warning("Provider relation data did not pass JSON Schema validation: ")
401431
return False

lib/charms/data_platform_libs/v0/data_interfaces.py

Lines changed: 26 additions & 3 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 = 41
334+
LIBPATCH = 42
335335

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

@@ -960,6 +960,7 @@ class Data(ABC):
960960
"username": SECRET_GROUPS.USER,
961961
"password": SECRET_GROUPS.USER,
962962
"uris": SECRET_GROUPS.USER,
963+
"read-only-uris": SECRET_GROUPS.USER,
963964
"tls": SECRET_GROUPS.TLS,
964965
"tls-ca": SECRET_GROUPS.TLS,
965966
}
@@ -1700,7 +1701,7 @@ def set_tls_ca(self, relation_id: int, tls_ca: str) -> None:
17001701
class RequirerData(Data):
17011702
"""Requirer-side of the relation."""
17021703

1703-
SECRET_FIELDS = ["username", "password", "tls", "tls-ca", "uris"]
1704+
SECRET_FIELDS = ["username", "password", "tls", "tls-ca", "uris", "read-only-uris"]
17041705

17051706
def __init__(
17061707
self,
@@ -2368,7 +2369,7 @@ def _delete_relation_data(self, relation: Relation, fields: List[str]) -> None:
23682369
self.secret_fields,
23692370
fields,
23702371
self._update_relation_secret,
2371-
data={field: self.deleted_label for field in fields},
2372+
data=dict.fromkeys(fields, self.deleted_label),
23722373
)
23732374
else:
23742375
_, normal_fields = self._process_secret_fields(
@@ -2749,6 +2750,19 @@ def uris(self) -> Optional[str]:
27492750

27502751
return self.relation.data[self.relation.app].get("uris")
27512752

2753+
@property
2754+
def read_only_uris(self) -> Optional[str]:
2755+
"""Returns the readonly connection URIs."""
2756+
if not self.relation.app:
2757+
return None
2758+
2759+
if self.secrets_enabled:
2760+
secret = self._get_secret("user")
2761+
if secret:
2762+
return secret.get("read-only-uris")
2763+
2764+
return self.relation.data[self.relation.app].get("read-only-uris")
2765+
27522766
@property
27532767
def version(self) -> Optional[str]:
27542768
"""Returns the version of the database.
@@ -2855,6 +2869,15 @@ def set_uris(self, relation_id: int, uris: str) -> None:
28552869
"""
28562870
self.update_relation_data(relation_id, {"uris": uris})
28572871

2872+
def set_read_only_uris(self, relation_id: int, uris: str) -> None:
2873+
"""Set the database readonly connection URIs in the application relation databag.
2874+
2875+
Args:
2876+
relation_id: the identifier for a particular relation.
2877+
uris: connection URIs.
2878+
"""
2879+
self.update_relation_data(relation_id, {"read-only-uris": uris})
2880+
28582881
def set_version(self, relation_id: int, version: str) -> None:
28592882
"""Set the database version in the application relation databag.
28602883

lib/charms/glauth_k8s/v0/ldap.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def _on_ldap_requested(self, event: LdapRequestedEvent) -> None:
147147

148148
# Increment this PATCH version before using `charmcraft publish-lib` or reset
149149
# to 0 if you are raising the major API version
150-
LIBPATCH = 9
150+
LIBPATCH = 10
151151

152152
PYDEPS = ["pydantic"]
153153

@@ -178,13 +178,13 @@ def field_validator(*args: Any, **kwargs: Any) -> Callable:
178178

179179
encoders_config = {}
180180

181-
def field_serializer(field_name: str, mode: Optional[str] = None) -> Callable:
181+
def field_serializer(*fields: str, mode: Optional[str] = None) -> Callable:
182182
def _field_serializer(f: Callable, *args: Any, **kwargs: Any) -> Callable:
183183
@wraps(f)
184184
def wrapper(self: object, *args: Any, **kwargs: Any) -> Any:
185185
return f(self, *args, **kwargs)
186186

187-
encoders_config[wrapper] = field_name
187+
encoders_config[wrapper] = fields
188188
return wrapper
189189

190190
return _field_serializer
@@ -195,9 +195,10 @@ def __init__(self, name: str, bases: Tuple[object], attrs: Dict) -> None:
195195
self._encoders = {}
196196

197197
self._encoders.update({
198-
encoders_config[func]: func
198+
encoder: func
199199
for func in attrs.values()
200200
if callable(func) and func in encoders_config
201+
for encoder in encoders_config[func]
201202
})
202203

203204
super().__init__(name, bases, attrs)
@@ -284,6 +285,7 @@ def remove(self) -> None:
284285

285286
class LdapProviderBaseData(BaseModel):
286287
urls: List[str] = Field(frozen=True)
288+
ldaps_urls: List[str] = Field(frozen=True)
287289
base_dn: str = Field(frozen=True)
288290
starttls: StrictBool = Field(frozen=True)
289291

@@ -301,7 +303,21 @@ def validate_ldap_urls(cls, vs: List[str] | str) -> List[str]:
301303

302304
return vs
303305

304-
@field_serializer("urls")
306+
@field_validator("ldaps_urls", mode="before")
307+
@classmethod
308+
def validate_ldaps_urls(cls, vs: List[str] | str) -> List[str]:
309+
if isinstance(vs, str):
310+
vs = json.loads(vs)
311+
if isinstance(vs, str):
312+
vs = [vs]
313+
314+
for v in vs:
315+
if not v.startswith("ldaps://"):
316+
raise ValidationError.from_exception_data("Invalid LDAPS URL scheme.")
317+
318+
return vs
319+
320+
@field_serializer("urls", "ldaps_urls")
305321
def serialize_list(self, urls: List[str]) -> str:
306322
return str(json.dumps(urls))
307323

lib/charms/loki_k8s/v1/loki_push_api.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
This document explains how to use the two principal objects this library provides:
1010
1111
- `LokiPushApiProvider`: This object is meant to be used by any Charmed Operator that needs to
12-
implement the provider side of the `loki_push_api` relation interface: for instance, a Loki charm.
12+
implement the provider side of the `loki_push_api` relation interface. For instance, a Loki charm.
1313
The provider side of the relation represents the server side, to which logs are being pushed.
1414
1515
- `LokiPushApiConsumer`: This object is meant to be used by any Charmed Operator that needs to
@@ -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 = 15
549+
LIBPATCH = 16
550550

551551
PYDEPS = ["cosl"]
552552

@@ -1756,8 +1756,11 @@ def _on_logging_relation_changed(self, event: RelationEvent):
17561756

17571757
self.on.loki_push_api_endpoint_joined.emit()
17581758

1759-
def _reinitialize_alert_rules(self):
1759+
def reload_alerts(self) -> None:
17601760
"""Reloads alert rules and updates all relations."""
1761+
self._reinitialize_alert_rules()
1762+
1763+
def _reinitialize_alert_rules(self):
17611764
for relation in self._charm.model.relations[self._relation_name]:
17621765
self._handle_alert_rules(relation)
17631766

lib/charms/tempo_coordinator_k8s/v0/tracing.py

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def __init__(self, *args):
110110

111111
# Increment this PATCH version before using `charmcraft publish-lib` or reset
112112
# to 0 if you are raising the major API version
113-
LIBPATCH = 6
113+
LIBPATCH = 7
114114

115115
PYDEPS = ["pydantic"]
116116

@@ -780,7 +780,16 @@ def __init__(
780780
self.framework.observe(events.relation_broken, self._on_tracing_relation_broken)
781781

782782
if protocols:
783-
self.request_protocols(protocols)
783+
# we can't be sure that the current event context supports read/writing relation data for this relation,
784+
# so we catch ModelErrors. This is because we're doing this in init.
785+
try:
786+
self.request_protocols(protocols)
787+
except ModelError as e:
788+
logger.error(
789+
f"encountered error {e} while attempting to request_protocols."
790+
f"The relation must be gone."
791+
)
792+
pass
784793

785794
def request_protocols(
786795
self, protocols: Sequence[ReceiverProtocol], relation: Optional[Relation] = None
@@ -795,26 +804,11 @@ def request_protocols(
795804
"You need to pass a nonempty sequence of protocols to `request_protocols`."
796805
)
797806

798-
try:
799-
if self._charm.unit.is_leader():
800-
for relation in relations:
801-
TracingRequirerAppData(
802-
receivers=list(protocols),
803-
).dump(relation.data[self._charm.app])
804-
805-
except ModelError as e:
806-
# args are bytes
807-
msg = e.args[0]
808-
if isinstance(msg, bytes):
809-
if msg.startswith(
810-
b"ERROR cannot read relation application settings: permission denied"
811-
):
812-
logger.error(
813-
f"encountered error {e} while attempting to request_protocols."
814-
f"The relation must be gone."
815-
)
816-
return
817-
raise
807+
if self._charm.unit.is_leader():
808+
for relation in relations:
809+
TracingRequirerAppData(
810+
receivers=list(protocols),
811+
).dump(relation.data[self._charm.app])
818812

819813
@property
820814
def relations(self) -> List[Relation]:

src/relations/postgresql_provider.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def _on_database_requested(self, event: DatabaseRequestedEvent) -> None:
136136
self.database_provides.set_tls_ca(event.relation.id, ca)
137137

138138
# Update the read-only endpoint.
139-
self.update_read_only_endpoint(event)
139+
self.update_read_only_endpoint(event, user, password)
140140

141141
# Set the database version.
142142
self.database_provides.set_version(
@@ -200,7 +200,13 @@ def _on_relation_broken(self, event: RelationBrokenEvent) -> None:
200200
f"Failed to delete user during {self.relation_name} relation broken event"
201201
)
202202

203-
def update_read_only_endpoint(self, event: DatabaseRequestedEvent = None) -> None:
203+
def update_read_only_endpoint(
204+
self,
205+
event: DatabaseRequestedEvent | None = None,
206+
user: str | None = None,
207+
password: str | None = None,
208+
database: str | None = None,
209+
) -> None:
204210
"""Set the read-only endpoint only if there are replicas."""
205211
if not self.charm.unit.is_leader():
206212
return
@@ -209,18 +215,43 @@ def update_read_only_endpoint(self, event: DatabaseRequestedEvent = None) -> Non
209215
endpoints = (
210216
f"{self.charm.replicas_endpoint}:{DATABASE_PORT}"
211217
if len(self.charm._peers.units) > 0
212-
else ""
218+
else f"{self.charm.primary_endpoint}:{DATABASE_PORT}"
213219
)
214220

215221
# Get the current relation or all the relations
216222
# if this is triggered by another type of event.
217223
relations = [event.relation] if event else self.model.relations[self.relation_name]
224+
if not event:
225+
user = None
226+
password = None
227+
database = None
218228

219229
for relation in relations:
220230
self.database_provides.set_read_only_endpoints(
221231
relation.id,
222232
endpoints,
223233
)
234+
# Make sure that the URI will be a secret
235+
if (
236+
secret_fields := self.database_provides.fetch_relation_field(
237+
relation.id, "requested-secrets"
238+
)
239+
) and "read-only-uris" in secret_fields:
240+
if not user or not password or not database:
241+
user = f"relation_id_{relation.id}"
242+
database = self.database_provides.fetch_relation_field(relation.id, "database")
243+
password = self.database_provides.fetch_my_relation_field(
244+
relation.id, "password"
245+
)
246+
247+
self.database_provides.set_read_only_uris(
248+
relation.id,
249+
f"postgresql://{user}:{password}@{endpoints}/{database}",
250+
)
251+
# Reset the creds for the next iteration
252+
user = None
253+
password = None
254+
database = None
224255

225256
def update_tls_flag(self, tls: str) -> None:
226257
"""Update TLS flag and CA in relation databag."""

0 commit comments

Comments
 (0)