Skip to content

Commit

Permalink
chore: update charm libraries
Browse files Browse the repository at this point in the history
  • Loading branch information
Github Actions committed Nov 15, 2023
1 parent 65ab2f1 commit 6479f10
Showing 1 changed file with 54 additions and 19 deletions.
73 changes: 54 additions & 19 deletions lib/charms/traefik_k8s/v2/ingress.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,13 @@ def _on_ingress_ready(self, event: IngressPerAppReadyEvent):
def _on_ingress_revoked(self, event: IngressPerAppRevokedEvent):
logger.info("This app no longer has ingress")
"""
import ipaddress
import json
import logging
import socket
import typing
from dataclasses import dataclass
from typing import (
Any,
Dict,
List,
MutableMapping,
Optional,
Sequence,
Tuple,
)
from typing import Any, Callable, Dict, List, MutableMapping, Optional, Sequence, Tuple, Union

import pydantic
from ops.charm import CharmBase, RelationBrokenEvent, RelationEvent
Expand All @@ -79,7 +72,7 @@ def _on_ingress_revoked(self, event: IngressPerAppRevokedEvent):

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 6
LIBPATCH = 8

PYDEPS = ["pydantic<2.0"]

Expand Down Expand Up @@ -200,14 +193,36 @@ def validate_port(cls, port): # noqa: N805 # pydantic wants 'cls' as first arg
class IngressRequirerUnitData(DatabagModel):
"""Ingress requirer unit databag model."""

host: str = Field(description="Hostname the unit wishes to be exposed.")
host: str = Field(description="Hostname at which the unit is reachable.")
ip: Optional[str] = Field(
description="IP at which the unit is reachable, "
"IP can only be None if the IP information can't be retrieved from juju."
)

@validator("host", pre=True)
def validate_host(cls, host): # noqa: N805 # pydantic wants 'cls' as first arg
"""Validate host."""
assert isinstance(host, str), type(host)
return host

@validator("ip", pre=True)
def validate_ip(cls, ip): # noqa: N805 # pydantic wants 'cls' as first arg
"""Validate ip."""
if ip is None:
return None
if not isinstance(ip, str):
raise TypeError(f"got ip of type {type(ip)} instead of expected str")
try:
ipaddress.IPv4Address(ip)
return ip
except ipaddress.AddressValueError:
pass
try:
ipaddress.IPv6Address(ip)
return ip
except ipaddress.AddressValueError:
raise ValueError(f"{ip!r} is not a valid ip address")


class RequirerSchema(BaseModel):
"""Requirer schema for Ingress."""
Expand Down Expand Up @@ -244,6 +259,7 @@ def __init__(self, charm: CharmBase, relation_name: str = DEFAULT_RELATION_NAME)
observe(rel_events.relation_created, self._handle_relation)
observe(rel_events.relation_joined, self._handle_relation)
observe(rel_events.relation_changed, self._handle_relation)
observe(rel_events.relation_departed, self._handle_relation)
observe(rel_events.relation_broken, self._handle_relation_broken)
observe(charm.on.leader_elected, self._handle_upgrade_or_leader) # type: ignore
observe(charm.on.upgrade_charm, self._handle_upgrade_or_leader) # type: ignore
Expand Down Expand Up @@ -540,12 +556,13 @@ def __init__(
relation_name: str = DEFAULT_RELATION_NAME,
*,
host: Optional[str] = None,
ip: Optional[str] = None,
port: Optional[int] = None,
strip_prefix: bool = False,
redirect_https: bool = False,
# fixme: this is horrible UX.
# shall we switch to manually calling provide_ingress_requirements with all args when ready?
scheme: typing.Callable[[], str] = lambda: "http",
scheme: Union[Callable[[], str], str] = lambda: "http",
):
"""Constructor for IngressRequirer.
Expand All @@ -560,9 +577,12 @@ def __init__(
relation must be of interface type `ingress` and have "limit: 1")
host: Hostname to be used by the ingress provider to address the requiring
application; if unspecified, the default Kubernetes service name will be used.
ip: Alternative addressing method other than host to be used by the ingress provider;
if unspecified, binding address from juju network API will be used.
strip_prefix: configure Traefik to strip the path prefix.
redirect_https: redirect incoming requests to HTTPS.
scheme: callable returning the scheme to use when constructing the ingress url.
Or a string, if the scheme is known and stable at charm-init-time.
Request Args:
port: the port of the service
Expand All @@ -572,14 +592,14 @@ def __init__(
self.relation_name = relation_name
self._strip_prefix = strip_prefix
self._redirect_https = redirect_https
self._get_scheme = scheme
self._get_scheme = scheme if callable(scheme) else lambda: scheme

self._stored.set_default(current_url=None) # type: ignore

# if instantiated with a port, and we are related, then
# we immediately publish our ingress data to speed up the process.
if port:
self._auto_data = host, port
self._auto_data = host, ip, port
else:
self._auto_data = None

Expand Down Expand Up @@ -616,14 +636,15 @@ def is_ready(self):

def _publish_auto_data(self):
if self._auto_data:
host, port = self._auto_data
self.provide_ingress_requirements(host=host, port=port)
host, ip, port = self._auto_data
self.provide_ingress_requirements(host=host, ip=ip, port=port)

def provide_ingress_requirements(
self,
*,
scheme: Optional[str] = None,
host: Optional[str] = None,
ip: Optional[str] = None,
port: int,
):
"""Publishes the data that Traefik needs to provide ingress.
Expand All @@ -632,34 +653,48 @@ def provide_ingress_requirements(
scheme: Scheme to be used; if unspecified, use the one used by __init__.
host: Hostname to be used by the ingress provider to address the
requirer unit; if unspecified, FQDN will be used instead
ip: Alternative addressing method other than host to be used by the ingress provider.
if unspecified, binding address from juju network API will be used.
port: the port of the service (required)
"""
for relation in self.relations:
self._provide_ingress_requirements(scheme, host, port, relation)
self._provide_ingress_requirements(scheme, host, ip, port, relation)

def _provide_ingress_requirements(
self,
scheme: Optional[str],
host: Optional[str],
ip: Optional[str],
port: int,
relation: Relation,
):
if self.unit.is_leader():
self._publish_app_data(scheme, port, relation)

self._publish_unit_data(host, relation)
self._publish_unit_data(host, ip, relation)

def _publish_unit_data(
self,
host: Optional[str],
ip: Optional[str],
relation: Relation,
):
if not host:
host = socket.getfqdn()

if ip is None:
network_binding = self.charm.model.get_binding(relation)
if (
network_binding is not None
and (bind_address := network_binding.network.bind_address) is not None
):
ip = str(bind_address)
else:
log.error("failed to retrieve ip information from juju")

unit_databag = relation.data[self.unit]
try:
IngressRequirerUnitData(host=host).dump(unit_databag)
IngressRequirerUnitData(host=host, ip=ip).dump(unit_databag)
except pydantic.ValidationError as e:
msg = "failed to validate unit data"
log.info(msg, exc_info=True) # log to INFO because this might be expected
Expand Down

0 comments on commit 6479f10

Please sign in to comment.