fix(helm/ai-gateway): render service nodePort with an explicit if guard (#27682)

> Coder Agents generated this commit

Replace the with block around the Service nodePort field with an if
guard that references .Values.service.nodePort directly. The with form
rebinds the dot inside the block, so a later addition that needs .Values
or .Release there would break.

Extend the default_values fixture to enable ingress and httproute with
only the values each one requires, so the golden file covers every
template with the minimum viable configuration.

Add a mustNotContain list to the render test cases. Golden files are
rewritten wholesale by TestUpdateGoldenFiles, so these assertions pin
optional fields and resources that each fixture leaves unset, including
the Service nodePort.
This commit is contained in:
Paweł Banaszewski
2026-07-30 14:47:07 +02:00
committed by GitHub
parent dc31791c88
commit e819dd4af6
4 changed files with 150 additions and 2 deletions
+2 -2
View File
@@ -29,7 +29,7 @@ spec:
port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
{{- with .Values.service.nodePort }}
nodePort: {{ . }}
{{- if .Values.service.nodePort }}
nodePort: {{ .Values.service.nodePort }}
{{- end }}
{{- end }}
+87
View File
@@ -22,21 +22,85 @@ var testCases = []testCase{
{
name: "default_values",
fixture: "default_values",
// The fixture enables every optional resource, so this case covers
// rendering of all templates with the minimum required values.
apiVersions: []string{"gateway.networking.k8s.io/v1/HTTPRoute"},
mustNotContain: []string{
// Service fields that only render when their value is set.
"nodePort:",
"loadBalancerClass:",
"loadBalancerIP:",
// externalTrafficPolicy only applies to NodePort and LoadBalancer.
"externalTrafficPolicy:",
// Ingress and HTTPRoute render with required fields only.
"ingressClassName:",
"hostnames:",
// No secret is configured, so nothing mounts a Secret or switches
// the probes and listener to TLS.
"secretName:",
"CODER_AI_GATEWAY_KEY_FILE",
"CODER_AI_GATEWAY_TLS_CERT_FILE",
"CODER_CLIENT_TLS_CA_FILE",
"CODER_CLIENT_TLS_CERT_FILE",
"scheme: HTTPS",
// Opt-in workload settings.
"startupProbe:",
"envFrom:",
"imagePullSecrets:",
"priorityClassName:",
"kind: ConfigMap",
},
},
{
name: "networking",
fixture: "networking",
namespace: "ai-gateway-test",
apiVersions: []string{"gateway.networking.k8s.io/v1/HTTPRoute"},
mustNotContain: []string{
// The Service is a LoadBalancer without an explicit nodePort, so
// Kubernetes allocates the port.
"nodePort:",
"loadBalancerIP:",
// Only the Gateway key Secret is configured.
"ai-gateway-listener",
"coder-client-ca",
"coder-client-tls",
"scheme: HTTPS",
"startupProbe:",
"envFrom:",
},
},
{
name: "custom",
fixture: "custom",
namespace: "ai-gateway-test",
mustNotContain: []string{
// coder.serviceAccount.disableCreate is true, so the chart uses the
// existing account instead of creating one.
"kind: ServiceAccount",
// Only the default ClusterIP Service renders.
"kind: Ingress",
"kind: HTTPRoute",
"nodePort:",
"loadBalancerClass:",
"loadBalancerIP:",
"externalTrafficPolicy:",
},
},
{
name: "nodeport",
fixture: "nodeport",
mustNotContain: []string{
// LoadBalancer-only fields stay absent for a NodePort Service.
"loadBalancerClass:",
"loadBalancerIP:",
"kind: Ingress",
"kind: HTTPRoute",
"ai-gateway-listener",
"coder-client-ca",
"coder-client-tls",
"scheme: HTTPS",
},
},
{
name: "missing_key_field",
@@ -54,6 +118,20 @@ var testCases = []testCase{
{
name: "listener_tls_with_ingress",
fixture: "listener_tls_with_ingress",
mustNotContain: []string{
// Listener TLS does not imply client TLS to coderd.
"coder-client-ca",
"coder-client-tls",
"CODER_CLIENT_TLS_CA_FILE",
"CODER_CLIENT_TLS_CERT_FILE",
// The Ingress renders with required fields only.
"ingressClassName:",
"kind: HTTPRoute",
"nodePort:",
"loadBalancerClass:",
"loadBalancerIP:",
"externalTrafficPolicy:",
},
},
{
name: "partial_client_tls",
@@ -104,6 +182,12 @@ type testCase struct {
namespace string
expectedError string
apiVersions []string
// mustNotContain holds substrings that must be absent from the rendered
// output. Golden files are rewritten wholesale by TestUpdateGoldenFiles,
// so absence assertions live here to keep an unintended field from being
// baked into the golden file. Each entry names an optional field or
// resource that the fixture leaves unset.
mustNotContain []string
}
func (tc testCase) valuesFilePath() string {
@@ -143,6 +227,9 @@ func TestRenderChart(t *testing.T) {
return
}
require.NoError(t, err, output)
for _, absent := range tc.mustNotContain {
require.NotContains(t, output, absent)
}
golden, err := os.ReadFile(tc.goldenFilePath())
require.NoError(t, err)
golden = bytes.ReplaceAll(golden, []byte("\r"), nil)
+51
View File
@@ -137,3 +137,54 @@ spec:
serviceAccountName: coder-ai-gateway
terminationGracePeriodSeconds: 330
volumes: []
---
# Source: coder-ai-gateway/templates/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: coder-ai-gateway
namespace: default
labels:
helm.sh/chart: coder-ai-gateway-0.1.0
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: "0.1.0"
app.kubernetes.io/managed-by: Helm
spec:
rules:
- host: "ai.example.com"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: coder-ai-gateway
port:
number: 80
---
# Source: coder-ai-gateway/templates/httproute.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: coder-ai-gateway
namespace: default
labels:
helm.sh/chart: coder-ai-gateway-0.1.0
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: "0.1.0"
app.kubernetes.io/managed-by: Helm
spec:
parentRefs:
- name: shared-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: coder-ai-gateway
port: 80
+10
View File
@@ -11,3 +11,13 @@ coder:
name: ai-gateway-key
key: key
aigateway: {}
# Enable every optional resource with only the values each one requires, so
# the golden file covers rendering of all templates without opting into
# non-default settings such as service.nodePort.
ingress:
enable: true
host: ai.example.com
httproute:
enable: true
parentRefs:
- name: shared-gateway