diff --git a/apis/v1/gateway_types.go b/apis/v1/gateway_types.go index ea8667af61..8425231530 100644 --- a/apis/v1/gateway_types.go +++ b/apis/v1/gateway_types.go @@ -396,6 +396,10 @@ type Listener struct { // same port, subject to the Listener compatibility rules. // // Support: Core + // + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 + // // +required Port PortNumber `json:"port"` diff --git a/apis/v1/httproute_types.go b/apis/v1/httproute_types.go index 96e245c386..d051df1046 100644 --- a/apis/v1/httproute_types.go +++ b/apis/v1/httproute_types.go @@ -1219,6 +1219,9 @@ type HTTPRequestRedirectFilter struct { // Support: Extended // // +optional + // + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 Port *PortNumber `json:"port,omitempty"` // StatusCode is the HTTP status code to be used in response. diff --git a/apis/v1/object_reference_types.go b/apis/v1/object_reference_types.go index 54e34fa2ed..414e39b947 100644 --- a/apis/v1/object_reference_types.go +++ b/apis/v1/object_reference_types.go @@ -148,6 +148,8 @@ type BackendObjectReference struct { // resource or this field. // // +optional + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 Port *PortNumber `json:"port,omitempty"` } diff --git a/apis/v1/shared_types.go b/apis/v1/shared_types.go index e874c2f905..231a22a746 100644 --- a/apis/v1/shared_types.go +++ b/apis/v1/shared_types.go @@ -149,6 +149,9 @@ type ParentReference struct { // Support: Extended // // +optional + // + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 Port *PortNumber `json:"port,omitempty"` } @@ -228,9 +231,6 @@ type CommonRouteSpec struct { } // PortNumber defines a network port. -// -// +kubebuilder:validation:Minimum=1 -// +kubebuilder:validation:Maximum=65535 type PortNumber int32 // BackendRef defines how a Route should forward a request to a Kubernetes diff --git a/apis/v1alpha2/shared_types.go b/apis/v1alpha2/shared_types.go index 2fb84d5f3b..3d2f787909 100644 --- a/apis/v1alpha2/shared_types.go +++ b/apis/v1alpha2/shared_types.go @@ -40,9 +40,6 @@ type ParentReference = v1.ParentReference type CommonRouteSpec = v1.CommonRouteSpec // PortNumber defines a network port. -// -// +kubebuilder:validation:Minimum=1 -// +kubebuilder:validation:Maximum=65535 type PortNumber = v1.PortNumber // BackendRef defines how a Route should forward a request to a Kubernetes diff --git a/apis/v1beta1/shared_types.go b/apis/v1beta1/shared_types.go index 3dbcc280fc..ce1c430649 100644 --- a/apis/v1beta1/shared_types.go +++ b/apis/v1beta1/shared_types.go @@ -40,9 +40,6 @@ type ParentReference = v1.ParentReference type CommonRouteSpec = v1.CommonRouteSpec // PortNumber defines a network port. -// -// +kubebuilder:validation:Minimum=1 -// +kubebuilder:validation:Maximum=65535 type PortNumber = v1.PortNumber // BackendRef defines how a Route should forward a request to a Kubernetes diff --git a/apisx/v1alpha1/xlistenerset_types.go b/apisx/v1alpha1/xlistenerset_types.go index 5eb6942b1b..a4cedc6949 100644 --- a/apisx/v1alpha1/xlistenerset_types.go +++ b/apisx/v1alpha1/xlistenerset_types.go @@ -158,8 +158,18 @@ type ListenerEntry struct { // Port is the network port. Multiple listeners may use the // same port, subject to the Listener compatibility rules. - // +required - Port PortNumber `json:"port"` + // + // If the port is not set or specified as zero, the implementation will assign + // a unique port. If the implementation does not support dynamic port + // assignment, it MUST set `Accepted` condition to `False` with the + // `UnsupportedPort` reason. + // + // +optional + // + // +kubebuilder:default=0 + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=65535 + Port PortNumber `json:"port,omitempty"` // Protocol specifies the network protocol this listener expects to receive. // +required @@ -242,6 +252,10 @@ type ListenerEntryStatus struct { Name SectionName `json:"name"` // Port is the network port the listener is configured to listen on. + // + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 + // // +required Port PortNumber `json:"port"` diff --git a/applyconfiguration/internal/internal.go b/applyconfiguration/internal/internal.go index 0403df0d63..92508d3a05 100644 --- a/applyconfiguration/internal/internal.go +++ b/applyconfiguration/internal/internal.go @@ -1795,7 +1795,6 @@ var schemaYAML = typed.YAMLObject(`types: - name: port type: scalar: numeric - default: 0 - name: protocol type: scalar: string diff --git a/config/crd/experimental/gateway.networking.x-k8s.io_xlistenersets.yaml b/config/crd/experimental/gateway.networking.x-k8s.io_xlistenersets.yaml index 637a9ed365..9a21d52e81 100644 --- a/config/crd/experimental/gateway.networking.x-k8s.io_xlistenersets.yaml +++ b/config/crd/experimental/gateway.networking.x-k8s.io_xlistenersets.yaml @@ -296,12 +296,18 @@ spec: pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string port: + default: 0 description: |- Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules. + + If the port is not set or specified as zero, the implementation will assign + a unique port. If the implementation does not support dynamic port + assignment, it MUST set `Accepted` condition to `False` with the + `UnsupportedPort` reason. format: int32 maximum: 65535 - minimum: 1 + minimum: 0 type: integer protocol: description: Protocol specifies the network protocol this listener @@ -541,7 +547,6 @@ spec: > 0 || size(self.options) > 0 : true' required: - name - - port - protocol type: object maxItems: 64 diff --git a/geps/gep-1713/index.md b/geps/gep-1713/index.md index 4cad39fde1..c720b35794 100644 --- a/geps/gep-1713/index.md +++ b/geps/gep-1713/index.md @@ -163,8 +163,19 @@ type ListenerEntry struct { // Port is the network port. Multiple listeners may use the // same port, subject to the Listener compatibility rules. - // + // + // If the port is not set or specified as zero, the implementation will assign + // a unique port. If the implementation does not support dynamic port + // assignment, it MUST set `Accepted` condition to `False` with the + // `UnsupportedPort` reason. + // // Support: Core + // + // +optional + // + // +kubebuilder:default=0 + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=65535 Port PortNumber `json:"port,omitempty"` // Protocol specifies the network protocol this listener expects to receive. @@ -380,6 +391,10 @@ spec: `ListenerEntry` is currently a copy of the `Listener` struct with some changes noted in the below sections +#### Port + +`Port` is now optional to allow for dynamic port assignment. If the port is unspecified or set to zero, the implementation will assign a unique port. If the implementation does not support dynamic port assignment, it MUST set `Accepted` condition to `False` with the `UnsupportedPort` reason. + ## Semantics ### Gateway Changes diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index cf47507b1f..62c65b8e74 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -7944,8 +7944,7 @@ func schema_sigsk8sio_gateway_api_apisx_v1alpha1_ListenerEntry(ref common.Refere }, "port": { SchemaProps: spec.SchemaProps{ - Description: "Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules.", - Default: 0, + Description: "Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules.\n\nIf the port is not set or specified as zero, the implementation will assign a unique port. If the implementation does not support dynamic port assignment, it MUST set `Accepted` condition to `False` with the `UnsupportedPort` reason.", Type: []string{"integer"}, Format: "int32", }, @@ -7971,7 +7970,7 @@ func schema_sigsk8sio_gateway_api_apisx_v1alpha1_ListenerEntry(ref common.Refere }, }, }, - Required: []string{"name", "port", "protocol"}, + Required: []string{"name", "protocol"}, }, }, Dependencies: []string{ diff --git a/pkg/generator/main.go b/pkg/generator/main.go index dc170b8823..1149e1a902 100644 --- a/pkg/generator/main.go +++ b/pkg/generator/main.go @@ -69,6 +69,8 @@ func main() { log.Fatalf("failed to register markers: %s", err) } + registerMarkerOverrides(parser.Collector.Registry) + crd.AddKnownTypes(parser) for _, r := range roots { parser.NeedPackage(r) diff --git a/pkg/generator/markers.go b/pkg/generator/markers.go new file mode 100644 index 0000000000..ab5f625aae --- /dev/null +++ b/pkg/generator/markers.go @@ -0,0 +1,66 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "sigs.k8s.io/controller-tools/pkg/markers" +) + +type Minimum float64 + +func (m Minimum) Value() float64 { + return float64(m) +} + +//nolint:unparam +func (m Minimum) ApplyToSchema(schema *apiext.JSONSchemaProps) error { + val := m.Value() + schema.Minimum = &val + return nil +} + +type Maximum float64 + +func (m Maximum) Value() float64 { + return float64(m) +} + +//nolint:unparam +func (m Maximum) ApplyToSchema(schema *apiext.JSONSchemaProps) error { + val := m.Value() + schema.Maximum = &val + return nil +} + +// kubebuilder Min Max markers are broken with type aliases +func registerMarkerOverrides(into *markers.Registry) { + minMarker, _ := markers.MakeDefinition( + "kubebuilder:validation:Minimum", + markers.DescribesField, + Minimum(0), + ) + + maxMarker, _ := markers.MakeDefinition( + "kubebuilder:validation:Maximum", + markers.DescribesField, + Maximum(0), + ) + + into.Register(minMarker) //nolint:errcheck + into.Register(maxMarker) //nolint:errcheck +}