authentik.outposts.controllers.k8s.deployment
Kubernetes Deployment Reconciler
1"""Kubernetes Deployment Reconciler""" 2 3from typing import TYPE_CHECKING 4 5from django.utils.text import slugify 6from kubernetes.client import ( 7 AppsV1Api, 8 V1Capabilities, 9 V1Container, 10 V1ContainerPort, 11 V1Deployment, 12 V1DeploymentSpec, 13 V1EnvVar, 14 V1EnvVarSource, 15 V1LabelSelector, 16 V1ObjectMeta, 17 V1ObjectReference, 18 V1PodSecurityContext, 19 V1PodSpec, 20 V1PodTemplateSpec, 21 V1SeccompProfile, 22 V1SecretKeySelector, 23 V1SecurityContext, 24) 25 26from authentik import authentik_full_version 27from authentik.outposts.controllers.base import FIELD_MANAGER 28from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler 29from authentik.outposts.controllers.k8s.triggers import NeedsUpdate 30from authentik.outposts.controllers.k8s.utils import compare_ports 31from authentik.outposts.models import Outpost 32 33if TYPE_CHECKING: 34 from authentik.outposts.controllers.kubernetes import KubernetesController 35 36 37class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]): 38 """Kubernetes Deployment Reconciler""" 39 40 outpost: Outpost 41 42 def __init__(self, controller: KubernetesController) -> None: 43 super().__init__(controller) 44 self.api = AppsV1Api(controller.client) 45 self.outpost = self.controller.outpost 46 47 @property 48 def noop(self) -> bool: 49 return self.is_embedded 50 51 @staticmethod 52 def reconciler_name() -> str: 53 return "deployment" 54 55 def reconcile(self, current: V1Deployment, reference: V1Deployment): 56 compare_ports( 57 current.spec.template.spec.containers[0].ports, 58 reference.spec.template.spec.containers[0].ports, 59 ) 60 if current.spec.replicas != reference.spec.replicas: 61 raise NeedsUpdate() 62 if ( 63 current.spec.template.spec.containers[0].image 64 != reference.spec.template.spec.containers[0].image 65 ): 66 raise NeedsUpdate() 67 super().reconcile(current, reference) 68 69 def get_pod_meta(self, **kwargs) -> dict[str, str]: 70 """Get common object metadata""" 71 kwargs.update( 72 { 73 "app.kubernetes.io/name": f"authentik-outpost-{self.outpost.type}", 74 "app.kubernetes.io/managed-by": "goauthentik.io", 75 "goauthentik.io/outpost-uuid": self.controller.outpost.uuid.hex, 76 "goauthentik.io/outpost-name": slugify(self.controller.outpost.name), 77 "goauthentik.io/outpost-type": str(self.controller.outpost.type), 78 } 79 ) 80 return kwargs 81 82 def get_reference_object(self) -> V1Deployment: 83 """Get deployment object for outpost""" 84 # Generate V1ContainerPort objects 85 container_ports = [] 86 for port in self.controller.deployment_ports: 87 container_ports.append( 88 V1ContainerPort( 89 container_port=port.inner_port or port.port, 90 name=port.name, 91 protocol=port.protocol.upper(), 92 ) 93 ) 94 meta = self.get_object_meta(name=self.name) 95 image_name = self.controller.get_container_image() 96 image_pull_secrets = self.outpost.config.kubernetes_image_pull_secrets 97 version = authentik_full_version().replace("+", "-") 98 return V1Deployment( 99 metadata=meta, 100 spec=V1DeploymentSpec( 101 replicas=self.outpost.config.kubernetes_replicas, 102 selector=V1LabelSelector(match_labels=self.get_pod_meta()), 103 template=V1PodTemplateSpec( 104 metadata=V1ObjectMeta( 105 labels=self.get_pod_meta( 106 **{ 107 # Support istio-specific labels, but also use the standard k8s 108 # recommendations 109 "app.kubernetes.io/version": version, 110 "app": "authentik-outpost", 111 "version": version, 112 } 113 ) 114 ), 115 spec=V1PodSpec( 116 image_pull_secrets=[ 117 V1ObjectReference(name=secret) for secret in image_pull_secrets 118 ], 119 security_context=V1PodSecurityContext( 120 seccomp_profile=V1SeccompProfile( 121 type="RuntimeDefault", 122 ), 123 ), 124 containers=[ 125 V1Container( 126 name=str(self.outpost.type), 127 image=image_name, 128 ports=container_ports, 129 env=[ 130 V1EnvVar( 131 name="AUTHENTIK_HOST", 132 value_from=V1EnvVarSource( 133 secret_key_ref=V1SecretKeySelector( 134 name=self.name, 135 key="authentik_host", 136 ) 137 ), 138 ), 139 V1EnvVar( 140 name="AUTHENTIK_HOST_BROWSER", 141 value_from=V1EnvVarSource( 142 secret_key_ref=V1SecretKeySelector( 143 name=self.name, 144 key="authentik_host_browser", 145 ) 146 ), 147 ), 148 V1EnvVar( 149 name="AUTHENTIK_TOKEN", 150 value_from=V1EnvVarSource( 151 secret_key_ref=V1SecretKeySelector( 152 name=self.name, 153 key="token", 154 ) 155 ), 156 ), 157 V1EnvVar( 158 name="AUTHENTIK_INSECURE", 159 value_from=V1EnvVarSource( 160 secret_key_ref=V1SecretKeySelector( 161 name=self.name, 162 key="authentik_host_insecure", 163 ) 164 ), 165 ), 166 ], 167 security_context=V1SecurityContext( 168 run_as_non_root=True, 169 allow_privilege_escalation=False, 170 capabilities=V1Capabilities( 171 drop=["ALL"], 172 ), 173 ), 174 ) 175 ], 176 ), 177 ), 178 ), 179 ) 180 181 def create(self, reference: V1Deployment): 182 return self.api.create_namespaced_deployment( 183 self.namespace, reference, field_manager=FIELD_MANAGER 184 ) 185 186 def delete(self, reference: V1Deployment): 187 return self.api.delete_namespaced_deployment(reference.metadata.name, self.namespace) 188 189 def retrieve(self) -> V1Deployment: 190 return self.api.read_namespaced_deployment(self.name, self.namespace) 191 192 def update(self, current: V1Deployment, reference: V1Deployment): 193 return self.api.patch_namespaced_deployment( 194 current.metadata.name, 195 self.namespace, 196 reference, 197 field_manager=FIELD_MANAGER, 198 )
class
DeploymentReconciler(authentik.outposts.controllers.k8s.base.KubernetesObjectReconciler[kubernetes.client.models.v1_deployment.V1Deployment]):
38class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]): 39 """Kubernetes Deployment Reconciler""" 40 41 outpost: Outpost 42 43 def __init__(self, controller: KubernetesController) -> None: 44 super().__init__(controller) 45 self.api = AppsV1Api(controller.client) 46 self.outpost = self.controller.outpost 47 48 @property 49 def noop(self) -> bool: 50 return self.is_embedded 51 52 @staticmethod 53 def reconciler_name() -> str: 54 return "deployment" 55 56 def reconcile(self, current: V1Deployment, reference: V1Deployment): 57 compare_ports( 58 current.spec.template.spec.containers[0].ports, 59 reference.spec.template.spec.containers[0].ports, 60 ) 61 if current.spec.replicas != reference.spec.replicas: 62 raise NeedsUpdate() 63 if ( 64 current.spec.template.spec.containers[0].image 65 != reference.spec.template.spec.containers[0].image 66 ): 67 raise NeedsUpdate() 68 super().reconcile(current, reference) 69 70 def get_pod_meta(self, **kwargs) -> dict[str, str]: 71 """Get common object metadata""" 72 kwargs.update( 73 { 74 "app.kubernetes.io/name": f"authentik-outpost-{self.outpost.type}", 75 "app.kubernetes.io/managed-by": "goauthentik.io", 76 "goauthentik.io/outpost-uuid": self.controller.outpost.uuid.hex, 77 "goauthentik.io/outpost-name": slugify(self.controller.outpost.name), 78 "goauthentik.io/outpost-type": str(self.controller.outpost.type), 79 } 80 ) 81 return kwargs 82 83 def get_reference_object(self) -> V1Deployment: 84 """Get deployment object for outpost""" 85 # Generate V1ContainerPort objects 86 container_ports = [] 87 for port in self.controller.deployment_ports: 88 container_ports.append( 89 V1ContainerPort( 90 container_port=port.inner_port or port.port, 91 name=port.name, 92 protocol=port.protocol.upper(), 93 ) 94 ) 95 meta = self.get_object_meta(name=self.name) 96 image_name = self.controller.get_container_image() 97 image_pull_secrets = self.outpost.config.kubernetes_image_pull_secrets 98 version = authentik_full_version().replace("+", "-") 99 return V1Deployment( 100 metadata=meta, 101 spec=V1DeploymentSpec( 102 replicas=self.outpost.config.kubernetes_replicas, 103 selector=V1LabelSelector(match_labels=self.get_pod_meta()), 104 template=V1PodTemplateSpec( 105 metadata=V1ObjectMeta( 106 labels=self.get_pod_meta( 107 **{ 108 # Support istio-specific labels, but also use the standard k8s 109 # recommendations 110 "app.kubernetes.io/version": version, 111 "app": "authentik-outpost", 112 "version": version, 113 } 114 ) 115 ), 116 spec=V1PodSpec( 117 image_pull_secrets=[ 118 V1ObjectReference(name=secret) for secret in image_pull_secrets 119 ], 120 security_context=V1PodSecurityContext( 121 seccomp_profile=V1SeccompProfile( 122 type="RuntimeDefault", 123 ), 124 ), 125 containers=[ 126 V1Container( 127 name=str(self.outpost.type), 128 image=image_name, 129 ports=container_ports, 130 env=[ 131 V1EnvVar( 132 name="AUTHENTIK_HOST", 133 value_from=V1EnvVarSource( 134 secret_key_ref=V1SecretKeySelector( 135 name=self.name, 136 key="authentik_host", 137 ) 138 ), 139 ), 140 V1EnvVar( 141 name="AUTHENTIK_HOST_BROWSER", 142 value_from=V1EnvVarSource( 143 secret_key_ref=V1SecretKeySelector( 144 name=self.name, 145 key="authentik_host_browser", 146 ) 147 ), 148 ), 149 V1EnvVar( 150 name="AUTHENTIK_TOKEN", 151 value_from=V1EnvVarSource( 152 secret_key_ref=V1SecretKeySelector( 153 name=self.name, 154 key="token", 155 ) 156 ), 157 ), 158 V1EnvVar( 159 name="AUTHENTIK_INSECURE", 160 value_from=V1EnvVarSource( 161 secret_key_ref=V1SecretKeySelector( 162 name=self.name, 163 key="authentik_host_insecure", 164 ) 165 ), 166 ), 167 ], 168 security_context=V1SecurityContext( 169 run_as_non_root=True, 170 allow_privilege_escalation=False, 171 capabilities=V1Capabilities( 172 drop=["ALL"], 173 ), 174 ), 175 ) 176 ], 177 ), 178 ), 179 ), 180 ) 181 182 def create(self, reference: V1Deployment): 183 return self.api.create_namespaced_deployment( 184 self.namespace, reference, field_manager=FIELD_MANAGER 185 ) 186 187 def delete(self, reference: V1Deployment): 188 return self.api.delete_namespaced_deployment(reference.metadata.name, self.namespace) 189 190 def retrieve(self) -> V1Deployment: 191 return self.api.read_namespaced_deployment(self.name, self.namespace) 192 193 def update(self, current: V1Deployment, reference: V1Deployment): 194 return self.api.patch_namespaced_deployment( 195 current.metadata.name, 196 self.namespace, 197 reference, 198 field_manager=FIELD_MANAGER, 199 )
Kubernetes Deployment Reconciler
outpost: authentik.outposts.models.Outpost
@staticmethod
def
reconciler_name() -> str:
A name this reconciler is identified by in the configuration
def
reconcile( self, current: kubernetes.client.models.v1_deployment.V1Deployment, reference: kubernetes.client.models.v1_deployment.V1Deployment):
56 def reconcile(self, current: V1Deployment, reference: V1Deployment): 57 compare_ports( 58 current.spec.template.spec.containers[0].ports, 59 reference.spec.template.spec.containers[0].ports, 60 ) 61 if current.spec.replicas != reference.spec.replicas: 62 raise NeedsUpdate() 63 if ( 64 current.spec.template.spec.containers[0].image 65 != reference.spec.template.spec.containers[0].image 66 ): 67 raise NeedsUpdate() 68 super().reconcile(current, reference)
Check what operations should be done, should be raised as ReconcileTrigger
def
get_pod_meta(self, **kwargs) -> dict[str, str]:
70 def get_pod_meta(self, **kwargs) -> dict[str, str]: 71 """Get common object metadata""" 72 kwargs.update( 73 { 74 "app.kubernetes.io/name": f"authentik-outpost-{self.outpost.type}", 75 "app.kubernetes.io/managed-by": "goauthentik.io", 76 "goauthentik.io/outpost-uuid": self.controller.outpost.uuid.hex, 77 "goauthentik.io/outpost-name": slugify(self.controller.outpost.name), 78 "goauthentik.io/outpost-type": str(self.controller.outpost.type), 79 } 80 ) 81 return kwargs
Get common object metadata
def
get_reference_object(self) -> kubernetes.client.models.v1_deployment.V1Deployment:
83 def get_reference_object(self) -> V1Deployment: 84 """Get deployment object for outpost""" 85 # Generate V1ContainerPort objects 86 container_ports = [] 87 for port in self.controller.deployment_ports: 88 container_ports.append( 89 V1ContainerPort( 90 container_port=port.inner_port or port.port, 91 name=port.name, 92 protocol=port.protocol.upper(), 93 ) 94 ) 95 meta = self.get_object_meta(name=self.name) 96 image_name = self.controller.get_container_image() 97 image_pull_secrets = self.outpost.config.kubernetes_image_pull_secrets 98 version = authentik_full_version().replace("+", "-") 99 return V1Deployment( 100 metadata=meta, 101 spec=V1DeploymentSpec( 102 replicas=self.outpost.config.kubernetes_replicas, 103 selector=V1LabelSelector(match_labels=self.get_pod_meta()), 104 template=V1PodTemplateSpec( 105 metadata=V1ObjectMeta( 106 labels=self.get_pod_meta( 107 **{ 108 # Support istio-specific labels, but also use the standard k8s 109 # recommendations 110 "app.kubernetes.io/version": version, 111 "app": "authentik-outpost", 112 "version": version, 113 } 114 ) 115 ), 116 spec=V1PodSpec( 117 image_pull_secrets=[ 118 V1ObjectReference(name=secret) for secret in image_pull_secrets 119 ], 120 security_context=V1PodSecurityContext( 121 seccomp_profile=V1SeccompProfile( 122 type="RuntimeDefault", 123 ), 124 ), 125 containers=[ 126 V1Container( 127 name=str(self.outpost.type), 128 image=image_name, 129 ports=container_ports, 130 env=[ 131 V1EnvVar( 132 name="AUTHENTIK_HOST", 133 value_from=V1EnvVarSource( 134 secret_key_ref=V1SecretKeySelector( 135 name=self.name, 136 key="authentik_host", 137 ) 138 ), 139 ), 140 V1EnvVar( 141 name="AUTHENTIK_HOST_BROWSER", 142 value_from=V1EnvVarSource( 143 secret_key_ref=V1SecretKeySelector( 144 name=self.name, 145 key="authentik_host_browser", 146 ) 147 ), 148 ), 149 V1EnvVar( 150 name="AUTHENTIK_TOKEN", 151 value_from=V1EnvVarSource( 152 secret_key_ref=V1SecretKeySelector( 153 name=self.name, 154 key="token", 155 ) 156 ), 157 ), 158 V1EnvVar( 159 name="AUTHENTIK_INSECURE", 160 value_from=V1EnvVarSource( 161 secret_key_ref=V1SecretKeySelector( 162 name=self.name, 163 key="authentik_host_insecure", 164 ) 165 ), 166 ), 167 ], 168 security_context=V1SecurityContext( 169 run_as_non_root=True, 170 allow_privilege_escalation=False, 171 capabilities=V1Capabilities( 172 drop=["ALL"], 173 ), 174 ), 175 ) 176 ], 177 ), 178 ), 179 ), 180 )
Get deployment object for outpost
def
create(self, reference: kubernetes.client.models.v1_deployment.V1Deployment):
182 def create(self, reference: V1Deployment): 183 return self.api.create_namespaced_deployment( 184 self.namespace, reference, field_manager=FIELD_MANAGER 185 )
API Wrapper to create object
def
delete(self, reference: kubernetes.client.models.v1_deployment.V1Deployment):
187 def delete(self, reference: V1Deployment): 188 return self.api.delete_namespaced_deployment(reference.metadata.name, self.namespace)
API Wrapper to delete object
def
retrieve(self) -> kubernetes.client.models.v1_deployment.V1Deployment:
190 def retrieve(self) -> V1Deployment: 191 return self.api.read_namespaced_deployment(self.name, self.namespace)
API Wrapper to retrieve object
def
update( self, current: kubernetes.client.models.v1_deployment.V1Deployment, reference: kubernetes.client.models.v1_deployment.V1Deployment):
193 def update(self, current: V1Deployment, reference: V1Deployment): 194 return self.api.patch_namespaced_deployment( 195 current.metadata.name, 196 self.namespace, 197 reference, 198 field_manager=FIELD_MANAGER, 199 )
API Wrapper to update object