From 4e9151318a3d70dab0f80120669bac75d3b618b0 Mon Sep 17 00:00:00 2001 From: David Boreham Date: Tue, 21 Nov 2023 13:22:44 -0700 Subject: [PATCH] Add ingress object --- stack_orchestrator/constants.py | 2 + stack_orchestrator/deploy/k8s/cluster_info.py | 64 +++++++++++++++++-- stack_orchestrator/deploy/k8s/deploy_k8s.py | 27 +++++++- stack_orchestrator/deploy/spec.py | 12 ++++ 4 files changed, 98 insertions(+), 7 deletions(-) diff --git a/stack_orchestrator/constants.py b/stack_orchestrator/constants.py index aedc4f3c..1cff6055 100644 --- a/stack_orchestrator/constants.py +++ b/stack_orchestrator/constants.py @@ -19,6 +19,8 @@ k8s_kind_deploy_type = "k8s-kind" k8s_deploy_type = "k8s" kube_config_key = "kube-config" deploy_to_key = "deploy-to" +network_key = "network" +http_proxy_key = "http-proxy" image_resigtry_key = "image-registry" kind_config_filename = "kind-config.yml" kube_config_filename = "kubeconfig.yml" diff --git a/stack_orchestrator/deploy/k8s/cluster_info.py b/stack_orchestrator/deploy/k8s/cluster_info.py index ff052bf9..f8c2ce94 100644 --- a/stack_orchestrator/deploy/k8s/cluster_info.py +++ b/stack_orchestrator/deploy/k8s/cluster_info.py @@ -22,6 +22,7 @@ from stack_orchestrator.deploy.k8s.helpers import get_node_pv_mount_path from stack_orchestrator.deploy.k8s.helpers import env_var_map_from_file, envs_from_environment_variables_map from stack_orchestrator.deploy.deploy_util import parsed_pod_files_map_from_file_names, images_for_deployment from stack_orchestrator.deploy.deploy_types import DeployEnvVars +from stack_orchestrator.deploy.spec import Spec from stack_orchestrator.deploy.images import remote_tag_for_image @@ -31,20 +32,74 @@ class ClusterInfo: app_name: str = "test-app" deployment_name: str = "test-deployment" environment_variables: DeployEnvVars - remote_image_repo: str + spec: Spec def __init__(self) -> None: pass - def int(self, pod_files: List[str], compose_env_file, remote_image_repo): + def int(self, pod_files: List[str], compose_env_file, spec: Spec): self.parsed_pod_yaml_map = parsed_pod_files_map_from_file_names(pod_files) # Find the set of images in the pods self.image_set = images_for_deployment(pod_files) self.environment_variables = DeployEnvVars(env_var_map_from_file(compose_env_file)) - self.remote_image_repo = remote_image_repo + self.spec = spec if (opts.o.debug): print(f"Env vars: {self.environment_variables.map}") + def get_ingress(self): + # No ingress for a deployment that has no http-proxy defined, for now + http_proxy_info = self.spec.get_http_proxy() + ingress = None + if http_proxy_info: + if opts.o.debug: + print(f"http-proxy: {http_proxy_info}") + # TODO: good enough parsing for webapp deployment for now + host_name = http_proxy_info["host-name"] + rules = [] + tls = [client.V1IngressTLS( + hosts=[host_name], + secret_name=f"{self.app_name}-tls" + )] + paths = [] + for route in http_proxy_info["routes"]: + path = route["path"] + proxy_to = route["proxy-to"] + if opts.o.debug: + print(f"proxy config: {path} -> {proxy_to}") + paths.append(client.V1HTTPIngressPath( + path_type="Prefix", + path=path, + backend=client.V1IngressBackend( + service=client.V1IngressServiceBackend( + # TODO: this looks wrong + name=self.deployment_name, + # TODO: pull port number from the service + port=client.V1ServiceBackendPort(number=80) + ) + ) + )) + rules.append(client.V1IngressRule( + host=host_name, + http=client.V1HTTPIngressRuleValue( + paths=paths + ) + )) + spec = client.V1IngressSpec( + tls=tls, + rules=rules + ) + ingress = client.V1Ingress( + metadata=client.V1ObjectMeta( + name=f"{self.app_name}-ingress", + annotations={ + "kubernetes.io/ingress.class": "nginx", + "cert-manager.io/cluster-issuer": "letsencrypt-prod" + } + ), + spec=spec + ) + return ingress + def get_pvcs(self): result = [] volumes = named_volumes_from_pod_files(self.parsed_pod_yaml_map) @@ -96,7 +151,8 @@ class ClusterInfo: service_info = services[service_name] image = service_info["image"] # Re-write the image tag for remote deployment - image_to_use = remote_tag_for_image(image, self.remote_image_repo) if self.remote_image_repo is not None else image + image_to_use = remote_tag_for_image( + image, self.spec.get_image_registry()) if self.spec.get_image_registry() is not None else image volume_mounts = volume_mounts_for_service(self.parsed_pod_yaml_map, service_name) container = client.V1Container( name=container_name, diff --git a/stack_orchestrator/deploy/k8s/deploy_k8s.py b/stack_orchestrator/deploy/k8s/deploy_k8s.py index 483f64c6..2e1f6e90 100644 --- a/stack_orchestrator/deploy/k8s/deploy_k8s.py +++ b/stack_orchestrator/deploy/k8s/deploy_k8s.py @@ -30,6 +30,7 @@ class K8sDeployer(Deployer): type: str core_api: client.CoreV1Api apps_api: client.AppsV1Api + networking_api: client.NetworkingV1Api k8s_namespace: str = "default" kind_cluster_name: str cluster_info : ClusterInfo @@ -45,7 +46,7 @@ class K8sDeployer(Deployer): self.deployment_context = deployment_context self.kind_cluster_name = compose_project_name self.cluster_info = ClusterInfo() - self.cluster_info.int(compose_files, compose_env_file, deployment_context.spec.obj[constants.image_resigtry_key]) + self.cluster_info.int(compose_files, compose_env_file, deployment_context.spec) if (opts.o.debug): print(f"Deployment dir: {deployment_context.deployment_dir}") print(f"Compose files: {compose_files}") @@ -60,9 +61,11 @@ class K8sDeployer(Deployer): # Get the config file and pass to load_kube_config() config.load_kube_config(config_file=self.deployment_dir.joinpath(constants.kube_config_filename).as_posix()) self.core_api = client.CoreV1Api() + self.networking_api = client.NetworkingV1Api() self.apps_api = client.AppsV1Api() def up(self, detach, services): + if self.is_kind(): # Create the kind cluster create_cluster(self.kind_cluster_name, self.deployment_dir.joinpath(constants.kind_config_filename)) @@ -102,6 +105,17 @@ class K8sDeployer(Deployer): print(f"{deployment_resp.metadata.namespace} {deployment_resp.metadata.name} \ {deployment_resp.metadata.generation} {deployment_resp.spec.template.spec.containers[0].image}") + # TODO: disable ingress for kind + ingress: client.V1Ingress = self.cluster_info.get_ingress() + + ingress_resp = self.networking_api.create_namespaced_ingress( + namespace=self.k8s_namespace, + body=ingress + ) + if opts.o.debug: + print("Ingress created:") + print(f"{ingress_resp}") + def down(self, timeout, volumes): self.connect_api() # Delete the k8s objects @@ -124,14 +138,21 @@ class K8sDeployer(Deployer): if opts.o.debug: print("PVCs deleted:") print(f"{pvc_resp}") - # Process compose files into a Deployment deployment = self.cluster_info.get_deployment() - # Create the k8s objects if opts.o.debug: print(f"Deleting this deployment: {deployment}") self.apps_api.delete_namespaced_deployment( name=deployment.metadata.name, namespace=self.k8s_namespace ) + + # TODO: disable ingress for kind + ingress: client.V1Ingress = self.cluster_info.get_ingress() + if opts.o.debug: + print(f"Deleting this ingress: {ingress}") + self.apps_api.delete_namespaced_ingress( + name=ingress.metadata.name, namespace=self.k8s_namespace + ) + if self.is_kind(): # Destroy the kind cluster destroy_cluster(self.kind_cluster_name) diff --git a/stack_orchestrator/deploy/spec.py b/stack_orchestrator/deploy/spec.py index 9ee893b9..c4f791bf 100644 --- a/stack_orchestrator/deploy/spec.py +++ b/stack_orchestrator/deploy/spec.py @@ -16,6 +16,7 @@ from pathlib import Path import typing from stack_orchestrator.util import get_yaml +from stack_orchestrator import constants class Spec: @@ -28,3 +29,14 @@ class Spec: def init_from_file(self, file_path: Path): with file_path: self.obj = get_yaml().load(open(file_path, "r")) + + def get_image_registry(self): + return (self.obj[constants.image_resigtry_key] + if self.obj and constants.image_resigtry_key in self.obj + else None) + + def get_http_proxy(self): + return (self.obj[constants.network_key][constants.http_proxy_key] + if self.obj and constants.network_key in self.obj + and constants.http_proxy_key in self.obj[constants.network_key] + else None)