istio_ingress_route

Istio ingress route interface library.

This library provides the provider and requirer sides of the istio-ingress-route relation interface for advanced ingress routing through the istio-ingress-k8s charm.

What is this library for?

The istio-ingress-k8s charm supports the standard ingress interface, but that interface has limitations: it cannot open multiple ports, configure custom path prefixes, apply URL rewrites or redirects, or set up gRPC routing.

The istio-ingress-route interface fills this gap. It allows requiring charms to publish rich routing configurations — multi-port listeners, HTTP path matching, gRPC method routing, URL rewrite filters, request redirect filters — that the istio-ingress-k8s charm translates into Kubernetes Gateway API resources (Gateway listeners, HTTPRoutes, and GRPCRoutes).

How it works

  1. The requirer charm builds an IstioIngressRouteConfig describing its listeners, HTTP routes, and gRPC routes, then calls submit_config(config).

  2. The provider (istio-ingress-k8s) receives a ready event, reads the config via get_config(relation), and creates the corresponding Gateway API resources.

  3. The provider publishes the external host and TLS status back to the requirer so it can construct its public URL.

Requirer usage:

from charmlibs.interfaces.istio_ingress_route import (
    IstioIngressRouteRequirer,
    IstioIngressRouteConfig,
    Listener,
    HTTPRoute,
    GRPCRoute,
    BackendRef,
    ProtocolType,
    HTTPMethod,
    HTTPRouteMatch,
    HTTPPathMatch,
    HTTPPathMatchType,
    GRPCMethodMatch,
    GRPCRouteMatch,
    to_gateway_protocol,
)

class MyCharm(CharmBase):
    def __init__(self, *args):
        super().__init__(*args)
        self.ingress = IstioIngressRouteRequirer(
            self,
            relation_name="ingress",
        )
        self.framework.observe(
            self.ingress.on.ready, self._on_ingress_ready
        )

    def _configure_ingress(self):
        http_listener = Listener(port=3200, protocol=ProtocolType.HTTP)
        grpc_listener = Listener(port=9096, protocol=ProtocolType.GRPC)

        config = IstioIngressRouteConfig(
            model=self.model.name,
            listeners=[http_listener, grpc_listener],
            http_routes=[
                HTTPRoute(
                    name="http-route",
                    listener=http_listener,
                    matches=[
                        HTTPRouteMatch(
                            path=HTTPPathMatch(
                                type=HTTPPathMatchType.PathPrefix, value="/api"
                            ),
                            method=HTTPMethod.GET,
                        )
                    ],
                    backends=[BackendRef(service=self.app.name, port=3200)],
                ),
            ],
            grpc_routes=[
                GRPCRoute(
                    name="grpc-route",
                    listener=grpc_listener,
                    matches=[
                        GRPCRouteMatch(
                            method=GRPCMethodMatch(
                                service="myapp.MyService", method="GetData"
                            )
                        )
                    ],
                    backends=[BackendRef(service=self.app.name, port=9096)],
                ),
            ],
        )
        self.ingress.submit_config(config)

    def _on_ingress_ready(self, event):
        scheme = "https" if self.ingress.tls_enabled else "http"
        url = f"{scheme}://{self.ingress.external_host}"

Provider usage:

from charmlibs.interfaces.istio_ingress_route import (
    IstioIngressRouteProvider,
    to_gateway_protocol,
)

class IstioIngressCharm(CharmBase):
    def __init__(self, *args):
        super().__init__(*args)
        self.istio_ingress_route = IstioIngressRouteProvider(
            self,
            external_host=self._external_host,
            tls_enabled=self._is_tls_enabled(),
        )
        self.framework.observe(
            self.istio_ingress_route.on.ready,
            self._handle_istio_ingress_route_ready,
        )

    def _handle_istio_ingress_route_ready(self, event):
        config = self.istio_ingress_route.get_config(event.relation)
        if not config:
            return
        is_tls_enabled = self._is_tls_enabled()
        for listener in config.listeners:
            gateway_protocol = to_gateway_protocol(
                listener.protocol, is_tls_enabled
            )
class BackendRef(
*,
service: str,
port: Annotated[int, Ge(ge=1), Le(le=65535)],
weight: Annotated[int | None, Ge(ge=1), Le(le=100)] = None,
)

Bases: BaseModel

Reference to a backend service.

service: str
port: int
weight: int | None
class FilterType(*values)

Bases: str, Enum

Filter type values.

URLRewrite = 'URLRewrite'
RequestRedirect = 'RequestRedirect'
class GRPCMethodMatch(*, service: str, method: str | None = None)

Bases: BaseModel

gRPC method matching configuration.

Matches gRPC methods in the format /service/method.

The service can be a simple name (e.g., "MyService") or package-qualified (e.g., "package.MyService"). The method is the RPC method name; if omitted, all methods on the service are matched.

Examples

>>> GRPCMethodMatch(service="com.example.UserService")
# Matches all methods on /com.example.UserService
>>> GRPCMethodMatch(service="com.example.UserService", method="GetUser")
# Matches only /com.example.UserService/GetUser
>>> GRPCMethodMatch(service="UserService", method="CreateUser")
# Matches /UserService/CreateUser
service: str
method: str | None
class GRPCRoute(
*,
name: str,
listener: Listener,
backends: list[BackendRef],
hostnames: list[str] | None = None,
matches: list[GRPCRouteMatch] | None = None,
filters: list[RequestRedirectFilter] | None = None,
)

Bases: _L7Route

gRPC route configuration.

matches: list[GRPCRouteMatch] | None
filters: list[RequestRedirectFilter] | None
property protocol: ProtocolType

Protocol type for gRPC routes.

class GRPCRouteMatch(
*,
headers: dict[str, str] | None = None,
method: GRPCMethodMatch | None = None,
)

Bases: _RouteMatch

Match conditions for gRPC routes.

method: GRPCMethodMatch | None
class HTTPMethod(*values)

Bases: str, Enum

HTTP methods for route matching.

GET = 'GET'
POST = 'POST'
PUT = 'PUT'
DELETE = 'DELETE'
PATCH = 'PATCH'
HEAD = 'HEAD'
OPTIONS = 'OPTIONS'
CONNECT = 'CONNECT'
TRACE = 'TRACE'
class HTTPPathMatch(
*,
type: HTTPPathMatchType = HTTPPathMatchType.PathPrefix,
value: str,
)

Bases: BaseModel

Path matching configuration for HTTP routes.

type: HTTPPathMatchType
value: str
class HTTPPathMatchType(*values)

Bases: str, Enum

Path match types for HTTP routes.

Exact = 'Exact'
PathPrefix = 'PathPrefix'
RegularExpression = 'RegularExpression'
class HTTPRoute(
*,
name: str,
listener: Listener,
backends: list[BackendRef],
hostnames: list[str] | None = None,
matches: list[HTTPRouteMatch] | None = None,
filters: list[URLRewriteFilter | RequestRedirectFilter] | None = None,
)

Bases: _L7Route

HTTP route configuration.

matches: list[HTTPRouteMatch] | None
filters: list[URLRewriteFilter | RequestRedirectFilter] | None
property protocol: ProtocolType

Protocol type for HTTP routes.

class HTTPRouteMatch(
*,
headers: dict[str, str] | None = None,
path: HTTPPathMatch | None = None,
method: HTTPMethod | None = None,
)

Bases: _RouteMatch

Match conditions for HTTP routes.

path: HTTPPathMatch | None
method: HTTPMethod | None
class IstioIngressRouteConfig(
*,
model: str,
listeners: list[Listener] = <factory>,
http_routes: list[HTTPRoute] = <factory>,
grpc_routes: list[GRPCRoute] = <factory>,
)

Bases: BaseModel

Complete configuration for istio-ingress-route.

model: str
listeners: list[Listener]
http_routes: list[HTTPRoute]
grpc_routes: list[GRPCRoute]
exception IstioIngressRouteError

Bases: RuntimeError

Base class for exceptions raised by IstioIngressRoute.

class IstioIngressRouteProvider(
charm: CharmBase,
relation_name: str = 'istio-ingress-route',
external_host: str = '',
*,
tls_enabled: bool = False,
)

Bases: Object

Implementation of the provider of istio_ingress_route.

This will be owned by the istio-ingress charm. The main idea is that istio-ingress will observe the ready event and, upon receiving it, will fetch the config from the requirer’s application databag, apply it (create Gateway listeners and Routes), and update its own app databag to let the requirer know that the ingress is ready.

on

Container for IstioIngressRouteProvider events.

property external_host: str

Return the external host set by istio-ingress, if any.

property tls_enabled: bool

Return whether TLS is enabled on the gateway.

property relations

The list of Relation instances associated with this endpoint.

update_ingress_address(
*,
external_host: str | None = None,
tls_enabled: bool | None = None,
)

Ensure that requirers know the external host for istio-ingress.

wipe_ingress_data(relation: Relation)

Clear ingress data from relation.

This removes the external_host and tls_enabled fields from the provider’s application databag for the given relation. This is typically used when route conflicts are detected or when the ingress should no longer be available.

Parameters:

relation – The relation to clear data from

is_ready(relation: Relation) bool

Whether IstioIngressRoute is ready on this relation.

Returns True when the remote app shared the config; False otherwise.

get_config(
relation: Relation,
) IstioIngressRouteConfig | None

Retrieve the config published by the remote application.

class IstioIngressRouteRequirer(
charm: CharmBase,
relation_name: str = 'ingress',
)

Bases: Object

Handles the requirer side of the istio-ingress-route interface.

This class provides an API for publishing routing configurations to the istio-ingress charm through the istio-ingress-route relation.

on

Container for IstioIngressRouteRequirer events.

property external_host: str

Return the external host set by istio-ingress, if any.

property tls_enabled: bool

Return whether TLS is enabled on the gateway.

is_ready() bool

Is the IstioIngressRouteRequirer ready to submit data?

submit_config(
config: IstioIngressRouteConfig,
)

Submit an ingress configuration to istio-ingress.

This method publishes routing configuration data to the istio-ingress-route relation.

Parameters:

config – The IstioIngressRouteConfig to submit.

Raises:

UnauthorizedError – If the unit is not the leader.

class Listener(
*,
port: Annotated[int, Ge(ge=1), Le(le=65535)],
protocol: ProtocolType,
)

Bases: BaseModel

Gateway listener configuration.

Specify the application-level protocol (HTTP or GRPC). The istio-ingress charm will automatically upgrade to TLS (HTTPS/GRPCS) when certificates are available.

The listener name is automatically derived from port and protocol by the charm.

port: int
protocol: ProtocolType
property name: str

Get the listener name derived from protocol and port.

Returns:

{protocol}-{port} (e.g., “http-8080”, “grpc-9090”)

Return type:

Listener name in format

property gateway_protocol: str

Get the Gateway API protocol (cleartext).

This maps GRPC -> HTTP, since Gateway API doesn’t have a GRPC protocol type. Both HTTP and gRPC use HTTP/2; the difference is in the route type.

Returns:

Gateway API protocol string without TLS (“HTTP”)

class PathModifier(
*,
type: PathModifierType,
value: str,
)

Bases: BaseModel

Path modification configuration.

type: PathModifierType
value: str
classmethod validate_path_modifier(data: Any) Any

Handle deserialization from K8s Gateway API format.

serialize_model() dict[str, str]

Serialize with correct field name for K8s Gateway API.

class PathModifierType(*values)

Bases: str, Enum

Path modifier types.

ReplaceFullPath = 'ReplaceFullPath'
ReplacePrefixMatch = 'ReplacePrefixMatch'
class ProtocolType(*values)

Bases: str, Enum

Application-level protocol types.

Consumers specify the application protocol (HTTP or GRPC). The istio-ingress charm automatically applies TLS encryption based on certificate availability, upgrading HTTP to HTTPS and GRPC to GRPCS transparently.

The Gateway API doesn’t have GRPC as a distinct protocol type. GRPC uses HTTP/2, so it maps to “HTTP” or “HTTPS” in Gateway listeners. The difference between HTTP and gRPC traffic is expressed through the route type (HTTPRoute vs GRPCRoute).

HTTP = 'HTTP'
GRPC = 'GRPC'
class RequestRedirectFilter(
*,
requestRedirect: RequestRedirectSpec,
)

Bases: BaseModel

Request redirect filter for HTTP/gRPC redirects.

requestRedirect: RequestRedirectSpec
property type: FilterType

Filter type.

class RequestRedirectSpec(
*,
scheme: str | None = None,
hostname: str | None = None,
path: PathModifier | None = None,
port: Annotated[int | None, Ge(ge=1), Le(le=65535)] = None,
statusCode: int = 301,
)

Bases: BaseModel

Specification for request redirect configuration.

scheme: str | None
hostname: str | None
path: PathModifier | None
port: int | None
statusCode: int
class URLRewriteFilter(
*,
urlRewrite: URLRewriteSpec,
)

Bases: BaseModel

URLRewrite filter for modifying request URL before proxying upstream.

urlRewrite: URLRewriteSpec
property type: FilterType

Filter type.

class URLRewriteSpec(
*,
hostname: str | None = None,
path: PathModifier | None = None,
)

Bases: BaseModel

Specification for URL rewrite configuration.

At least one of hostname or path must be specified.

hostname: str | None
path: PathModifier | None
exception UnauthorizedError

Bases: IstioIngressRouteError

Raised when the unit needs the leader to perform some action.

to_gateway_protocol(
protocol: ProtocolType,
tls_enabled: bool = False,
) str

Map application protocol to Gateway API protocol.

The Gateway API doesn’t have separate HTTP/gRPC protocol types. Both use HTTP, with the difference being in the route type (HTTPRoute vs GRPCRoute).

Parameters:
  • protocol – Application-level protocol (HTTP or GRPC)

  • tls_enabled – Whether TLS termination should be applied

Returns:

Gateway API protocol string (“HTTP” or “HTTPS”)

Examples

>>> to_gateway_protocol(ProtocolType.HTTP, tls_enabled=False)
'HTTP'
>>> to_gateway_protocol(ProtocolType.HTTP, tls_enabled=True)
'HTTPS'
>>> to_gateway_protocol(ProtocolType.GRPC, tls_enabled=False)
'HTTP'
>>> to_gateway_protocol(ProtocolType.GRPC, tls_enabled=True)
'HTTPS'