Skip to content

Commit

Permalink
Add support for Kuadrant
Browse files Browse the repository at this point in the history
  • Loading branch information
pehala committed Jan 16, 2023
1 parent 11cfa01 commit 8201586
Show file tree
Hide file tree
Showing 19 changed files with 322 additions and 26 deletions.
8 changes: 7 additions & 1 deletion config/settings.local.yaml.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,10 @@
# deploy: false # If false, the testsuite will use already deployed authorino for testing
# url: "" # URL for already deployed Authorino
# envoy:
# image: "docker.io/envoyproxy/envoy:v1.23-latest" # Envoy image, the testsuite should use, only for Authorino tests now
# image: "docker.io/envoyproxy/envoy:v1.23-latest" # Envoy image, the testsuite should use, only for Authorino tests now
# kuadrant:
# enabled: true # True, if Testsuite should test Kuadrant instead of individual operators
# namespace: "kuadrant" # Namespaces where Kuadrant resides
# gateway: # Reference to Gateway that should be used
# namespace: "istio-system"
# name: "istio-ingressgateway"
8 changes: 7 additions & 1 deletion config/settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,10 @@ default:
deploy: true
log_level: "debug"
envoy:
image: "docker.io/envoyproxy/envoy:v1.23-latest"
image: "docker.io/envoyproxy/envoy:v1.23-latest"
kuadrant:
enabled: true
project: "kuadrant"
gateway:
project: "istio-system"
name: "istio-ingressgateway"
3 changes: 2 additions & 1 deletion testsuite/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ def __init__(self, name, default, **kwargs) -> None:
DefaultValueValidator("rhsso.url", default=fetch_route("no-ssl-sso")),
DefaultValueValidator("rhsso.password", default=fetch_secret("credential-sso", "ADMIN_PASSWORD")),
DefaultValueValidator("mockserver.url", default=fetch_route("mockserver", force_http=True)),
Validator("kuadrant.enable", must_exist=False, eq=False) | Validator("kuadrant.gateway.name", must_exist=True),
],
validate_only=["authorino"],
validate_only=["authorino", "kuadrant"],
loaders=["dynaconf.loaders.env_loader", "testsuite.config.openshift_loader"]
)
9 changes: 8 additions & 1 deletion testsuite/openshift/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
from functools import cached_property
from typing import Dict, Optional
from urllib.parse import urlparse

import openshift as oc
from openshift import Context, Selector, OpenShiftPythonException
Expand Down Expand Up @@ -34,7 +35,7 @@ def __init__(self, project: str, api_url: str = None, token: str = None, kubecon
self.token = token
self._kubeconfig_path = kubeconfig_path

def change_project(self, project):
def change_project(self, project) -> "OpenShiftClient":
"""Return new OpenShiftClient with a different project"""
return OpenShiftClient(project, self._api_url, self.token, self._kubeconfig_path)

Expand All @@ -56,6 +57,12 @@ def api_url(self):
with self.context:
return oc.whoami("--show-server=true")

@cached_property
def apps_url(self):
"""Return URL under which all routes are routed"""
hostname = urlparse(self.api_url).hostname
return "apps." + hostname.split(".", 1)[1]

@property
def project(self):
"""Returns real OpenShift project name"""
Expand Down
6 changes: 5 additions & 1 deletion testsuite/openshift/httpbin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

from testsuite.objects import LifecycleObject
from testsuite.openshift.client import OpenShiftClient
from testsuite.openshift.objects.gateway_api import Referencable


class Httpbin(LifecycleObject):
class Httpbin(LifecycleObject, Referencable):
"""Httpbin deployed in OpenShift through template"""
def __init__(self, openshift: OpenShiftClient, name, label) -> None:
super().__init__()
Expand All @@ -19,6 +20,9 @@ def __init__(self, openshift: OpenShiftClient, name, label) -> None:
@property
def reference(self):
return {
"group": "",
"kind": "Service",
"port": 8080,
"name": self.name,
"namespace": self.openshift.project
}
Expand Down
12 changes: 9 additions & 3 deletions testsuite/openshift/objects/auth_config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
class AuthConfig(OpenShiftObject, Authorization):
"""Represents AuthConfig CR from Authorino"""

@property
def auth_section(self):
"""Returns objects where all auth related things should be added"""
return self.model.spec

@cached_property
def authorization(self) -> Authorizations:
"""Gives access to authorization settings"""
Expand All @@ -34,7 +39,8 @@ def responses(self) -> Responses:
return ResponsesSection(self, "response")

@classmethod
def create_instance(cls, openshift: OpenShiftClient, name, route: Route, labels: Dict[str, str] = None):
def create_instance(cls, openshift: OpenShiftClient, name, route: Route,
labels: Dict[str, str] = None, hostnames=None):
"""Creates base instance"""
model: Dict = {
"apiVersion": "authorino.kuadrant.io/v1beta1",
Expand All @@ -44,7 +50,7 @@ def create_instance(cls, openshift: OpenShiftClient, name, route: Route, labels:
"namespace": openshift.project
},
"spec": {
"hosts": route.hostnames
"hosts": hostnames or route.hostnames
}
}

Expand All @@ -71,5 +77,5 @@ def remove_all_hosts(self):
@modify
def set_deny_with(self, code, value):
"""Set denyWith"""
self.model.spec["denyWith"] = {
self.auth_section["denyWith"] = {
"unauthenticated": {"code": code, "headers": [{"name": "Location", "valueFrom": {"authJSON": value}}]}}
37 changes: 37 additions & 0 deletions testsuite/openshift/objects/auth_config/auth_policy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Module containing classes related to Auth Policy"""
from typing import Dict

from testsuite.openshift.client import OpenShiftClient
from testsuite.openshift.objects.auth_config import AuthConfig
from testsuite.openshift.objects.gateway_api import Referencable


class AuthPolicy(AuthConfig):
"""AuthPolicy object, it serves as Kuadrants AuthConfig"""

@property
def auth_section(self):
return self.model.spec.setdefault("authScheme", {})

# pylint: disable=unused-argument
@classmethod
def create_instance(cls, openshift: OpenShiftClient, name, route: Referencable, # type: ignore
labels: Dict[str, str] = None, hostnames=None):
"""Creates base instance
"""
model: Dict = {
"apiVersion": "kuadrant.io/v1beta1",
"kind": "AuthPolicy",
"metadata": {
"name": name,
"namespace": openshift.project
},
"spec": {
"targetRef": route.reference,
}
}

if labels is not None:
model["metadata"]["labels"] = labels

return cls(model, context=openshift.context)
11 changes: 7 additions & 4 deletions testsuite/openshift/objects/auth_config/sections.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
"""AuthConfig CR object"""
from dataclasses import asdict
from typing import Dict, Literal, Iterable
from typing import Dict, Literal, Iterable, TYPE_CHECKING

from testsuite.objects import Identities, Metadata, Responses, MatchExpression, Authorizations, Rule, Cache, Value
from testsuite.openshift.objects import OpenShiftObject, modify
from testsuite.openshift.objects import modify

if TYPE_CHECKING:
from testsuite.openshift.objects.auth_config import AuthConfig


class Section:
"""Common class for all Sections"""
def __init__(self, obj: OpenShiftObject, section_name) -> None:
def __init__(self, obj: "AuthConfig", section_name) -> None:
super().__init__()
self.obj = obj
self.section_name = section_name
Expand All @@ -27,7 +30,7 @@ def committed(self):
@property
def section(self):
"""The actual dict section which will be edited"""
return self.obj.model.spec.setdefault(self.section_name, [])
return self.obj.auth_section.setdefault(self.section_name, [])

def add_item(self, name, value, priority: int = None, when: Iterable[Rule] = None,
metrics: bool = None, cache: Cache = None):
Expand Down
141 changes: 141 additions & 0 deletions testsuite/openshift/objects/gateway_api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
"""Module containing all Gateway API related classes"""
import typing
from abc import ABC, abstractmethod
from functools import cached_property

from openshift import Selector

from testsuite.httpx import HttpxBackoffClient
from testsuite.openshift.client import OpenShiftClient
from testsuite.openshift.objects import OpenShiftObject, modify
from testsuite.openshift.objects.proxy import Proxy
from testsuite.openshift.objects.route import Route
from testsuite.utils import randomize

if typing.TYPE_CHECKING:
from testsuite.openshift.httpbin import Httpbin


class Referencable(ABC):
"""Object that can be referenced in Gateway API style"""
@property
@abstractmethod
def reference(self) -> dict[str, str]:
"""
Returns dict, which can be used as reference in Gateway API Objects.
https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.ParentReference
"""


class HTTPRoute(OpenShiftObject, Referencable, Route):
"""HTTPRoute object, serves as replacement for Routes and Ingresses"""

@cached_property
def hostnames(self):
return self.model.spec.hostnames

@classmethod
def create_instance(cls, openshift: OpenShiftClient, name, parent: Referencable,
hostname, backend: "Httpbin", labels: dict[str, str] = None):
"""Creates new instance of HTTPRoute"""
model = {
"apiVersion": "gateway.networking.k8s.io/v1alpha2",
"kind": "HTTPRoute",
"metadata": {
"name": name,
"namespace": openshift.project
},
"spec": {
"parentRefs": [parent.reference],
"hostnames": [hostname],
"rules": [
{
# "matches": [
# {
# "path": {
# "type": "PathPrefix",
# "value": "/"
# },
# "method": "GET"
# },
# ],
"backendRefs": [backend.reference]
}
]
}
}

if labels is not None:
model["metadata"]["labels"] = labels # type: ignore

return cls(model, context=openshift.context)

@property
def reference(self):
return {
"group": "gateway.networking.k8s.io",
"kind": "HTTPRoute",
"name": self.name(),
"namespace": self.namespace()
}

@modify
def add_hostname(self, hostname):
"""Adds hostname to the Route"""
self.model.spec.hostnames.append(hostname)


# pylint: disable=too-many-instance-attributes
class Gateway(Referencable, Proxy):
"""Gateway object already present on the server"""

def __init__(self, openshift: OpenShiftClient, name, namespace, label, httpbin: "Httpbin") -> None:
super().__init__()
self.openshift = openshift
self.system_openshift = openshift.change_project(namespace)
self.name = name
self.label = label
self.namespace = namespace
self.httpbin = httpbin

self._route: HTTPRoute = None # type: ignore
self._selector: Selector = None # type: ignore

def _expose_route(self, name, service):
return self.system_openshift.routes.expose(name, service, port=8080)

@cached_property
def route(self) -> HTTPRoute:
return self._route

def add_hostname(self, name) -> tuple[Route, str]:
route = self._expose_route(name, self.name)
self._selector.union(route.self_selector())
self.route.add_hostname(route.model.spec.host)
return self.route, route.model.spec.host

def client(self, **kwargs):
"""Return Httpx client for the requests to this backend"""
return HttpxBackoffClient(base_url=f"http://{self.route.hostnames[0]}", **kwargs)

def commit(self):
name = randomize(self.name)
route = self._expose_route(name, self.name)
self._selector = route.self_selector()

self._route = HTTPRoute.create_instance(self.openshift, name, self, route.model.spec.host,
self.httpbin, {"app": self.label})
self._route.commit()

def delete(self):
self._route.delete()
self._selector.delete()

@property
def reference(self):
return {
"group": "gateway.networking.k8s.io",
"kind": "Gateway",
"name": self.name,
"namespace": self.namespace
}
Loading

0 comments on commit 8201586

Please sign in to comment.