Refresh libraries for new publication points

Ensure that all libraries are sourced from k8s operators
rather than the sunbeam prototypes.

Drive-by fix for link to config.yaml in tests folder.

Change-Id: I32919b13eb5e974af10140113829146516acc6d8
This commit is contained in:
James Page 2022-10-17 13:27:26 +01:00
parent 61784f64b9
commit 2188416b10
5 changed files with 83 additions and 316 deletions

View File

@ -1,9 +1,8 @@
#!/bin/bash #!/bin/bash
echo "INFO: Fetching libs from charmhub." echo "INFO: Fetching libs from charmhub."
charmcraft fetch-lib charms.nginx_ingress_integrator.v0.ingress
charmcraft fetch-lib charms.data_platform_libs.v0.database_requires charmcraft fetch-lib charms.data_platform_libs.v0.database_requires
charmcraft fetch-lib charms.sunbeam_keystone_operator.v0.identity_service charmcraft fetch-lib charms.keystone_k8s.v0.identity_service
charmcraft fetch-lib charms.sunbeam_rabbitmq_operator.v0.amqp charmcraft fetch-lib charms.rabbitmq_k8s.v0.rabbitmq
charmcraft fetch-lib charms.observability_libs.v0.kubernetes_service_patch charmcraft fetch-lib charms.observability_libs.v0.kubernetes_service_patch
charmcraft fetch-lib charms.traefik_k8s.v1.ingress charmcraft fetch-lib charms.traefik_k8s.v1.ingress

View File

@ -90,7 +90,7 @@ from ops.model import Relation
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# The unique Charmhub library identifier, never change it # The unique Charmhub library identifier, never change it
LIBID = "6a7cb19b98314ecf916e3fcb02708608" LIBID = "0fa7fe7236c14c6e9624acf232b9a3b0"
# Increment this major API version when introducing breaking changes # Increment this major API version when introducing breaking changes
LIBAPI = 0 LIBAPI = 0
@ -329,7 +329,9 @@ class IdentityServiceRequires(Object):
if self.model.unit.is_leader(): if self.model.unit.is_leader():
logging.debug("Requesting service registration") logging.debug("Requesting service registration")
app_data = self._identity_service_rel.data[self.charm.app] app_data = self._identity_service_rel.data[self.charm.app]
app_data["service-endpoints"] = json.dumps(service_endpoints) app_data["service-endpoints"] = json.dumps(
service_endpoints, sort_keys=True
)
app_data["region"] = region app_data["region"] = region
@ -455,9 +457,13 @@ class IdentityServiceProvides(Object):
admin_auth_url: str, admin_auth_url: str,
public_auth_url: str): public_auth_url: str):
logging.debug("Setting identity_service connection information.") logging.debug("Setting identity_service connection information.")
_identity_service_rel = None
for relation in self.framework.model.relations[relation_name]: for relation in self.framework.model.relations[relation_name]:
if relation.id == relation_id: if relation.id == relation_id:
_identity_service_rel = relation _identity_service_rel = relation
if not _identity_service_rel:
# Relation has disappeared so skip send of data
return
app_data = _identity_service_rel.data[self.charm.app] app_data = _identity_service_rel.data[self.charm.app]
app_data["api-version"] = api_version app_data["api-version"] = api_version
app_data["auth-host"] = auth_host app_data["auth-host"] = auth_host

View File

@ -1,211 +0,0 @@
"""Library for the ingress relation.
This library contains the Requires and Provides classes for handling
the ingress interface.
Import `IngressRequires` in your charm, with two required options:
- "self" (the charm itself)
- config_dict
`config_dict` accepts the following keys:
- service-hostname (required)
- service-name (required)
- service-port (required)
- additional-hostnames
- limit-rps
- limit-whitelist
- max-body-size
- path-routes
- retry-errors
- rewrite-enabled
- rewrite-target
- service-namespace
- session-cookie-max-age
- tls-secret-name
See [the config section](https://charmhub.io/nginx-ingress-integrator/configure) for descriptions
of each, along with the required type.
As an example, add the following to `src/charm.py`:
```
from charms.nginx_ingress_integrator.v0.ingress import IngressRequires
# In your charm's `__init__` method.
self.ingress = IngressRequires(self, {"service-hostname": self.config["external_hostname"],
"service-name": self.app.name,
"service-port": 80})
# In your charm's `config-changed` handler.
self.ingress.update_config({"service-hostname": self.config["external_hostname"]})
```
And then add the following to `metadata.yaml`:
```
requires:
ingress:
interface: ingress
```
You _must_ register the IngressRequires class as part of the `__init__` method
rather than, for instance, a config-changed event handler. This is because
doing so won't get the current relation changed event, because it wasn't
registered to handle the event (because it wasn't created in `__init__` when
the event was fired).
"""
import logging
from ops.charm import CharmEvents
from ops.framework import EventBase, EventSource, Object
from ops.model import BlockedStatus
# The unique Charmhub library identifier, never change it
LIBID = "db0af4367506491c91663468fb5caa4c"
# Increment this major API version when introducing breaking changes
LIBAPI = 0
# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 9
logger = logging.getLogger(__name__)
REQUIRED_INGRESS_RELATION_FIELDS = {
"service-hostname",
"service-name",
"service-port",
}
OPTIONAL_INGRESS_RELATION_FIELDS = {
"additional-hostnames",
"limit-rps",
"limit-whitelist",
"max-body-size",
"retry-errors",
"rewrite-target",
"rewrite-enabled",
"service-namespace",
"session-cookie-max-age",
"tls-secret-name",
"path-routes",
}
class IngressAvailableEvent(EventBase):
pass
class IngressCharmEvents(CharmEvents):
"""Custom charm events."""
ingress_available = EventSource(IngressAvailableEvent)
class IngressRequires(Object):
"""This class defines the functionality for the 'requires' side of the 'ingress' relation.
Hook events observed:
- relation-changed
"""
def __init__(self, charm, config_dict):
super().__init__(charm, "ingress")
self.framework.observe(charm.on["ingress"].relation_changed, self._on_relation_changed)
self.config_dict = config_dict
def _config_dict_errors(self, update_only=False):
"""Check our config dict for errors."""
blocked_message = "Error in ingress relation, check `juju debug-log`"
unknown = [
x
for x in self.config_dict
if x not in REQUIRED_INGRESS_RELATION_FIELDS | OPTIONAL_INGRESS_RELATION_FIELDS
]
if unknown:
logger.error(
"Ingress relation error, unknown key(s) in config dictionary found: %s",
", ".join(unknown),
)
self.model.unit.status = BlockedStatus(blocked_message)
return True
if not update_only:
missing = [x for x in REQUIRED_INGRESS_RELATION_FIELDS if x not in self.config_dict]
if missing:
logger.error(
"Ingress relation error, missing required key(s) in config dictionary: %s",
", ".join(missing),
)
self.model.unit.status = BlockedStatus(blocked_message)
return True
return False
def _on_relation_changed(self, event):
"""Handle the relation-changed event."""
# `self.unit` isn't available here, so use `self.model.unit`.
if self.model.unit.is_leader():
if self._config_dict_errors():
return
for key in self.config_dict:
event.relation.data[self.model.app][key] = str(self.config_dict[key])
def update_config(self, config_dict):
"""Allow for updates to relation."""
if self.model.unit.is_leader():
self.config_dict = config_dict
if self._config_dict_errors(update_only=True):
return
relation = self.model.get_relation("ingress")
if relation:
for key in self.config_dict:
relation.data[self.model.app][key] = str(self.config_dict[key])
class IngressProvides(Object):
"""This class defines the functionality for the 'provides' side of the 'ingress' relation.
Hook events observed:
- relation-changed
"""
def __init__(self, charm):
super().__init__(charm, "ingress")
# Observe the relation-changed hook event and bind
# self.on_relation_changed() to handle the event.
self.framework.observe(charm.on["ingress"].relation_changed, self._on_relation_changed)
self.charm = charm
def _on_relation_changed(self, event):
"""Handle a change to the ingress relation.
Confirm we have the fields we expect to receive."""
# `self.unit` isn't available here, so use `self.model.unit`.
if not self.model.unit.is_leader():
return
ingress_data = {
field: event.relation.data[event.app].get(field)
for field in REQUIRED_INGRESS_RELATION_FIELDS | OPTIONAL_INGRESS_RELATION_FIELDS
}
missing_fields = sorted(
[
field
for field in REQUIRED_INGRESS_RELATION_FIELDS
if ingress_data.get(field) is None
]
)
if missing_fields:
logger.error(
"Missing required data fields for ingress relation: {}".format(
", ".join(missing_fields)
)
)
self.model.unit.status = BlockedStatus(
"Missing fields for ingress: {}".format(", ".join(missing_fields))
)
# Create an event that our charm can use to decide it's okay to
# configure the ingress.
self.charm.on.ingress_available.emit()

View File

@ -1,10 +1,9 @@
"""AMQPProvides and Requires module. """RabbitMQProvides and Requires module.
This library contains the Requires and Provides classes for handling This library contains the Requires and Provides classes for handling
the amqp interface. the rabbitmq interface.
Import `AMQPRequires` in your charm, with the charm object and the Import `RabbitMQRequires` in your charm, with the charm object and the
relation name: relation name:
- self - self
- "amqp" - "amqp"
@ -21,13 +20,13 @@ Two events are also available to respond to:
A basic example showing the usage of this relation follows: A basic example showing the usage of this relation follows:
``` ```
from charms.sunbeam_rabbitmq_operator.v0.amqp import AMQPRequires from charms.rabbitmq_k8s.v0.rabbitmq import RabbitMQRequires
class AMQPClientCharm(CharmBase): class RabbitMQClientCharm(CharmBase):
def __init__(self, *args): def __init__(self, *args):
super().__init__(*args) super().__init__(*args)
# AMQP Requires # RabbitMQ Requires
self.amqp = AMQPRequires( self.amqp = RabbitMQRequires(
self, "amqp", self, "amqp",
username="myusername", username="myusername",
vhost="vhostname" vhost="vhostname"
@ -40,45 +39,44 @@ class AMQPClientCharm(CharmBase):
self.amqp.on.goneaway, self._on_amqp_goneaway) self.amqp.on.goneaway, self._on_amqp_goneaway)
def _on_amqp_connected(self, event): def _on_amqp_connected(self, event):
'''React to the AMQP connected event. '''React to the RabbitMQ connected event.
This event happens when n AMQP relation is added to the This event happens when n RabbitMQ relation is added to the
model before credentials etc have been provided. model before credentials etc have been provided.
''' '''
# Do something before the relation is complete # Do something before the relation is complete
pass pass
def _on_amqp_ready(self, event): def _on_amqp_ready(self, event):
'''React to the AMQP ready event. '''React to the RabbitMQ ready event.
The AMQP interface will use the provided username and vhost for the The RabbitMQ interface will use the provided username and vhost for the
request to the rabbitmq server. request to the rabbitmq server.
''' '''
# AMQP Relation is ready. Do something with the completed relation. # RabbitMQ Relation is ready. Do something with the completed relation.
pass pass
def _on_amqp_goneaway(self, event): def _on_amqp_goneaway(self, event):
'''React to the AMQP goneaway event. '''React to the RabbitMQ goneaway event.
This event happens when an AMQP relation is removed. This event happens when an RabbitMQ relation is removed.
''' '''
# AMQP Relation has goneaway. shutdown services or suchlike # RabbitMQ Relation has goneaway. shutdown services or suchlike
pass pass
``` ```
""" """
# The unique Charmhub library identifier, never change it # The unique Charmhub library identifier, never change it
LIBID = "ab1414b6baf044f099caf9c117f1a101" LIBID = "45622352791142fd9cf87232e3bd6f2a"
# Increment this major API version when introducing breaking changes # Increment this major API version when introducing breaking changes
LIBAPI = 0 LIBAPI = 0
# Increment this PATCH version before using `charmcraft publish-lib` or reset # Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version # to 0 if you are raising the major API version
LIBPATCH = 3 LIBPATCH = 1
import logging import logging
import requests
from ops.framework import ( from ops.framework import (
StoredState, StoredState,
@ -95,39 +93,38 @@ from typing import List
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class AMQPConnectedEvent(EventBase): class RabbitMQConnectedEvent(EventBase):
"""AMQP connected Event.""" """RabbitMQ connected Event."""
pass pass
class AMQPReadyEvent(EventBase): class RabbitMQReadyEvent(EventBase):
"""AMQP ready for use Event.""" """RabbitMQ ready for use Event."""
pass pass
class AMQPGoneAwayEvent(EventBase): class RabbitMQGoneAwayEvent(EventBase):
"""AMQP relation has gone-away Event""" """RabbitMQ relation has gone-away Event"""
pass pass
class AMQPServerEvents(ObjectEvents): class RabbitMQServerEvents(ObjectEvents):
"""Events class for `on`""" """Events class for `on`"""
connected = EventSource(AMQPConnectedEvent) connected = EventSource(RabbitMQConnectedEvent)
ready = EventSource(AMQPReadyEvent) ready = EventSource(RabbitMQReadyEvent)
goneaway = EventSource(AMQPGoneAwayEvent) goneaway = EventSource(RabbitMQGoneAwayEvent)
class AMQPRequires(Object): class RabbitMQRequires(Object):
""" """
AMQPRequires class RabbitMQRequires class
""" """
on = AMQPServerEvents() on = RabbitMQServerEvents()
_stored = StoredState()
def __init__(self, charm, relation_name: str, username: str, vhost: str): def __init__(self, charm, relation_name: str, username: str, vhost: str):
super().__init__(charm, relation_name) super().__init__(charm, relation_name)
@ -153,94 +150,94 @@ class AMQPRequires(Object):
) )
def _on_amqp_relation_joined(self, event): def _on_amqp_relation_joined(self, event):
"""AMQP relation joined.""" """RabbitMQ relation joined."""
logging.debug("RabbitMQAMQPRequires on_joined") logging.debug("RabbitMQRabbitMQRequires on_joined")
self.on.connected.emit() self.on.connected.emit()
self.request_access(self.username, self.vhost) self.request_access(self.username, self.vhost)
def _on_amqp_relation_changed(self, event): def _on_amqp_relation_changed(self, event):
"""AMQP relation changed.""" """RabbitMQ relation changed."""
logging.debug("RabbitMQAMQPRequires on_changed") logging.debug("RabbitMQRabbitMQRequires on_changed/departed")
if self.password: if self.password:
self.on.ready.emit() self.on.ready.emit()
def _on_amqp_relation_broken(self, event): def _on_amqp_relation_broken(self, event):
"""AMQP relation broken.""" """RabbitMQ relation broken."""
logging.debug("RabbitMQAMQPRequires on_broken") logging.debug("RabbitMQRabbitMQRequires on_broken")
self.on.goneaway.emit() self.on.goneaway.emit()
@property @property
def _amqp_rel(self) -> Relation: def _amqp_rel(self) -> Relation:
"""The AMQP relation.""" """The RabbitMQ relation."""
return self.framework.model.get_relation(self.relation_name) return self.framework.model.get_relation(self.relation_name)
@property @property
def password(self) -> str: def password(self) -> str:
"""Return the AMQP password from the server side of the relation.""" """Return the RabbitMQ password from the server side of the relation."""
return self._amqp_rel.data[self._amqp_rel.app].get("password") return self._amqp_rel.data[self._amqp_rel.app].get("password")
@property @property
def hostname(self) -> str: def hostname(self) -> str:
"""Return the hostname from the AMQP relation""" """Return the hostname from the RabbitMQ relation"""
return self._amqp_rel.data[self._amqp_rel.app].get("hostname") return self._amqp_rel.data[self._amqp_rel.app].get("hostname")
@property @property
def ssl_port(self) -> str: def ssl_port(self) -> str:
"""Return the SSL port from the AMQP relation""" """Return the SSL port from the RabbitMQ relation"""
return self._amqp_rel.data[self._amqp_rel.app].get("ssl_port") return self._amqp_rel.data[self._amqp_rel.app].get("ssl_port")
@property @property
def ssl_ca(self) -> str: def ssl_ca(self) -> str:
"""Return the SSL port from the AMQP relation""" """Return the SSL port from the RabbitMQ relation"""
return self._amqp_rel.data[self._amqp_rel.app].get("ssl_ca") return self._amqp_rel.data[self._amqp_rel.app].get("ssl_ca")
@property @property
def hostnames(self) -> List[str]: def hostnames(self) -> List[str]:
"""Return a list of remote RMQ hosts from the AMQP relation""" """Return a list of remote RMQ hosts from the RabbitMQ relation"""
_hosts = [] _hosts = []
for unit in self._amqp_rel.units: for unit in self._amqp_rel.units:
_hosts.append(self._amqp_rel.data[unit].get("ingress-address")) _hosts.append(self._amqp_rel.data[unit].get("ingress-address"))
return _hosts return _hosts
def request_access(self, username: str, vhost: str) -> None: def request_access(self, username: str, vhost: str) -> None:
"""Request access to the AMQP server.""" """Request access to the RabbitMQ server."""
if self.model.unit.is_leader(): if self.model.unit.is_leader():
logging.debug("Requesting AMQP user and vhost") logging.debug("Requesting RabbitMQ user and vhost")
self._amqp_rel.data[self.charm.app]["username"] = username self._amqp_rel.data[self.charm.app]["username"] = username
self._amqp_rel.data[self.charm.app]["vhost"] = vhost self._amqp_rel.data[self.charm.app]["vhost"] = vhost
class HasAMQPClientsEvent(EventBase): class HasRabbitMQClientsEvent(EventBase):
"""Has AMQPClients Event.""" """Has RabbitMQClients Event."""
pass pass
class ReadyAMQPClientsEvent(EventBase): class ReadyRabbitMQClientsEvent(EventBase):
"""AMQPClients Ready Event.""" """RabbitMQClients Ready Event."""
pass pass
class AMQPClientEvents(ObjectEvents): class RabbitMQClientEvents(ObjectEvents):
"""Events class for `on`""" """Events class for `on`"""
has_amqp_clients = EventSource(HasAMQPClientsEvent) has_amqp_clients = EventSource(HasRabbitMQClientsEvent)
ready_amqp_clients = EventSource(ReadyAMQPClientsEvent) ready_amqp_clients = EventSource(ReadyRabbitMQClientsEvent)
class AMQPProvides(Object): class RabbitMQProvides(Object):
""" """
AMQPProvides class RabbitMQProvides class
""" """
on = AMQPClientEvents() on = RabbitMQClientEvents()
_stored = StoredState()
def __init__(self, charm, relation_name): def __init__(self, charm, relation_name, callback):
super().__init__(charm, relation_name) super().__init__(charm, relation_name)
self.charm = charm self.charm = charm
self.relation_name = relation_name self.relation_name = relation_name
self.callback = callback
self.framework.observe( self.framework.observe(
self.charm.on[relation_name].relation_joined, self.charm.on[relation_name].relation_joined,
self._on_amqp_relation_joined, self._on_amqp_relation_joined,
@ -255,60 +252,35 @@ class AMQPProvides(Object):
) )
def _on_amqp_relation_joined(self, event): def _on_amqp_relation_joined(self, event):
"""Handle AMQP joined.""" """Handle RabbitMQ joined."""
logging.debug("RabbitMQAMQPProvides on_joined") logging.debug("RabbitMQRabbitMQProvides on_joined data={}"
.format(event.relation.data[event.relation.app]))
self.on.has_amqp_clients.emit() self.on.has_amqp_clients.emit()
def _on_amqp_relation_changed(self, event): def _on_amqp_relation_changed(self, event):
"""Handle AMQP changed.""" """Handle RabbitMQ changed."""
logging.debug("RabbitMQAMQPProvides on_changed") logging.debug("RabbitMQRabbitMQProvides on_changed data={}"
.format(event.relation.data[event.relation.app]))
# Validate data on the relation # Validate data on the relation
if self.username(event) and self.vhost(event): if self.username(event) and self.vhost(event):
self.on.ready_amqp_clients.emit() self.on.ready_amqp_clients.emit()
if self.charm.unit.is_leader(): if self.charm.unit.is_leader():
self.set_amqp_credentials( self.callback(event, self.username(event), self.vhost(event))
event, self.username(event), self.vhost(event) else:
) logging.warning("Received RabbitMQ changed event without the "
"expected keys ('username', 'vhost') in the "
"application data bag. Incompatible charm in "
"other end of relation?")
def _on_amqp_relation_broken(self, event): def _on_amqp_relation_broken(self, event):
"""Handle AMQP broken.""" """Handle RabbitMQ broken."""
logging.debug("RabbitMQAMQPProvides on_departed") logging.debug("RabbitMQRabbitMQProvides on_departed")
# TODO clear data on the relation # TODO clear data on the relation
def username(self, event): def username(self, event):
"""Return the AMQP username from the client side of the relation.""" """Return the RabbitMQ username from the client side of the relation."""
return event.relation.data[event.relation.app].get("username") return event.relation.data[event.relation.app].get("username")
def vhost(self, event): def vhost(self, event):
"""Return the AMQP vhost from the client side of the relation.""" """Return the RabbitMQ vhost from the client side of the relation."""
return event.relation.data[event.relation.app].get("vhost") return event.relation.data[event.relation.app].get("vhost")
def set_amqp_credentials(self, event, username, vhost):
"""Set AMQP Credentials.
:param event: The current event
:type EventsBase
:param username: The requested username
:type username: str
:param vhost: The requested vhost
:type vhost: str
:returns: None
:rtype: None
"""
# TODO: Can we move this into the charm code?
# TODO TLS Support. Existing interfaces set ssl_port and ssl_ca
logging.debug("Setting amqp connection information.")
try:
if not self.charm.does_vhost_exist(vhost):
self.charm.create_vhost(vhost)
password = self.charm.create_user(username)
self.charm.set_user_permissions(username, vhost)
event.relation.data[self.charm.app]["password"] = password
event.relation.data[self.charm.app][
"hostname"
] = self.charm.hostname
except requests.exceptions.ConnectionError as e:
logging.warning(
"Rabbitmq is not ready. Defering. Errno: {}".format(e.errno)
)
event.defer()

View File

@ -0,0 +1 @@
../config.yaml