| Patricia Reinoso | d5b463c | 2023-05-31 08:37:18 +0000 | [diff] [blame] | 1 | # Copyright 2021 Canonical Ltd. |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may |
| 4 | # not use this file except in compliance with the License. You may obtain |
| 5 | # a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 12 | # License for the specific language governing permissions and limitations |
| 13 | # under the License. |
| 14 | # |
| 15 | # For those usages not covered by the Apache License, Version 2.0 please |
| 16 | # contact: legal@canonical.com |
| 17 | # |
| 18 | # To get in touch with the maintainers, please contact: |
| 19 | # osm-charmers@lists.launchpad.net |
| 20 | # |
| 21 | # |
| 22 | # This file populates the Actions tab on Charmhub. |
| 23 | # See https://juju.is/docs/some-url-to-be-determined/ for a checklist and guidance. |
| 24 | |
| 25 | """Keystone cluster library. |
| 26 | |
| 27 | This library allows the integration with Apache Guacd charm. Is is published as part of the |
| 28 | [davigar15-apache-guacd]((https://charmhub.io/davigar15-apache-guacd) charm. |
| 29 | |
| 30 | The charm that requires guacd should include the following content in its metadata.yaml: |
| 31 | |
| 32 | ```yaml |
| 33 | # ... |
| 34 | peers: |
| 35 | cluster: |
| 36 | interface: cluster |
| 37 | # ... |
| 38 | ``` |
| 39 | |
| 40 | A typical example of including this library might be: |
| 41 | |
| 42 | ```python |
| 43 | # ... |
| 44 | from ops.framework import StoredState |
| 45 | from charms.keystone.v0 import cluster |
| 46 | |
| 47 | class SomeApplication(CharmBase): |
| 48 | on = cluster.ClusterEvents() |
| 49 | |
| 50 | def __init__(self, *args): |
| 51 | # ... |
| 52 | self.cluster = cluster.Cluster(self) |
| 53 | self.framework.observe(self.on.cluster_keys_changed, self._cluster_keys_changed) |
| 54 | # ... |
| 55 | |
| 56 | def _cluster_keys_changed(self, _): |
| 57 | fernet_keys = self.cluster.fernet_keys |
| 58 | credential_keys = self.cluster.credential_keys |
| 59 | # ... |
| 60 | ``` |
| 61 | """ |
| 62 | |
| 63 | |
| 64 | import json |
| 65 | import logging |
| 66 | from typing import Any, Dict, List |
| 67 | |
| 68 | from ops.charm import CharmEvents |
| 69 | from ops.framework import EventBase, EventSource, Object |
| 70 | from ops.model import Relation |
| 71 | |
| 72 | # Number of keys need might need to be adjusted in the future |
| 73 | NUMBER_FERNET_KEYS = 2 |
| 74 | NUMBER_CREDENTIAL_KEYS = 2 |
| 75 | |
| 76 | logger = logging.getLogger(__name__) |
| 77 | |
| 78 | |
| 79 | class ClusterKeysChangedEvent(EventBase): |
| 80 | """Event to announce a change in the Guacd service.""" |
| 81 | |
| 82 | |
| 83 | class ClusterEvents(CharmEvents): |
| 84 | """Cluster Events.""" |
| 85 | |
| 86 | cluster_keys_changed = EventSource(ClusterKeysChangedEvent) |
| 87 | |
| 88 | |
| 89 | class Cluster(Object): |
| 90 | """Peer relation.""" |
| 91 | |
| 92 | def __init__(self, charm): |
| 93 | super().__init__(charm, "cluster") |
| 94 | self.charm = charm |
| 95 | |
| 96 | @property |
| 97 | def fernet_keys(self) -> List[str]: |
| 98 | """Fernet keys.""" |
| 99 | relation: Relation = self.model.get_relation("cluster") |
| 100 | application_data = relation.data[self.model.app] |
| 101 | return json.loads(application_data.get("keys-fernet", "[]")) |
| 102 | |
| 103 | @property |
| 104 | def credential_keys(self) -> List[str]: |
| 105 | """Credential keys.""" |
| 106 | relation: Relation = self.model.get_relation("cluster") |
| 107 | application_data = relation.data[self.model.app] |
| 108 | return json.loads(application_data.get("keys-credential", "[]")) |
| 109 | |
| 110 | def save_keys(self, keys: Dict[str, Any]) -> None: |
| 111 | """Generate fernet and credential keys. |
| 112 | |
| 113 | This method will generate new keys and fire the cluster_keys_changed event. |
| 114 | """ |
| 115 | logger.debug("Saving keys...") |
| 116 | relation: Relation = self.model.get_relation("cluster") |
| 117 | data = relation.data[self.model.app] |
| 118 | current_keys_str = data.get("key_repository", "{}") |
| 119 | current_keys = json.loads(current_keys_str) |
| 120 | if current_keys != keys: |
| 121 | data["key_repository"] = json.dumps(keys) |
| 122 | self.charm.on.cluster_keys_changed.emit() |
| 123 | logger.info("Keys saved!") |
| 124 | |
| 125 | def get_keys(self) -> Dict[str, Any]: |
| 126 | """Get keys from the relation. |
| 127 | |
| 128 | Returns: |
| 129 | Dict[str, Any]: Dictionary with the keys. |
| 130 | """ |
| 131 | relation: Relation = self.model.get_relation("cluster") |
| 132 | data = relation.data[self.model.app] |
| 133 | current_keys_str = data.get("key_repository", "{}") |
| 134 | current_keys = json.loads(current_keys_str) |
| 135 | return current_keys |