Upgrade external libraries
Change-Id: I8e06d63b8eb895f88c86f1f9b3896ce87f22932a Signed-off-by: Guillaume Boutry <guillaume.boutry@canonical.com>
This commit is contained in:
parent
16a65cf4e4
commit
6eb7f3b72b
@ -331,7 +331,7 @@ LIBAPI = 0
|
||||
|
||||
# Increment this PATCH version before using `charmcraft publish-lib` or reset
|
||||
# to 0 if you are raising the major API version
|
||||
LIBPATCH = 37
|
||||
LIBPATCH = 38
|
||||
|
||||
PYDEPS = ["ops>=2.0.0"]
|
||||
|
||||
@ -2606,6 +2606,14 @@ class DatabaseProviderData(ProviderData):
|
||||
"""
|
||||
self.update_relation_data(relation_id, {"version": version})
|
||||
|
||||
def set_subordinated(self, relation_id: int) -> None:
|
||||
"""Raises the subordinated flag in the application relation databag.
|
||||
|
||||
Args:
|
||||
relation_id: the identifier for a particular relation.
|
||||
"""
|
||||
self.update_relation_data(relation_id, {"subordinated": "true"})
|
||||
|
||||
|
||||
class DatabaseProviderEventHandlers(EventHandlers):
|
||||
"""Provider-side of the database relation handlers."""
|
||||
@ -2842,6 +2850,21 @@ class DatabaseRequirerEventHandlers(RequirerEventHandlers):
|
||||
|
||||
def _on_relation_changed_event(self, event: RelationChangedEvent) -> None:
|
||||
"""Event emitted when the database relation has changed."""
|
||||
is_subordinate = False
|
||||
remote_unit_data = None
|
||||
for key in event.relation.data.keys():
|
||||
if isinstance(key, Unit) and not key.name.startswith(self.charm.app.name):
|
||||
remote_unit_data = event.relation.data[key]
|
||||
elif isinstance(key, Application) and key.name != self.charm.app.name:
|
||||
is_subordinate = event.relation.data[key].get("subordinated") == "true"
|
||||
|
||||
if is_subordinate:
|
||||
if not remote_unit_data:
|
||||
return
|
||||
|
||||
if remote_unit_data.get("state") != "ready":
|
||||
return
|
||||
|
||||
# Check which data has changed to emit customs events.
|
||||
diff = self._diff(event)
|
||||
|
||||
|
@ -219,7 +219,7 @@ LIBAPI = 0
|
||||
# Increment this PATCH version before using `charmcraft publish-lib` or reset
|
||||
# to 0 if you are raising the major API version
|
||||
|
||||
LIBPATCH = 35
|
||||
LIBPATCH = 36
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -1050,6 +1050,7 @@ class GrafanaDashboardProvider(Object):
|
||||
|
||||
self.framework.observe(self._charm.on.leader_elected, self._update_all_dashboards_from_dir)
|
||||
self.framework.observe(self._charm.on.upgrade_charm, self._update_all_dashboards_from_dir)
|
||||
self.framework.observe(self._charm.on.config_changed, self._update_all_dashboards_from_dir)
|
||||
|
||||
self.framework.observe(
|
||||
self._charm.on[self._relation_name].relation_created,
|
||||
|
@ -6,7 +6,7 @@
|
||||
This library is designed to enable developers to more simply patch the Kubernetes Service created
|
||||
by Juju during the deployment of a sidecar charm. When sidecar charms are deployed, Juju creates a
|
||||
service named after the application in the namespace (named after the Juju model). This service by
|
||||
default contains a "placeholder" port, which is 65536/TCP.
|
||||
default contains a "placeholder" port, which is 65535/TCP.
|
||||
|
||||
When modifying the default set of resources managed by Juju, one must consider the lifecycle of the
|
||||
charm. In this case, any modifications to the default service (created during deployment), will be
|
||||
@ -109,6 +109,26 @@ class SomeCharm(CharmBase):
|
||||
# ...
|
||||
```
|
||||
|
||||
Creating a new k8s lb service instead of patching the one created by juju
|
||||
Service name is optional. If not provided, it defaults to {app_name}-lb.
|
||||
If provided and equal to app_name, it also defaults to {app_name}-lb to prevent conflicts with the Juju default service.
|
||||
```python
|
||||
from charms.observability_libs.v1.kubernetes_service_patch import KubernetesServicePatch
|
||||
from lightkube.models.core_v1 import ServicePort
|
||||
|
||||
class SomeCharm(CharmBase):
|
||||
def __init__(self, *args):
|
||||
# ...
|
||||
port = ServicePort(int(self.config["charm-config-port"]), name=f"{self.app.name}")
|
||||
self.service_patcher = KubernetesServicePatch(
|
||||
self,
|
||||
[port],
|
||||
service_type="LoadBalancer",
|
||||
service_name="application-lb"
|
||||
)
|
||||
# ...
|
||||
```
|
||||
|
||||
Additionally, you may wish to use mocks in your charm's unit testing to ensure that the library
|
||||
does not try to make any API calls, or open any files during testing that are unlikely to be
|
||||
present, and could break your tests. The easiest way to do this is during your test `setUp`:
|
||||
@ -146,7 +166,7 @@ LIBAPI = 1
|
||||
|
||||
# Increment this PATCH version before using `charmcraft publish-lib` or reset
|
||||
# to 0 if you are raising the major API version
|
||||
LIBPATCH = 9
|
||||
LIBPATCH = 11
|
||||
|
||||
ServiceType = Literal["ClusterIP", "LoadBalancer"]
|
||||
|
||||
@ -186,10 +206,15 @@ class KubernetesServicePatch(Object):
|
||||
"""
|
||||
super().__init__(charm, "kubernetes-service-patch")
|
||||
self.charm = charm
|
||||
self.service_name = service_name if service_name else self._app
|
||||
self.service_name = service_name or self._app
|
||||
# To avoid conflicts with the default Juju service, append "-lb" to the service name.
|
||||
# The Juju application name is retained for the default service created by Juju.
|
||||
if self.service_name == self._app and service_type == "LoadBalancer":
|
||||
self.service_name = f"{self._app}-lb"
|
||||
self.service_type = service_type
|
||||
self.service = self._service_object(
|
||||
ports,
|
||||
service_name,
|
||||
self.service_name,
|
||||
service_type,
|
||||
additional_labels,
|
||||
additional_selectors,
|
||||
@ -202,6 +227,7 @@ class KubernetesServicePatch(Object):
|
||||
self.framework.observe(charm.on.install, self._patch)
|
||||
self.framework.observe(charm.on.upgrade_charm, self._patch)
|
||||
self.framework.observe(charm.on.update_status, self._patch)
|
||||
self.framework.observe(charm.on.stop, self._remove_service)
|
||||
|
||||
# apply user defined events
|
||||
if refresh_event:
|
||||
@ -277,7 +303,10 @@ class KubernetesServicePatch(Object):
|
||||
if self._is_patched(client):
|
||||
return
|
||||
if self.service_name != self._app:
|
||||
self._delete_and_create_service(client)
|
||||
if not self.service_type == "LoadBalancer":
|
||||
self._delete_and_create_service(client)
|
||||
else:
|
||||
self._create_lb_service(client)
|
||||
client.patch(Service, self.service_name, self.service, patch_type=PatchType.MERGE)
|
||||
except ApiError as e:
|
||||
if e.status.code == 403:
|
||||
@ -294,6 +323,12 @@ class KubernetesServicePatch(Object):
|
||||
client.delete(Service, self._app, namespace=self._namespace)
|
||||
client.create(service)
|
||||
|
||||
def _create_lb_service(self, client: Client):
|
||||
try:
|
||||
client.get(Service, self.service_name, namespace=self._namespace)
|
||||
except ApiError:
|
||||
client.create(self.service)
|
||||
|
||||
def is_patched(self) -> bool:
|
||||
"""Reports if the service patch has been applied.
|
||||
|
||||
@ -321,6 +356,30 @@ class KubernetesServicePatch(Object):
|
||||
] # noqa: E501
|
||||
return expected_ports == fetched_ports
|
||||
|
||||
def _remove_service(self, _):
|
||||
"""Remove a Kubernetes service associated with this charm.
|
||||
|
||||
Specifically designed to delete the load balancer service created by the charm, since Juju only deletes the
|
||||
default ClusterIP service and not custom services.
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
Raises:
|
||||
ApiError: for deletion errors, excluding when the service is not found (404 Not Found).
|
||||
"""
|
||||
client = Client() # pyright: ignore
|
||||
|
||||
try:
|
||||
client.delete(Service, self.service_name, namespace=self._namespace)
|
||||
except ApiError as e:
|
||||
if e.status.code == 404:
|
||||
# Service not found, so no action needed
|
||||
pass
|
||||
else:
|
||||
# Re-raise for other statuses
|
||||
raise
|
||||
|
||||
@property
|
||||
def _app(self) -> str:
|
||||
"""Name of the current Juju application.
|
||||
|
@ -83,7 +83,7 @@ LIBAPI = 2
|
||||
|
||||
# Increment this PATCH version before using `charmcraft publish-lib` or reset
|
||||
# to 0 if you are raising the major API version
|
||||
LIBPATCH = 5
|
||||
LIBPATCH = 7
|
||||
|
||||
|
||||
# Regex to locate 7-bit C1 ANSI sequences
|
||||
@ -319,7 +319,10 @@ class Snap(object):
|
||||
Default is to return a string.
|
||||
"""
|
||||
if typed:
|
||||
config = json.loads(self._snap("get", ["-d", key]))
|
||||
args = ["-d"]
|
||||
if key:
|
||||
args.append(key)
|
||||
config = json.loads(self._snap("get", args))
|
||||
if key:
|
||||
return config.get(key)
|
||||
return config
|
||||
@ -584,13 +587,16 @@ class Snap(object):
|
||||
"Installing snap %s, revision %s, tracking %s", self._name, revision, channel
|
||||
)
|
||||
self._install(channel, cohort, revision)
|
||||
else:
|
||||
logger.info("The snap installation completed successfully")
|
||||
elif revision is None or revision != self._revision:
|
||||
# The snap is installed, but we are changing it (e.g., switching channels).
|
||||
logger.info(
|
||||
"Refreshing snap %s, revision %s, tracking %s", self._name, revision, channel
|
||||
)
|
||||
self._refresh(channel=channel, cohort=cohort, revision=revision, devmode=devmode)
|
||||
logger.info("The snap installation completed successfully")
|
||||
logger.info("The snap refresh completed successfully")
|
||||
else:
|
||||
logger.info("Refresh of snap %s was unnecessary", self._name)
|
||||
|
||||
self._update_snap_apps()
|
||||
self._state = state
|
||||
|
@ -178,7 +178,7 @@ configure the following scrape-related settings, which behave as described by th
|
||||
- `scrape_timeout`
|
||||
- `proxy_url`
|
||||
- `relabel_configs`
|
||||
- `metrics_relabel_configs`
|
||||
- `metric_relabel_configs`
|
||||
- `sample_limit`
|
||||
- `label_limit`
|
||||
- `label_name_length_limit`
|
||||
@ -362,7 +362,7 @@ LIBAPI = 0
|
||||
|
||||
# Increment this PATCH version before using `charmcraft publish-lib` or reset
|
||||
# to 0 if you are raising the major API version
|
||||
LIBPATCH = 46
|
||||
LIBPATCH = 47
|
||||
|
||||
PYDEPS = ["cosl"]
|
||||
|
||||
@ -377,7 +377,7 @@ ALLOWED_KEYS = {
|
||||
"scrape_timeout",
|
||||
"proxy_url",
|
||||
"relabel_configs",
|
||||
"metrics_relabel_configs",
|
||||
"metric_relabel_configs",
|
||||
"sample_limit",
|
||||
"label_limit",
|
||||
"label_name_length_limit",
|
||||
|
@ -277,13 +277,13 @@ juju relate <tls-certificates provider charm> <tls-certificates requirer charm>
|
||||
""" # noqa: D405, D410, D411, D214, D416
|
||||
|
||||
import copy
|
||||
import ipaddress
|
||||
import json
|
||||
import logging
|
||||
import uuid
|
||||
from contextlib import suppress
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from ipaddress import IPv4Address
|
||||
from typing import List, Literal, Optional, Union
|
||||
|
||||
from cryptography import x509
|
||||
@ -317,7 +317,7 @@ LIBAPI = 3
|
||||
|
||||
# Increment this PATCH version before using `charmcraft publish-lib` or reset
|
||||
# to 0 if you are raising the major API version
|
||||
LIBPATCH = 15
|
||||
LIBPATCH = 17
|
||||
|
||||
PYDEPS = ["cryptography", "jsonschema"]
|
||||
|
||||
@ -1077,7 +1077,7 @@ def generate_csr( # noqa: C901
|
||||
if sans_oid:
|
||||
_sans.extend([x509.RegisteredID(x509.ObjectIdentifier(san)) for san in sans_oid])
|
||||
if sans_ip:
|
||||
_sans.extend([x509.IPAddress(IPv4Address(san)) for san in sans_ip])
|
||||
_sans.extend([x509.IPAddress(ipaddress.ip_address(san)) for san in sans_ip])
|
||||
if sans:
|
||||
_sans.extend([x509.DNSName(san) for san in sans])
|
||||
if sans_dns:
|
||||
@ -1109,25 +1109,16 @@ def csr_matches_certificate(csr: str, cert: str) -> bool:
|
||||
Returns:
|
||||
bool: True/False depending on whether the CSR matches the certificate.
|
||||
"""
|
||||
try:
|
||||
csr_object = x509.load_pem_x509_csr(csr.encode("utf-8"))
|
||||
cert_object = x509.load_pem_x509_certificate(cert.encode("utf-8"))
|
||||
csr_object = x509.load_pem_x509_csr(csr.encode("utf-8"))
|
||||
cert_object = x509.load_pem_x509_certificate(cert.encode("utf-8"))
|
||||
|
||||
if csr_object.public_key().public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||
) != cert_object.public_key().public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||
):
|
||||
return False
|
||||
if (
|
||||
csr_object.public_key().public_numbers().n # type: ignore[union-attr]
|
||||
!= cert_object.public_key().public_numbers().n # type: ignore[union-attr]
|
||||
):
|
||||
return False
|
||||
except ValueError:
|
||||
logger.warning("Could not load certificate or CSR.")
|
||||
if csr_object.public_key().public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||
) != cert_object.public_key().public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
@ -72,7 +72,7 @@ LIBAPI = 2
|
||||
|
||||
# Increment this PATCH version before using `charmcraft publish-lib` or reset
|
||||
# to 0 if you are raising the major API version
|
||||
LIBPATCH = 12
|
||||
LIBPATCH = 13
|
||||
|
||||
PYDEPS = ["pydantic"]
|
||||
|
||||
@ -590,7 +590,7 @@ class IngressPerAppProvider(_IngressPerAppBase):
|
||||
if PYDANTIC_IS_V1:
|
||||
results[ingress_relation.app.name] = ingress_data.ingress.dict()
|
||||
else:
|
||||
results[ingress_relation.app.name] = ingress_data.ingress.model_dump(mode=json) # type: ignore
|
||||
results[ingress_relation.app.name] = ingress_data.ingress.model_dump(mode="json")
|
||||
return results
|
||||
|
||||
|
||||
|
@ -68,7 +68,7 @@ class ExampleRequirerCharm(CharmBase):
|
||||
unit_credentials = self.interface.get_unit_credentials(relation)
|
||||
# unit_credentials is a juju secret id
|
||||
secret = self.model.get_secret(id=unit_credentials)
|
||||
secret_content = secret.get_content()
|
||||
secret_content = secret.get_content(refresh=True)
|
||||
role_id = secret_content["role-id"]
|
||||
role_secret_id = secret_content["role-secret-id"]
|
||||
|
||||
@ -99,7 +99,7 @@ class ExampleRequirerCharm(CharmBase):
|
||||
|
||||
def get_nonce(self):
|
||||
secret = self.model.get_secret(label=NONCE_SECRET_LABEL)
|
||||
nonce = secret.get_content()["nonce"]
|
||||
nonce = secret.get_content(refresh=True)["nonce"]
|
||||
return nonce
|
||||
|
||||
|
||||
@ -132,7 +132,7 @@ LIBAPI = 0
|
||||
|
||||
# Increment this PATCH version before using `charmcraft publish-lib` or reset
|
||||
# to 0 if you are raising the major API version
|
||||
LIBPATCH = 5
|
||||
LIBPATCH = 7
|
||||
|
||||
PYDEPS = ["pydantic", "pytest-interface-tester"]
|
||||
|
||||
@ -163,6 +163,9 @@ class VaultKvProviderSchema(BaseModel):
|
||||
ca_certificate: str = Field(
|
||||
description="The CA certificate to use when validating the Vault server's certificate."
|
||||
)
|
||||
egress_subnet: str = Field(
|
||||
description="The CIDR allowed by the role."
|
||||
)
|
||||
credentials: Json[Mapping[str, str]] = Field(
|
||||
description=(
|
||||
"Mapping of unit name and credentials for that unit."
|
||||
@ -352,7 +355,18 @@ class VaultKvProvides(ops.Object):
|
||||
|
||||
relation.data[self.charm.app]["mount"] = mount
|
||||
|
||||
def set_unit_credentials(self, relation: ops.Relation, nonce: str, secret: ops.Secret):
|
||||
def set_egress_subnet(self, relation: ops.Relation, egress_subnet: str):
|
||||
"""Set the egress_subnet on the relation."""
|
||||
if not self.charm.unit.is_leader():
|
||||
return
|
||||
relation.data[self.charm.app]["egress_subnet"] = egress_subnet
|
||||
|
||||
def set_unit_credentials(
|
||||
self,
|
||||
relation: ops.Relation,
|
||||
nonce: str,
|
||||
secret: ops.Secret,
|
||||
):
|
||||
"""Set the unit credentials on the relation."""
|
||||
if not self.charm.unit.is_leader():
|
||||
return
|
||||
@ -526,7 +540,11 @@ class VaultKvRequires(ops.Object):
|
||||
self.mount_suffix = mount_suffix
|
||||
self.framework.observe(
|
||||
self.charm.on[relation_name].relation_joined,
|
||||
self._on_vault_kv_relation_joined,
|
||||
self._handle_relation,
|
||||
)
|
||||
self.framework.observe(
|
||||
self.charm.on.config_changed,
|
||||
self._handle_relation,
|
||||
)
|
||||
self.framework.observe(
|
||||
self.charm.on[relation_name].relation_changed,
|
||||
@ -545,17 +563,20 @@ class VaultKvRequires(ops.Object):
|
||||
"""Set the egress_subnet on the relation."""
|
||||
relation.data[self.charm.unit]["egress_subnet"] = egress_subnet
|
||||
|
||||
def _on_vault_kv_relation_joined(self, event: ops.RelationJoinedEvent):
|
||||
"""Handle relation joined.
|
||||
def _handle_relation(self, event: ops.EventBase):
|
||||
"""Run when a new unit joins the relation or when the address of the unit changes.
|
||||
|
||||
Set the secret backend in the application databag if we are the leader.
|
||||
Always update the egress_subnet in the unit databag.
|
||||
Emit the connected event.
|
||||
"""
|
||||
relation = self.model.get_relation(relation_name=self.relation_name)
|
||||
if not relation:
|
||||
return
|
||||
if self.charm.unit.is_leader():
|
||||
event.relation.data[self.charm.app]["mount_suffix"] = self.mount_suffix
|
||||
relation.data[self.charm.app]["mount_suffix"] = self.mount_suffix
|
||||
self.on.connected.emit(
|
||||
event.relation.id,
|
||||
event.relation.name,
|
||||
relation.id,
|
||||
relation.name,
|
||||
)
|
||||
|
||||
def _on_vault_kv_relation_changed(self, event: ops.RelationChangedEvent):
|
||||
|
Loading…
Reference in New Issue
Block a user