| # Copyright 2021 Canonical Ltd. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may |
| # not use this file except in compliance with the License. You may obtain |
| # a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations |
| # under the License. |
| # |
| # For those usages not covered by the Apache License, Version 2.0 please |
| # contact: legal@canonical.com |
| # |
| # To get in touch with the maintainers, please contact: |
| # osm-charmers@lists.launchpad.net |
| # |
| # |
| # This file populates the Actions tab on Charmhub. |
| # See https://juju.is/docs/some-url-to-be-determined/ for a checklist and guidance. |
| |
| """Keystone cluster library. |
| |
| This library allows the integration with Apache Guacd charm. Is is published as part of the |
| [davigar15-apache-guacd]((https://charmhub.io/davigar15-apache-guacd) charm. |
| |
| The charm that requires guacd should include the following content in its metadata.yaml: |
| |
| ```yaml |
| # ... |
| peers: |
| cluster: |
| interface: cluster |
| # ... |
| ``` |
| |
| A typical example of including this library might be: |
| |
| ```python |
| # ... |
| from ops.framework import StoredState |
| from charms.keystone.v0 import cluster |
| |
| class SomeApplication(CharmBase): |
| on = cluster.ClusterEvents() |
| |
| def __init__(self, *args): |
| # ... |
| self.cluster = cluster.Cluster(self) |
| self.framework.observe(self.on.cluster_keys_changed, self._cluster_keys_changed) |
| # ... |
| |
| def _cluster_keys_changed(self, _): |
| fernet_keys = self.cluster.fernet_keys |
| credential_keys = self.cluster.credential_keys |
| # ... |
| ``` |
| """ |
| |
| |
| import json |
| import logging |
| from typing import Any, Dict, List |
| |
| from ops.charm import CharmEvents |
| from ops.framework import EventBase, EventSource, Object |
| from ops.model import Relation |
| |
| # Number of keys need might need to be adjusted in the future |
| NUMBER_FERNET_KEYS = 2 |
| NUMBER_CREDENTIAL_KEYS = 2 |
| |
| logger = logging.getLogger(__name__) |
| |
| |
| class ClusterKeysChangedEvent(EventBase): |
| """Event to announce a change in the Guacd service.""" |
| |
| |
| class ClusterEvents(CharmEvents): |
| """Cluster Events.""" |
| |
| cluster_keys_changed = EventSource(ClusterKeysChangedEvent) |
| |
| |
| class Cluster(Object): |
| """Peer relation.""" |
| |
| def __init__(self, charm): |
| super().__init__(charm, "cluster") |
| self.charm = charm |
| |
| @property |
| def fernet_keys(self) -> List[str]: |
| """Fernet keys.""" |
| relation: Relation = self.model.get_relation("cluster") |
| application_data = relation.data[self.model.app] |
| return json.loads(application_data.get("keys-fernet", "[]")) |
| |
| @property |
| def credential_keys(self) -> List[str]: |
| """Credential keys.""" |
| relation: Relation = self.model.get_relation("cluster") |
| application_data = relation.data[self.model.app] |
| return json.loads(application_data.get("keys-credential", "[]")) |
| |
| def save_keys(self, keys: Dict[str, Any]) -> None: |
| """Generate fernet and credential keys. |
| |
| This method will generate new keys and fire the cluster_keys_changed event. |
| """ |
| logger.debug("Saving keys...") |
| relation: Relation = self.model.get_relation("cluster") |
| data = relation.data[self.model.app] |
| current_keys_str = data.get("key_repository", "{}") |
| current_keys = json.loads(current_keys_str) |
| if current_keys != keys: |
| data["key_repository"] = json.dumps(keys) |
| self.charm.on.cluster_keys_changed.emit() |
| logger.info("Keys saved!") |
| |
| def get_keys(self) -> Dict[str, Any]: |
| """Get keys from the relation. |
| |
| Returns: |
| Dict[str, Any]: Dictionary with the keys. |
| """ |
| relation: Relation = self.model.get_relation("cluster") |
| data = relation.data[self.model.app] |
| current_keys_str = data.get("key_repository", "{}") |
| current_keys = json.loads(current_keys_str) |
| return current_keys |