Skip to content

Commit 4cd27ff

Browse files
authored
DPE-3265 refactor for in lib secrets (#362)
* moving secret logic to lib * updated other libs * remove fallback key * runs on leader only - set app data * using correct secret label * unused var * bump lib * increasing timeout period for k8s service setup * ci is failing on deployment * bump agent versions * current 2.9.x * current 3.1.x (secret fix in lib)
1 parent 9032a28 commit 4cd27ff

File tree

12 files changed

+781
-579
lines changed

12 files changed

+781
-579
lines changed

.github/workflows/ci.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ jobs:
4545
fail-fast: false
4646
matrix:
4747
juju:
48-
- agent: 2.9.45
48+
- agent: 2.9.46
4949
libjuju: ^2
50-
- agent: 3.1.6
50+
- agent: 3.1.7
5151
name: Integration test charm | ${{ matrix.juju.agent }}
5252
needs:
5353
- lint

lib/charms/data_platform_libs/v0/data_interfaces.py

Lines changed: 595 additions & 179 deletions
Large diffs are not rendered by default.

lib/charms/data_platform_libs/v0/s3.py

Lines changed: 74 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
"""A library for communicating with the S3 credentials providers and consumers.
15+
r"""A library for communicating with the S3 credentials providers and consumers.
1616
1717
This library provides the relevant interface code implementing the communication
1818
specification for fetching, retrieving, triggering, and responding to events related to
@@ -113,23 +113,21 @@ def _on_credential_gone(self, event: CredentialsGoneEvent):
113113
import json
114114
import logging
115115
from collections import namedtuple
116-
from typing import Dict, List, Optional
116+
from typing import Dict, List, Optional, Union
117117

118118
import ops.charm
119119
import ops.framework
120120
import ops.model
121121
from ops.charm import (
122122
CharmBase,
123123
CharmEvents,
124-
EventSource,
125-
Object,
126-
ObjectEvents,
127124
RelationBrokenEvent,
128125
RelationChangedEvent,
129126
RelationEvent,
130127
RelationJoinedEvent,
131128
)
132-
from ops.model import Relation
129+
from ops.framework import EventSource, Object, ObjectEvents
130+
from ops.model import Application, Relation, RelationDataContent, Unit
133131

134132
# The unique Charmhub library identifier, never change it
135133
LIBID = "fca396f6254246c9bfa565b1f85ab528"
@@ -139,7 +137,7 @@ def _on_credential_gone(self, event: CredentialsGoneEvent):
139137

140138
# Increment this PATCH version before using `charmcraft publish-lib` or reset
141139
# to 0 if you are raising the major API version
142-
LIBPATCH = 2
140+
LIBPATCH = 4
143141

144142
logger = logging.getLogger(__name__)
145143

@@ -152,7 +150,7 @@ def _on_credential_gone(self, event: CredentialsGoneEvent):
152150
deleted - key that were deleted"""
153151

154152

155-
def diff(event: RelationChangedEvent, bucket: str) -> Diff:
153+
def diff(event: RelationChangedEvent, bucket: Union[Unit, Application]) -> Diff:
156154
"""Retrieves the diff of the data in the relation changed databag.
157155
158156
Args:
@@ -166,9 +164,11 @@ def diff(event: RelationChangedEvent, bucket: str) -> Diff:
166164
# Retrieve the old data from the data key in the application relation databag.
167165
old_data = json.loads(event.relation.data[bucket].get("data", "{}"))
168166
# Retrieve the new data from the event relation databag.
169-
new_data = {
170-
key: value for key, value in event.relation.data[event.app].items() if key != "data"
171-
}
167+
new_data = (
168+
{key: value for key, value in event.relation.data[event.app].items() if key != "data"}
169+
if event.app
170+
else {}
171+
)
172172

173173
# These are the keys that were added to the databag and triggered this event.
174174
added = new_data.keys() - old_data.keys()
@@ -193,7 +193,10 @@ class BucketEvent(RelationEvent):
193193
@property
194194
def bucket(self) -> Optional[str]:
195195
"""Returns the bucket was requested."""
196-
return self.relation.data[self.relation.app].get("bucket")
196+
if not self.relation.app:
197+
return None
198+
199+
return self.relation.data[self.relation.app].get("bucket", "")
197200

198201

199202
class CredentialRequestedEvent(BucketEvent):
@@ -209,7 +212,7 @@ class S3CredentialEvents(CharmEvents):
209212
class S3Provider(Object):
210213
"""A provider handler for communicating S3 credentials to consumers."""
211214

212-
on = S3CredentialEvents()
215+
on = S3CredentialEvents() # pyright: ignore [reportGeneralTypeIssues]
213216

214217
def __init__(
215218
self,
@@ -232,7 +235,9 @@ def _on_relation_changed(self, event: RelationChangedEvent) -> None:
232235
diff = self._diff(event)
233236
# emit on credential requested if bucket is provided by the requirer application
234237
if "bucket" in diff.added:
235-
self.on.credentials_requested.emit(event.relation, app=event.app, unit=event.unit)
238+
getattr(self.on, "credentials_requested").emit(
239+
event.relation, app=event.app, unit=event.unit
240+
)
236241

237242
def _load_relation_data(self, raw_relation_data: dict) -> dict:
238243
"""Loads relation data from the relation data bag.
@@ -242,7 +247,7 @@ def _load_relation_data(self, raw_relation_data: dict) -> dict:
242247
Returns:
243248
dict: Relation data in dict format.
244249
"""
245-
connection_data = dict()
250+
connection_data = {}
246251
for key in raw_relation_data:
247252
try:
248253
connection_data[key] = json.loads(raw_relation_data[key])
@@ -309,9 +314,11 @@ def fetch_relation_data(self) -> dict:
309314
"""
310315
data = {}
311316
for relation in self.relations:
312-
data[relation.id] = {
313-
key: value for key, value in relation.data[relation.app].items() if key != "data"
314-
}
317+
data[relation.id] = (
318+
{key: value for key, value in relation.data[relation.app].items() if key != "data"}
319+
if relation.app
320+
else {}
321+
)
315322
return data
316323

317324
def update_connection_info(self, relation_id: int, connection_data: dict) -> None:
@@ -493,46 +500,73 @@ class S3Event(RelationEvent):
493500
@property
494501
def bucket(self) -> Optional[str]:
495502
"""Returns the bucket name."""
503+
if not self.relation.app:
504+
return None
505+
496506
return self.relation.data[self.relation.app].get("bucket")
497507

498508
@property
499509
def access_key(self) -> Optional[str]:
500510
"""Returns the access key."""
511+
if not self.relation.app:
512+
return None
513+
501514
return self.relation.data[self.relation.app].get("access-key")
502515

503516
@property
504517
def secret_key(self) -> Optional[str]:
505518
"""Returns the secret key."""
519+
if not self.relation.app:
520+
return None
521+
506522
return self.relation.data[self.relation.app].get("secret-key")
507523

508524
@property
509525
def path(self) -> Optional[str]:
510526
"""Returns the path where data can be stored."""
527+
if not self.relation.app:
528+
return None
529+
511530
return self.relation.data[self.relation.app].get("path")
512531

513532
@property
514533
def endpoint(self) -> Optional[str]:
515534
"""Returns the endpoint address."""
535+
if not self.relation.app:
536+
return None
537+
516538
return self.relation.data[self.relation.app].get("endpoint")
517539

518540
@property
519541
def region(self) -> Optional[str]:
520542
"""Returns the region."""
543+
if not self.relation.app:
544+
return None
545+
521546
return self.relation.data[self.relation.app].get("region")
522547

523548
@property
524549
def s3_uri_style(self) -> Optional[str]:
525550
"""Returns the s3 uri style."""
551+
if not self.relation.app:
552+
return None
553+
526554
return self.relation.data[self.relation.app].get("s3-uri-style")
527555

528556
@property
529557
def storage_class(self) -> Optional[str]:
530558
"""Returns the storage class name."""
559+
if not self.relation.app:
560+
return None
561+
531562
return self.relation.data[self.relation.app].get("storage-class")
532563

533564
@property
534565
def tls_ca_chain(self) -> Optional[List[str]]:
535566
"""Returns the TLS CA chain."""
567+
if not self.relation.app:
568+
return None
569+
536570
tls_ca_chain = self.relation.data[self.relation.app].get("tls-ca-chain")
537571
if tls_ca_chain is not None:
538572
return json.loads(tls_ca_chain)
@@ -541,11 +575,17 @@ def tls_ca_chain(self) -> Optional[List[str]]:
541575
@property
542576
def s3_api_version(self) -> Optional[str]:
543577
"""Returns the S3 API version."""
578+
if not self.relation.app:
579+
return None
580+
544581
return self.relation.data[self.relation.app].get("s3-api-version")
545582

546583
@property
547584
def attributes(self) -> Optional[List[str]]:
548585
"""Returns the attributes."""
586+
if not self.relation.app:
587+
return None
588+
549589
attributes = self.relation.data[self.relation.app].get("attributes")
550590
if attributes is not None:
551591
return json.loads(attributes)
@@ -573,9 +613,11 @@ class S3CredentialRequiresEvents(ObjectEvents):
573613
class S3Requirer(Object):
574614
"""Requires-side of the s3 relation."""
575615

576-
on = S3CredentialRequiresEvents()
616+
on = S3CredentialRequiresEvents() # pyright: ignore[reportGeneralTypeIssues]
577617

578-
def __init__(self, charm: ops.charm.CharmBase, relation_name: str, bucket_name: str = None):
618+
def __init__(
619+
self, charm: ops.charm.CharmBase, relation_name: str, bucket_name: Optional[str] = None
620+
):
579621
"""Manager of the s3 client relations."""
580622
super().__init__(charm, relation_name)
581623

@@ -658,15 +700,15 @@ def update_connection_info(self, relation_id: int, connection_data: dict) -> Non
658700
relation.data[self.local_app].update(updated_connection_data)
659701
logger.debug(f"Updated S3 credentials: {updated_connection_data}")
660702

661-
def _load_relation_data(self, raw_relation_data: dict) -> dict:
703+
def _load_relation_data(self, raw_relation_data: RelationDataContent) -> Dict[str, str]:
662704
"""Loads relation data from the relation data bag.
663705
664706
Args:
665707
raw_relation_data: Relation data from the databag
666708
Returns:
667709
dict: Relation data in dict format.
668710
"""
669-
connection_data = dict()
711+
connection_data = {}
670712
for key in raw_relation_data:
671713
try:
672714
connection_data[key] = json.loads(raw_relation_data[key])
@@ -700,22 +742,25 @@ def _on_relation_changed(self, event: RelationChangedEvent) -> None:
700742
missing_options.append(configuration_option)
701743
# emit credential change event only if all mandatory fields are present
702744
if contains_required_options:
703-
self.on.credentials_changed.emit(event.relation, app=event.app, unit=event.unit)
745+
getattr(self.on, "credentials_changed").emit(
746+
event.relation, app=event.app, unit=event.unit
747+
)
704748
else:
705749
logger.warning(
706750
f"Some mandatory fields: {missing_options} are not present, do not emit credential change event!"
707751
)
708752

709-
def get_s3_connection_info(self) -> Dict:
753+
def get_s3_connection_info(self) -> Dict[str, str]:
710754
"""Return the s3 credentials as a dictionary."""
711-
relation = self.charm.model.get_relation(self.relation_name)
712-
if not relation:
713-
return {}
714-
return self._load_relation_data(relation.data[relation.app])
755+
for relation in self.relations:
756+
if relation and relation.app:
757+
return self._load_relation_data(relation.data[relation.app])
758+
759+
return {}
715760

716761
def _on_relation_broken(self, event: RelationBrokenEvent) -> None:
717762
"""Notify the charm about a broken S3 credential store relation."""
718-
self.on.credentials_gone.emit(event.relation, app=event.app, unit=event.unit)
763+
getattr(self.on, "credentials_gone").emit(event.relation, app=event.app, unit=event.unit)
719764

720765
@property
721766
def relations(self) -> List[Relation]:

lib/charms/data_platform_libs/v0/upgrade.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ def restart(self, event) -> None:
263263
import json
264264
import logging
265265
from abc import ABC, abstractmethod
266-
from typing import List, Literal, Optional, Set, Tuple
266+
from typing import Dict, List, Literal, Optional, Set, Tuple
267267

268268
import poetry.core.constraints.version as poetry_version
269269
from ops.charm import (
@@ -285,7 +285,7 @@ def restart(self, event) -> None:
285285

286286
# Increment this PATCH version before using `charmcraft publish-lib` or reset
287287
# to 0 if you are raising the major API version
288-
LIBPATCH = 13
288+
LIBPATCH = 15
289289

290290
PYDEPS = ["pydantic>=1.10,<2", "poetry-core"]
291291

@@ -346,7 +346,7 @@ class KafkaDependenciesModel(BaseModel):
346346
print(model.dict()) # exporting back validated deps
347347
"""
348348

349-
dependencies: dict[str, str]
349+
dependencies: Dict[str, str]
350350
name: str
351351
upgrade_supported: str
352352
version: str
@@ -895,6 +895,10 @@ def _on_upgrade_charm(self, event: UpgradeCharmEvent) -> None:
895895
self.charm.unit.status = WaitingStatus("other units upgrading first...")
896896
self.peer_relation.data[self.charm.unit].update({"state": "ready"})
897897

898+
if self.charm.app.planned_units() == 1:
899+
# single unit upgrade, emit upgrade_granted event right away
900+
getattr(self.on, "upgrade_granted").emit()
901+
898902
else:
899903
# for k8s run version checks only on highest ordinal unit
900904
if (

0 commit comments

Comments
 (0)