diff --git a/deploy/helm/openshell/templates/_helpers.tpl b/deploy/helm/openshell/templates/_helpers.tpl index 3d9f2f3e0b..4a018ba941 100644 --- a/deploy/helm/openshell/templates/_helpers.tpl +++ b/deploy/helm/openshell/templates/_helpers.tpl @@ -274,6 +274,36 @@ Validate chart values that Helm would otherwise accept silently. {{- if and (eq $workloadKind "statefulset") (gt $replicaCount 1) (not (get $workload "allowMultiReplicaStatefulSet" | default false)) -}} {{- fail "replicaCount > 1 with workload.kind=statefulset requires workload.allowMultiReplicaStatefulSet=true; use workload.kind=deployment for external database-backed multi-replica gateways." -}} {{- end -}} +{{- $servicePortValue := toString .Values.service.port -}} +{{- $healthPortValue := toString .Values.service.healthPort -}} +{{- $metricsPortValue := toString (default 0 .Values.service.metricsPort) -}} +{{- if not (regexMatch `^-?[0-9]+$` $servicePortValue) -}} +{{- fail "service.port must be an integer." -}} +{{- end -}} +{{- if not (regexMatch `^-?[0-9]+$` $healthPortValue) -}} +{{- fail "service.healthPort must be an integer." -}} +{{- end -}} +{{- if not (regexMatch `^-?[0-9]+$` $metricsPortValue) -}} +{{- fail "service.metricsPort must be an integer." -}} +{{- end -}} +{{- $servicePort := int $servicePortValue -}} +{{- $healthPort := int $healthPortValue -}} +{{- $metricsPort := int $metricsPortValue -}} +{{- if or (lt $servicePort 1) (gt $servicePort 65535) -}} +{{- fail "service.port must be between 1 and 65535." -}} +{{- end -}} +{{- if or (lt $healthPort 1) (gt $healthPort 65535) -}} +{{- fail "service.healthPort must be between 1 and 65535 because gateway probes require it." -}} +{{- end -}} +{{- if eq $servicePort $healthPort -}} +{{- fail "service.port and service.healthPort must be different." -}} +{{- end -}} +{{- if and (ne $metricsPort 0) (or (lt $metricsPort 1) (gt $metricsPort 65535)) -}} +{{- fail "service.metricsPort must be 0 (disabled) or between 1 and 65535." -}} +{{- end -}} +{{- if and (ne $metricsPort 0) (or (eq $metricsPort $servicePort) (eq $metricsPort $healthPort)) -}} +{{- fail "service.metricsPort must differ from service.port and service.healthPort." -}} +{{- end -}} {{- $workspaceMode := .Values.server.drivers.kubernetes.workspaceMode | default "shared" -}} {{- if not (has $workspaceMode (list "shared" "managed" "operator")) -}} {{- fail "server.drivers.kubernetes.workspaceMode must be one of: shared, managed, operator." -}} diff --git a/deploy/helm/openshell/tests/gateway_ports_test.yaml b/deploy/helm/openshell/tests/gateway_ports_test.yaml new file mode 100644 index 0000000000..6aa4e76346 --- /dev/null +++ b/deploy/helm/openshell/tests/gateway_ports_test.yaml @@ -0,0 +1,129 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +suite: gateway listener port validation +templates: + - templates/gateway-config.yaml + - templates/statefulset.yaml + +tests: + - it: rejects primary gateway port 0 + template: templates/statefulset.yaml + set: + service.port: 0 + asserts: + - failedTemplate: + errorPattern: "service.port must be between 1 and 65535" + + - it: rejects a primary gateway listener above the valid port range + template: templates/statefulset.yaml + set: + service.port: 65536 + asserts: + - failedTemplate: + errorPattern: "service.port must be between 1 and 65535" + + - it: rejects a disabled health listener because probes require it + template: templates/statefulset.yaml + set: + service.healthPort: 0 + asserts: + - failedTemplate: + errorPattern: "service.healthPort must be between 1 and 65535 because gateway probes require it" + + - it: rejects a health listener above the valid port range + template: templates/statefulset.yaml + set: + service.healthPort: 65536 + asserts: + - failedTemplate: + errorPattern: "service.healthPort must be between 1 and 65535 because gateway probes require it" + + - it: rejects a health listener that collides with the primary listener + template: templates/statefulset.yaml + set: + service.healthPort: 8080 + asserts: + - failedTemplate: + errorPattern: "service.port and service.healthPort must be different" + + - it: rejects a metrics listener that collides with the primary listener + template: templates/statefulset.yaml + set: + service.metricsPort: 8080 + asserts: + - failedTemplate: + errorPattern: "service.metricsPort must differ from service.port and service.healthPort" + + - it: rejects a metrics listener that collides with the health listener + template: templates/statefulset.yaml + set: + service.metricsPort: 8081 + asserts: + - failedTemplate: + errorPattern: "service.metricsPort must differ from service.port and service.healthPort" + + - it: rejects a negative metrics listener port + template: templates/statefulset.yaml + set: + service.metricsPort: -1 + asserts: + - failedTemplate: + errorPattern: "service.metricsPort must be 0 \\(disabled\\) or between 1 and 65535" + + - it: rejects a boolean primary port + template: templates/statefulset.yaml + set: + service.port: true + asserts: + - failedTemplate: + errorPattern: "service.port must be an integer" + + - it: rejects a boolean health port + template: templates/statefulset.yaml + set: + service.healthPort: true + asserts: + - failedTemplate: + errorPattern: "service.healthPort must be an integer" + + - it: rejects a boolean metrics port + template: templates/statefulset.yaml + set: + service.metricsPort: true + asserts: + - failedTemplate: + errorPattern: "service.metricsPort must be an integer" + + - it: rejects a fractional metrics port + template: templates/statefulset.yaml + set: + service.metricsPort: 9090.5 + asserts: + - failedTemplate: + errorPattern: "service.metricsPort must be an integer" + + - it: rejects a metrics listener above the valid port range + template: templates/statefulset.yaml + set: + service.metricsPort: 65536 + asserts: + - failedTemplate: + errorPattern: "service.metricsPort must be 0 \\(disabled\\) or between 1 and 65535" + + - it: accepts boundary ports with metrics disabled + template: templates/gateway-config.yaml + set: + service.port: 1 + service.healthPort: 65535 + service.metricsPort: 0 + asserts: + - matchRegex: + path: data["gateway.toml"] + pattern: '(?m)^bind_address\s*=\s*"0\.0\.0\.0:1"$' + - matchRegex: + path: data["gateway.toml"] + pattern: '(?m)^health_bind_address\s*=\s*"0\.0\.0\.0:65535"$' + - notMatchRegex: + path: data["gateway.toml"] + pattern: '(?m)^metrics_bind_address\s*='