|
4 | 4 | """Library for the certificate_transfer relation.
|
5 | 5 |
|
6 | 6 | This library contains the Requires and Provides classes for handling the
|
7 |
| -certificate-transfer interface. |
| 7 | +certificate-transfer interface. It supports both v0 and v1 of the interface. |
| 8 | +
|
| 9 | +For requirers, they will set version 1 in their application databag as a hint to |
| 10 | +the provider. They will read the databag from the provider first as v1, and fallback |
| 11 | +to v0 if the format does not match. |
| 12 | +
|
| 13 | +For providers, they will check the version in the requirer's application databag, |
| 14 | +and send v1 if that version is set to 1, otherwise it will default to 0 for backwards |
| 15 | +compatibility. |
8 | 16 |
|
9 | 17 | ## Getting Started
|
10 | 18 | From a charm directory, fetch the library using `charmcraft`:
|
@@ -116,7 +124,7 @@ def _on_certificates_removed(self, event: CertificatesRemovedEvent):
|
116 | 124 |
|
117 | 125 | # Increment this PATCH version before using `charmcraft publish-lib` or reset
|
118 | 126 | # to 0 if you are raising the major API version
|
119 |
| -LIBPATCH = 7 |
| 127 | +LIBPATCH = 10 |
120 | 128 |
|
121 | 129 | logger = logging.getLogger(__name__)
|
122 | 130 |
|
@@ -283,6 +291,24 @@ class ProviderApplicationData(DatabagModel):
|
283 | 291 | )
|
284 | 292 |
|
285 | 293 |
|
| 294 | +class _Certificate(pydantic.BaseModel): |
| 295 | + """Certificate model.""" |
| 296 | + |
| 297 | + ca: str |
| 298 | + certificate: str |
| 299 | + chain: Optional[List[str]] = None |
| 300 | + version: int = pydantic.Field( |
| 301 | + description="Version of the interface used in this databag", |
| 302 | + default=0, |
| 303 | + ) |
| 304 | + |
| 305 | + |
| 306 | +class ProviderUnitDataV0(DatabagModel): |
| 307 | + """Provider Unit databag v0 model.""" |
| 308 | + |
| 309 | + certificates: List[_Certificate] = [] |
| 310 | + |
| 311 | + |
286 | 312 | class RequirerApplicationData(DatabagModel):
|
287 | 313 | """Requirer App databag model."""
|
288 | 314 |
|
@@ -399,14 +425,45 @@ def _get_relevant_relations(self, relation_id: Optional[int] = None) -> List[Rel
|
399 | 425 |
|
400 | 426 | def _set_relation_data(self, relation: Relation, data: Set[str]) -> None:
|
401 | 427 | """Set the given relation data."""
|
402 |
| - databag = relation.data[self.model.app] |
403 |
| - ProviderApplicationData(certificates=data).dump(databag, False) |
| 428 | + if relation.data.get(relation.app, {}).get("version", "0") == "1": |
| 429 | + databag = relation.data[self.model.app] |
| 430 | + ProviderApplicationData(certificates=data).dump(databag, True) |
| 431 | + else: |
| 432 | + if "version" in relation.data.get(relation.app, {}): |
| 433 | + logger.warning( |
| 434 | + ( |
| 435 | + "Requirer in relation %d is using version %s of the interface,", |
| 436 | + "defaulting to version 0.", |
| 437 | + "This is deprecated, please consider upgrading the requirer", |
| 438 | + "to version 1 of the library.", |
| 439 | + ), |
| 440 | + relation.id, |
| 441 | + relation.data[relation.app]["version"], |
| 442 | + ) |
| 443 | + else: |
| 444 | + logger.warning( |
| 445 | + ( |
| 446 | + "Requirer in relation %d did not provide version field,", |
| 447 | + "defaulting to version 0.", |
| 448 | + "This is deprecated, please consider upgrading the requirer", |
| 449 | + "to version 1 of the library.", |
| 450 | + ), |
| 451 | + relation.id, |
| 452 | + ) |
| 453 | + |
| 454 | + databag = relation.data[self.model.unit] |
| 455 | + certificates = [_Certificate(ca=cert, certificate=cert, chain=[cert]) for cert in data] |
| 456 | + ProviderUnitDataV0(certificates=certificates).dump(databag, True) |
404 | 457 |
|
405 | 458 | def _get_relation_data(self, relation: Relation) -> Set[str]:
|
406 | 459 | """Get the given relation data."""
|
407 |
| - databag = relation.data[self.model.app] |
408 | 460 | try:
|
409 |
| - return ProviderApplicationData().load(databag).certificates |
| 461 | + if relation.data.get(relation.app, {}).get("version", "0") == "1": |
| 462 | + databag = relation.data[self.model.app] |
| 463 | + return ProviderApplicationData().load(databag).certificates |
| 464 | + else: |
| 465 | + databag = relation.data[self.model.unit] |
| 466 | + return {cert.ca for cert in ProviderUnitDataV0().load(databag).certificates} |
410 | 467 | except DataValidationError as e:
|
411 | 468 | logger.error(
|
412 | 469 | (
|
@@ -566,9 +623,13 @@ def is_ready(self, relation: Relation) -> bool:
|
566 | 623 |
|
567 | 624 | def _get_relation_data(self, relation: Relation) -> Set[str]:
|
568 | 625 | """Get the given relation data."""
|
569 |
| - databag = relation.data[relation.app] |
570 | 626 | try:
|
571 |
| - return ProviderApplicationData().load(databag).certificates |
| 627 | + databag = relation.data[relation.app] |
| 628 | + certificates = ProviderApplicationData().load(databag).certificates |
| 629 | + if not certificates and relation.units: |
| 630 | + databag = relation.data.get(relation.units.pop(), {}) |
| 631 | + return {cert.ca for cert in ProviderUnitDataV0().load(databag).certificates} |
| 632 | + return certificates |
572 | 633 | except DataValidationError as e:
|
573 | 634 | logger.error(
|
574 | 635 | (
|
|
0 commit comments