--- /dev/null
+# 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