From 8d3191e4fdaabe72f0221b5471e80542b2da8fc1 Mon Sep 17 00:00:00 2001 From: "A. F. Dudley" Date: Mon, 2 Feb 2026 19:13:10 -0500 Subject: [PATCH] Fix Caddy ingress ACME email and RBAC issues - Add acme_email_key constant for spec.yml parsing - Add get_acme_email() method to Spec class - Modify install_ingress_for_kind() to patch ConfigMap with email - Pass acme-email from spec to ingress installation - Add 'delete' verb to leases RBAC for certificate lock cleanup The acme-email field in spec.yml was previously ignored, causing Let's Encrypt to fail with "unable to parse email address". The missing delete permission on leases caused lock cleanup failures. Co-Authored-By: Claude Opus 4.5 --- stack_orchestrator/constants.py | 1 + .../ingress/ingress-caddy-kind-deploy.yaml | 1 + stack_orchestrator/deploy/k8s/deploy_k8s.py | 2 +- stack_orchestrator/deploy/k8s/helpers.py | 17 ++++++++++++++++- stack_orchestrator/deploy/spec.py | 3 +++ 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/stack_orchestrator/constants.py b/stack_orchestrator/constants.py index 49dfa193..75bd0ebc 100644 --- a/stack_orchestrator/constants.py +++ b/stack_orchestrator/constants.py @@ -44,3 +44,4 @@ unlimited_memlock_key = "unlimited-memlock" runtime_class_key = "runtime-class" high_memlock_runtime = "high-memlock" high_memlock_spec_filename = "high-memlock-spec.json" +acme_email_key = "acme-email" diff --git a/stack_orchestrator/data/k8s/components/ingress/ingress-caddy-kind-deploy.yaml b/stack_orchestrator/data/k8s/components/ingress/ingress-caddy-kind-deploy.yaml index 632dcc05..844eb183 100644 --- a/stack_orchestrator/data/k8s/components/ingress/ingress-caddy-kind-deploy.yaml +++ b/stack_orchestrator/data/k8s/components/ingress/ingress-caddy-kind-deploy.yaml @@ -93,6 +93,7 @@ rules: - get - create - update + - delete --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/stack_orchestrator/deploy/k8s/deploy_k8s.py b/stack_orchestrator/deploy/k8s/deploy_k8s.py index 3d0b697c..7b88dd14 100644 --- a/stack_orchestrator/deploy/k8s/deploy_k8s.py +++ b/stack_orchestrator/deploy/k8s/deploy_k8s.py @@ -301,7 +301,7 @@ class K8sDeployer(Deployer): self.connect_api() if self.is_kind() and not self.skip_cluster_management: # Configure ingress controller (not installed by default in kind) - install_ingress_for_kind() + install_ingress_for_kind(self.cluster_info.spec.get_acme_email()) # Wait for ingress to start # (deployment provisioning will fail unless this is done) wait_for_ingress_in_kind() diff --git a/stack_orchestrator/deploy/k8s/helpers.py b/stack_orchestrator/deploy/k8s/helpers.py index f7603b5e..f4e8cf9d 100644 --- a/stack_orchestrator/deploy/k8s/helpers.py +++ b/stack_orchestrator/deploy/k8s/helpers.py @@ -132,7 +132,7 @@ def wait_for_ingress_in_kind(): error_exit("ERROR: Timed out waiting for Caddy ingress to become ready") -def install_ingress_for_kind(): +def install_ingress_for_kind(acme_email: str = ""): api_client = client.ApiClient() ingress_install = os.path.abspath( get_k8s_dir().joinpath( @@ -143,6 +143,21 @@ def install_ingress_for_kind(): print("Installing Caddy ingress controller in kind cluster") utils.create_from_yaml(api_client, yaml_file=ingress_install) + # Patch ConfigMap with acme email if provided + if acme_email: + core_v1 = client.CoreV1Api() + configmap = core_v1.read_namespaced_config_map( + name="caddy-ingress-controller-configmap", namespace="caddy-system" + ) + configmap.data["email"] = acme_email + core_v1.patch_namespaced_config_map( + name="caddy-ingress-controller-configmap", + namespace="caddy-system", + body=configmap, + ) + if opts.o.debug: + print(f"Patched Caddy ConfigMap with email: {acme_email}") + def load_images_into_kind(kind_cluster_name: str, image_set: Set[str]): for image in image_set: diff --git a/stack_orchestrator/deploy/spec.py b/stack_orchestrator/deploy/spec.py index 1713f28a..db7783c9 100644 --- a/stack_orchestrator/deploy/spec.py +++ b/stack_orchestrator/deploy/spec.py @@ -179,6 +179,9 @@ class Spec: def get_deployment_type(self): return self.obj.get(constants.deploy_to_key) + def get_acme_email(self): + return self.obj.get(constants.acme_email_key, "") + def is_kubernetes_deployment(self): return self.get_deployment_type() in [ constants.k8s_kind_deploy_type,