fix(ingress): scope host loadBalancerClass to the external-facing ingress

publishing.loadBalancerClass is a host-publishing setting, but its
_cluster.load-balancer-class block was emitted on any ingress taking the
LoadBalancer branch — including tenant ingresses — unlike expose-external-ips
and proxy-protocol, which gate on the host namespace. A tenant ingress would
then be claimed by an LB controller the tenant never selected. Gate it the
same way, and add helm-unittest coverage for the host/tenant split and the
platform to _cluster wiring.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
This commit is contained in:
Aleksei Sviridkin committed 2026-07-07 12:39:30 +03:00
1 parent 3dbc7d3662
commit a1748009ac
3 files changed
+104 -2

No files matched your search

@@ -0,0 +1,29 @@
suite: apps.yaml publishing.loadBalancerClass → _cluster wiring
templates:
- templates/apps.yaml
release:
name: cozy-platform
namespace: cozy-system
# publishing.loadBalancerClass selects which LoadBalancer controller handles the
# host ingress Service. It reaches the host ingress-nginx through
# _cluster.load-balancer-class in the cozystack-values Secret (the same path
# expose-external-ips and proxy-protocol travel). These assertions pin that the
# value lands in the Secret; without them the host ingress would silently never
# see the configured loadBalancerClass.
tests:
- it: writes an empty load-balancer-class by default
asserts:
- matchRegex:
path: stringData["values.yaml"]
pattern: '(?m)^\s*load-balancer-class:\s*""'
- it: writes the load-balancer-class when publishing.loadBalancerClass is set
set:
publishing.loadBalancerClass: metallb.io/metallb
asserts:
- matchRegex:
path: stringData["values.yaml"]
pattern: '(?m)^\s*load-balancer-class:\s*"metallb\.io/metallb"'
@@ -82,8 +82,12 @@ spec:
{{- else }}
type: LoadBalancer
externalTrafficPolicy: Local
{{- with $loadBalancerClass }}
loadBalancerClass: {{ . }}
{{- /* loadBalancerClass is a host-publishing setting: apply it only to
the external-facing ingress (the namespace named by expose-ingress),
host-scoped like expose-external-ips and proxy-protocol above.
Tenant ingress Services keep the cluster's default LB controller. */}}
{{- if and $loadBalancerClass (eq $exposeIngress .Release.Namespace) }}
loadBalancerClass: {{ $loadBalancerClass }}
{{- end }}
{{- end }}
{{- if or .Values.whitelist .Values.cloudflareProxy $proxyProtocol }}
@@ -0,0 +1,69 @@
suite: ingress loadBalancerClass wiring
templates:
- templates/nginx-ingress.yaml
release:
name: ingress
# publishing.loadBalancerClass is a host-publishing setting carried in
# _cluster.load-balancer-class. It must land on the external-facing (host)
# ingress Service only — host-scoped like expose-external-ips and
# proxy-protocol. These assertions pin that scope: a tenant ingress must NOT
# inherit the host's loadBalancerClass, or its Service would be claimed by an
# LB controller the tenant never asked for.
tests:
- it: sets loadBalancerClass on the host ingress LoadBalancer Service
set:
_cluster:
expose-ingress: tenant-root
load-balancer-class: metallb.io/metallb
release:
namespace: tenant-root
asserts:
- equal:
path: spec.values.ingress-nginx.controller.service.type
value: LoadBalancer
- equal:
path: spec.values.ingress-nginx.controller.service.loadBalancerClass
value: metallb.io/metallb
- it: does not leak the host loadBalancerClass onto a tenant ingress
set:
_cluster:
expose-ingress: tenant-root
load-balancer-class: metallb.io/metallb
release:
namespace: tenant-foo
asserts:
- equal:
path: spec.values.ingress-nginx.controller.service.type
value: LoadBalancer
- notExists:
path: spec.values.ingress-nginx.controller.service.loadBalancerClass
- it: keeps the externalIPs path untouched by loadBalancerClass
set:
_cluster:
expose-ingress: tenant-root
expose-external-ips: 203.0.113.10
load-balancer-class: metallb.io/metallb
release:
namespace: tenant-root
asserts:
- equal:
path: spec.values.ingress-nginx.controller.service.type
value: ClusterIP
- notExists:
path: spec.values.ingress-nginx.controller.service.loadBalancerClass
- it: omits loadBalancerClass on the host ingress when none is configured
set:
_cluster:
expose-ingress: tenant-root
release:
namespace: tenant-root
asserts:
- equal:
path: spec.values.ingress-nginx.controller.service.type
value: LoadBalancer
- notExists:
path: spec.values.ingress-nginx.controller.service.loadBalancerClass