Skip to content

fix(helpers): make backendRef port optional in HTTPRoute rules - #603

Merged
aslafy-z merged 7 commits into
stakater:mainfrom
alex1989hu:fix/httproute-backendref-port
Aug 19, 2026
Merged

fix(helpers): make backendRef port optional in HTTPRoute rules#603
aslafy-z merged 7 commits into
stakater:mainfrom
alex1989hu:fix/httproute-backendref-port

Conversation

@alex1989hu

@alex1989hu alex1989hu commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

The HTTPRoute backendRefs template required .port on every backendRef and failed the render if it was missing, but per the Gateway API spec port is optional on a BackendRef in general. It is only required when the referent is a core Kubernetes Service.

What changed

  • port is validated and rendered only when it is actually set, and left out of the manifest entirely when omitted (e.g. for kind: ServiceImport backends).
  • Per the Gateway API spec, omitting port when the referent is a core Kubernetes Service (group: "" and kind: Service, the default) still fails the render, now with an explicit message: backendRef "<name>": port is required when the referent is a Kubernetes Service.
  • A templated port that renders to an empty string (e.g. port: '{{ .Values.optionalPort }}' with the value unset) is treated as unset, so the optionality also works with template expressions.
  • The port documentation in values.yaml, README, and values.schema.json now states when the field may be omitted.

Note on the issue repro

The exact reproduction in #602 (no port, kind defaulting to Service) still fails by design, because the Gateway API spec requires port for Service referents and the HTTPRoute CRD would reject the manifest anyway. The difference is that the failure message now explains the actual constraint instead of Invalid port value: 0. Backends of any other kind render without a port as the issue expects.

Tests

Unit tests cover: port omitted for a non-Service kind (rendered without port), port omitted for a Service (explicit failure), empty templated port for both cases, out-of-range and explicit-zero ports, mixed rules where only some backendRefs have a port, and a Service in a non-core group (port optional).

Closes #602

@aslafy-z

aslafy-z commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Thank you for your contribution @alex1989hu, please review my comment:

The new test case is not valid according to the Gateway API specification.

It defines:

backendRefs:
  - name: example-service

Because neither group nor kind is specified, Gateway API infers the core API group and defaults kind to Service. This backendRef therefore refers to a Kubernetes Service, for which port is required:

https://gateway-api.sigs.k8s.io/reference/api-spec/main/spec/#:~:text=Port%20is%20required%20when%20the%20referent%20is%20a%20Kubernetes%20Service.

The chart does support explicit non-Service backends through the group and kind fields, but that is not what this test exercises.

As written, the PR allows an invalid Service backendRef to render successfully. I think the test should continue to require a port for the default Service case. Port omission should only be accepted when group or kind explicitly identifies a non-Service backend.

@alex1989hu

Copy link
Copy Markdown
Contributor Author

Do I understand correctly that you'd like to enforce the port field if the kind is omitted or set as Service ?

@aslafy-z

aslafy-z commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

@alex1989hu I feel like it's the more correct thing to do, what do you think?

@alex1989hu

Copy link
Copy Markdown
Contributor Author

@alex1989hu I feel like it's the more correct thing to do, what do you think?

I'm okay with that change. I've just pushed a commit 5f9dd47 , could you please take a look?

@aslafy-z

aslafy-z commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Thanks, the Service/default-kind case is now handled correctly.

One remaining issue: if .port checks truthiness rather than whether the field was provided. For example, port: 0 is falsey, so it bypasses the range validation. For a non-Service backend it would be silently omitted, while for a Service it would produce the less accurate "port is required" error.

Could we use kindIs "invalid" to distinguish an absent/null value from an explicitly provided falsey value, and reuse the same gate when rendering?

{{- $group := .group | default "" }}
{{- $kind := .kind | default "Service" }}
{{- $hasPort := not (kindIs "invalid" .port) }}
{{- $isKubernetesService := and (eq $group "") (eq $kind "Service") }}
{{- $portVal := 0 }}

{{- if $hasPort }}
  {{- $portVal = .port | int }}
  {{- if or (lt $portVal 1) (gt $portVal 65535) }}
    {{- fail (printf "Invalid port value: %v. Port must be between 1 and 65535" .port) }}
  {{- end }}
{{- else if $isKubernetesService }}
  {{- fail (printf "backendRef %q: port is required when the referent is a Kubernetes Service" .name) }}
{{- end }}

Then use $hasPort instead of if .port when rendering the field.

The group check also avoids treating a custom resource such as group: example.io, kind: Service as a Kubernetes Service.

@alex1989hu
alex1989hu force-pushed the fix/httproute-backendref-port branch from 5f9dd47 to 8a06504 Compare August 7, 2026 08:56
@alex1989hu

Copy link
Copy Markdown
Contributor Author

Thanks, the Service/default-kind case is now handled correctly.

One remaining issue: if .port checks truthiness rather than whether the field was provided. For example, port: 0 is falsey, so it bypasses the range validation. For a non-Service backend it would be silently omitted, while for a Service it would produce the less accurate "port is required" error.

Could we use kindIs "invalid" to distinguish an absent/null value from an explicitly provided falsey value, and reuse the same gate when rendering?

{{- $group := .group | default "" }}
{{- $kind := .kind | default "Service" }}
{{- $hasPort := not (kindIs "invalid" .port) }}
{{- $isKubernetesService := and (eq $group "") (eq $kind "Service") }}
{{- $portVal := 0 }}

{{- if $hasPort }}
  {{- $portVal = .port | int }}
  {{- if or (lt $portVal 1) (gt $portVal 65535) }}
    {{- fail (printf "Invalid port value: %v. Port must be between 1 and 65535" .port) }}
  {{- end }}
{{- else if $isKubernetesService }}
  {{- fail (printf "backendRef %q: port is required when the referent is a Kubernetes Service" .name) }}
{{- end }}

Then use $hasPort instead of if .port when rendering the field.

The group check also avoids treating a custom resource such as group: example.io, kind: Service as a Kubernetes Service.

Good catch, thank you! I copied your proposal and added two tests in 8a06504

alex1989hu and others added 7 commits August 19, 2026 16:16
The HTTPRoute backendRefs template required .port on every backendRef
and failed the render if it was missing, but per the Gateway API spec
port is optional on BackendRef

Only validate and render port when it is actually set, leaving it out
of the manifest entirely when omitted.

Signed-off-by: Alex Szakaly <alex.szakaly@gmail.com>
…te rules

The previous fix made port fully optional on every HTTPRoute backendRef,
but per the Gateway API spec port is only optional when kind is not
Service. Port is required when kind is omitted (defaults to Service) or
set explicitly to Service.

Fail the render with a clear error when port is missing on a Service
backendRef, and keep port optional for any other kind.

Signed-off-by: Alex Szakaly <alex.szakaly@gmail.com>
Port was checked for truthiness, so port: 0 was wrongly
treated as omitted

Signed-off-by: Alex Szakaly <alex.szakaly@gmail.com>
@aslafy-z
aslafy-z force-pushed the fix/httproute-backendref-port branch from 161b6d7 to 235c0ad Compare August 19, 2026 14:17
@aslafy-z
aslafy-z merged commit 20a6a23 into stakater:main Aug 19, 2026
13 checks passed
@aslafy-z

Copy link
Copy Markdown
Collaborator

Thanks for the fix @alex1989hu!

I pushed a few additions on top of your commits before merging:

  • A templated port that renders empty (e.g. port: '{{ .Values.optionalPort }}' with the value unset) is now treated as unset too, so the optionality composes with template expressions like the chart's own defaults use.
  • The render guard now reuses the same presence check as the validation, instead of a separate truthiness test.
  • Documented the "required for Service referents, optional otherwise" rule in values.yaml (README and schema regenerated).
  • Tidied the new tests a bit (dropped a few duplicate cases) and added coverage for a Service in a non-core group.

Also rebased onto main. Merged, thanks again for the contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HTTPRoute template fails to render when backendRef port is omitted

2 participants