Merge branch 'master' into version-0.14

This commit is contained in:
Jens Langhammer 2020-12-28 17:45:49 +01:00
commit db92178d0f
9 changed files with 73 additions and 24 deletions

View File

@ -1,5 +1,5 @@
"""Base Controller""" """Base Controller"""
from typing import Dict, List from dataclasses import dataclass
from structlog import get_logger from structlog import get_logger
from structlog.testing import capture_logs from structlog.testing import capture_logs
@ -7,15 +7,26 @@ from structlog.testing import capture_logs
from authentik.lib.sentry import SentryIgnoredException from authentik.lib.sentry import SentryIgnoredException
from authentik.outposts.models import Outpost, OutpostServiceConnection from authentik.outposts.models import Outpost, OutpostServiceConnection
FIELD_MANAGER = "goauthentik.io"
class ControllerException(SentryIgnoredException): class ControllerException(SentryIgnoredException):
"""Exception raised when anything fails during controller run""" """Exception raised when anything fails during controller run"""
@dataclass
class DeploymentPort:
"""Info about deployment's single port."""
port: int
name: str
protocol: str
class BaseController: class BaseController:
"""Base Outpost deployment controller""" """Base Outpost deployment controller"""
deployment_ports: Dict[str, int] deployment_ports: list[DeploymentPort]
outpost: Outpost outpost: Outpost
connection: OutpostServiceConnection connection: OutpostServiceConnection
@ -24,14 +35,14 @@ class BaseController:
self.outpost = outpost self.outpost = outpost
self.connection = connection self.connection = connection
self.logger = get_logger() self.logger = get_logger()
self.deployment_ports = {} self.deployment_ports = []
# pylint: disable=invalid-name # pylint: disable=invalid-name
def up(self): def up(self):
"""Called by scheduled task to reconcile deployment/service/etc""" """Called by scheduled task to reconcile deployment/service/etc"""
raise NotImplementedError raise NotImplementedError
def up_with_logs(self) -> List[str]: def up_with_logs(self) -> list[str]:
"""Call .up() but capture all log output and return it.""" """Call .up() but capture all log output and return it."""
with capture_logs() as logs: with capture_logs() as logs:
self.up() self.up()

View File

@ -68,7 +68,10 @@ class DockerController(BaseController):
"image": image_name, "image": image_name,
"name": f"authentik-proxy-{self.outpost.uuid.hex}", "name": f"authentik-proxy-{self.outpost.uuid.hex}",
"detach": True, "detach": True,
"ports": {x: x for _, x in self.deployment_ports.items()}, "ports": {
f"{port.port}/{port.protocol.lower()}": port.port
for port in self.deployment_ports
},
"environment": self._get_env(), "environment": self._get_env(),
"labels": self._get_labels(), "labels": self._get_labels(),
} }
@ -139,7 +142,10 @@ class DockerController(BaseController):
def get_static_deployment(self) -> str: def get_static_deployment(self) -> str:
"""Generate docker-compose yaml for proxy, version 3.5""" """Generate docker-compose yaml for proxy, version 3.5"""
ports = [f"{x}:{x}" for _, x in self.deployment_ports.items()] ports = [
f"{port.port}:{port.port}/{port.protocol.lower()}"
for port in self.deployment_ports
]
image_prefix = CONFIG.y("outposts.docker_image_base") image_prefix = CONFIG.y("outposts.docker_image_base")
compose = { compose = {
"version": "3.5", "version": "3.5",

View File

@ -93,7 +93,8 @@ class KubernetesObjectReconciler(Generic[T]):
def reconcile(self, current: T, reference: T): def reconcile(self, current: T, reference: T):
"""Check what operations should be done, should be raised as """Check what operations should be done, should be raised as
ReconcileTrigger""" ReconcileTrigger"""
raise NotImplementedError if current.metadata.annotations != reference.metadata.annotations:
raise NeedsUpdate()
def create(self, reference: T): def create(self, reference: T):
"""API Wrapper to create object""" """API Wrapper to create object"""

View File

@ -18,6 +18,7 @@ from kubernetes.client import (
from authentik import __version__ from authentik import __version__
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
from authentik.outposts.controllers.base import FIELD_MANAGER
from authentik.outposts.controllers.k8s.base import ( from authentik.outposts.controllers.k8s.base import (
KubernetesObjectReconciler, KubernetesObjectReconciler,
NeedsUpdate, NeedsUpdate,
@ -43,6 +44,7 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
return f"authentik-outpost-{self.controller.outpost.uuid.hex}" return f"authentik-outpost-{self.controller.outpost.uuid.hex}"
def reconcile(self, current: V1Deployment, reference: V1Deployment): def reconcile(self, current: V1Deployment, reference: V1Deployment):
super().reconcile(current, reference)
if current.spec.replicas != reference.spec.replicas: if current.spec.replicas != reference.spec.replicas:
raise NeedsUpdate() raise NeedsUpdate()
if ( if (
@ -63,8 +65,14 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
"""Get deployment object for outpost""" """Get deployment object for outpost"""
# Generate V1ContainerPort objects # Generate V1ContainerPort objects
container_ports = [] container_ports = []
for port_name, port in self.controller.deployment_ports.items(): for port in self.controller.deployment_ports:
container_ports.append(V1ContainerPort(container_port=port, name=port_name)) container_ports.append(
V1ContainerPort(
container_port=port.port,
name=port.name,
protocol=port.protocol.upper(),
)
)
meta = self.get_object_meta(name=self.name) meta = self.get_object_meta(name=self.name)
secret_name = f"authentik-outpost-{self.controller.outpost.uuid.hex}-api" secret_name = f"authentik-outpost-{self.controller.outpost.uuid.hex}-api"
image_prefix = CONFIG.y("outposts.docker_image_base") image_prefix = CONFIG.y("outposts.docker_image_base")
@ -118,7 +126,9 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
) )
def create(self, reference: V1Deployment): def create(self, reference: V1Deployment):
return self.api.create_namespaced_deployment(self.namespace, reference) return self.api.create_namespaced_deployment(
self.namespace, reference, field_manager=FIELD_MANAGER
)
def delete(self, reference: V1Deployment): def delete(self, reference: V1Deployment):
return self.api.delete_namespaced_deployment( return self.api.delete_namespaced_deployment(

View File

@ -4,6 +4,7 @@ from typing import TYPE_CHECKING
from kubernetes.client import CoreV1Api, V1Secret from kubernetes.client import CoreV1Api, V1Secret
from authentik.outposts.controllers.base import FIELD_MANAGER
from authentik.outposts.controllers.k8s.base import ( from authentik.outposts.controllers.k8s.base import (
KubernetesObjectReconciler, KubernetesObjectReconciler,
NeedsUpdate, NeedsUpdate,
@ -30,6 +31,7 @@ class SecretReconciler(KubernetesObjectReconciler[V1Secret]):
return f"authentik-outpost-{self.controller.outpost.uuid.hex}-api" return f"authentik-outpost-{self.controller.outpost.uuid.hex}-api"
def reconcile(self, current: V1Secret, reference: V1Secret): def reconcile(self, current: V1Secret, reference: V1Secret):
super().reconcile(current, reference)
for key in reference.data.keys(): for key in reference.data.keys():
if current.data[key] != reference.data[key]: if current.data[key] != reference.data[key]:
raise NeedsUpdate() raise NeedsUpdate()
@ -51,7 +53,9 @@ class SecretReconciler(KubernetesObjectReconciler[V1Secret]):
) )
def create(self, reference: V1Secret): def create(self, reference: V1Secret):
return self.api.create_namespaced_secret(self.namespace, reference) return self.api.create_namespaced_secret(
self.namespace, reference, field_manager=FIELD_MANAGER
)
def delete(self, reference: V1Secret): def delete(self, reference: V1Secret):
return self.api.delete_namespaced_secret( return self.api.delete_namespaced_secret(

View File

@ -3,6 +3,7 @@ from typing import TYPE_CHECKING
from kubernetes.client import CoreV1Api, V1Service, V1ServicePort, V1ServiceSpec from kubernetes.client import CoreV1Api, V1Service, V1ServicePort, V1ServiceSpec
from authentik.outposts.controllers.base import FIELD_MANAGER
from authentik.outposts.controllers.k8s.base import ( from authentik.outposts.controllers.k8s.base import (
KubernetesObjectReconciler, KubernetesObjectReconciler,
NeedsUpdate, NeedsUpdate,
@ -25,6 +26,7 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]):
return f"authentik-outpost-{self.controller.outpost.uuid.hex}" return f"authentik-outpost-{self.controller.outpost.uuid.hex}"
def reconcile(self, current: V1Service, reference: V1Service): def reconcile(self, current: V1Service, reference: V1Service):
super().reconcile(current, reference)
if len(current.spec.ports) != len(reference.spec.ports): if len(current.spec.ports) != len(reference.spec.ports):
raise NeedsUpdate() raise NeedsUpdate()
for port in reference.spec.ports: for port in reference.spec.ports:
@ -35,8 +37,15 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]):
"""Get deployment object for outpost""" """Get deployment object for outpost"""
meta = self.get_object_meta(name=self.name) meta = self.get_object_meta(name=self.name)
ports = [] ports = []
for port_name, port in self.controller.deployment_ports.items(): for port in self.controller.deployment_ports:
ports.append(V1ServicePort(name=port_name, port=port)) ports.append(
V1ServicePort(
name=port.name,
port=port.port,
protocol=port.protocol.upper(),
target_port=port.port,
)
)
selector_labels = DeploymentReconciler(self.controller).get_pod_meta() selector_labels = DeploymentReconciler(self.controller).get_pod_meta()
return V1Service( return V1Service(
metadata=meta, metadata=meta,
@ -44,7 +53,9 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]):
) )
def create(self, reference: V1Service): def create(self, reference: V1Service):
return self.api.create_namespaced_service(self.namespace, reference) return self.api.create_namespaced_service(
self.namespace, reference, field_manager=FIELD_MANAGER
)
def delete(self, reference: V1Service): def delete(self, reference: V1Service):
return self.api.delete_namespaced_service( return self.api.delete_namespaced_service(

View File

@ -2,6 +2,7 @@
from typing import Dict from typing import Dict
from urllib.parse import urlparse from urllib.parse import urlparse
from authentik.outposts.controllers.base import DeploymentPort
from authentik.outposts.controllers.docker import DockerController from authentik.outposts.controllers.docker import DockerController
from authentik.outposts.models import DockerServiceConnection, Outpost from authentik.outposts.models import DockerServiceConnection, Outpost
from authentik.providers.proxy.models import ProxyProvider from authentik.providers.proxy.models import ProxyProvider
@ -12,10 +13,10 @@ class ProxyDockerController(DockerController):
def __init__(self, outpost: Outpost, connection: DockerServiceConnection): def __init__(self, outpost: Outpost, connection: DockerServiceConnection):
super().__init__(outpost, connection) super().__init__(outpost, connection)
self.deployment_ports = { self.deployment_ports = [
"http": 4180, DeploymentPort(4180, "http", "tcp"),
"https": 4443, DeploymentPort(4443, "https", "tcp"),
} ]
def _get_labels(self) -> Dict[str, str]: def _get_labels(self) -> Dict[str, str]:
hosts = [] hosts = []

View File

@ -15,6 +15,7 @@ from kubernetes.client.models.networking_v1beta1_ingress_rule import (
NetworkingV1beta1IngressRule, NetworkingV1beta1IngressRule,
) )
from authentik.outposts.controllers.base import FIELD_MANAGER
from authentik.outposts.controllers.k8s.base import ( from authentik.outposts.controllers.k8s.base import (
KubernetesObjectReconciler, KubernetesObjectReconciler,
NeedsUpdate, NeedsUpdate,
@ -39,6 +40,7 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]):
def reconcile( def reconcile(
self, current: NetworkingV1beta1Ingress, reference: NetworkingV1beta1Ingress self, current: NetworkingV1beta1Ingress, reference: NetworkingV1beta1Ingress
): ):
super().reconcile(current, reference)
# Create a list of all expected host and tls hosts # Create a list of all expected host and tls hosts
expected_hosts = [] expected_hosts = []
expected_hosts_tls = [] expected_hosts_tls = []
@ -104,7 +106,7 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]):
NetworkingV1beta1HTTPIngressPath( NetworkingV1beta1HTTPIngressPath(
backend=NetworkingV1beta1IngressBackend( backend=NetworkingV1beta1IngressBackend(
service_name=self.name, service_name=self.name,
service_port=self.controller.deployment_ports["http"], service_port="http",
), ),
path="/", path="/",
) )
@ -124,7 +126,9 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]):
) )
def create(self, reference: NetworkingV1beta1Ingress): def create(self, reference: NetworkingV1beta1Ingress):
return self.api.create_namespaced_ingress(self.namespace, reference) return self.api.create_namespaced_ingress(
self.namespace, reference, field_manager=FIELD_MANAGER
)
def delete(self, reference: NetworkingV1beta1Ingress): def delete(self, reference: NetworkingV1beta1Ingress):
return self.api.delete_namespaced_ingress( return self.api.delete_namespaced_ingress(

View File

@ -1,4 +1,5 @@
"""Proxy Provider Kubernetes Contoller""" """Proxy Provider Kubernetes Contoller"""
from authentik.outposts.controllers.base import DeploymentPort
from authentik.outposts.controllers.kubernetes import KubernetesController from authentik.outposts.controllers.kubernetes import KubernetesController
from authentik.outposts.models import KubernetesServiceConnection, Outpost from authentik.outposts.models import KubernetesServiceConnection, Outpost
from authentik.providers.proxy.controllers.k8s.ingress import IngressReconciler from authentik.providers.proxy.controllers.k8s.ingress import IngressReconciler
@ -9,9 +10,9 @@ class ProxyKubernetesController(KubernetesController):
def __init__(self, outpost: Outpost, connection: KubernetesServiceConnection): def __init__(self, outpost: Outpost, connection: KubernetesServiceConnection):
super().__init__(outpost, connection) super().__init__(outpost, connection)
self.deployment_ports = { self.deployment_ports = [
"http": 4180, DeploymentPort(4180, "http", "tcp"),
"https": 4443, DeploymentPort(4443, "https", "tcp"),
} ]
self.reconcilers["ingress"] = IngressReconciler self.reconcilers["ingress"] = IngressReconciler
self.reconcile_order.append("ingress") self.reconcile_order.append("ingress")