diff --git a/apis/v1/gateway_types.go b/apis/v1/gateway_types.go index f54e5d10ce..fac0c55e79 100644 --- a/apis/v1/gateway_types.go +++ b/apis/v1/gateway_types.go @@ -390,6 +390,9 @@ type Listener struct { // same port, subject to the Listener compatibility rules. // // Support: Core + // + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 Port PortNumber `json:"port"` // Protocol specifies the network protocol this listener expects to receive. diff --git a/apis/v1/httproute_types.go b/apis/v1/httproute_types.go index 6fec27d6e0..3e1b5b5753 100644 --- a/apis/v1/httproute_types.go +++ b/apis/v1/httproute_types.go @@ -1209,6 +1209,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 dd507b2136..0b254b367e 100644 --- a/apis/v1/object_reference_types.go +++ b/apis/v1/object_reference_types.go @@ -143,6 +143,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 e059b98145..586eb916b8 100644 --- a/apis/v1/shared_types.go +++ b/apis/v1/shared_types.go @@ -148,6 +148,9 @@ type ParentReference struct { // Support: Extended // // +optional + // + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 Port *PortNumber `json:"port,omitempty"` } @@ -227,9 +230,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 92cca1b735..e551307f04 100644 --- a/apisx/v1alpha1/xlistenerset_types.go +++ b/apisx/v1alpha1/xlistenerset_types.go @@ -152,7 +152,18 @@ type ListenerEntry struct { // Port is the network port. Multiple listeners may use the // same port, subject to the Listener compatibility rules. - 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. Protocol ProtocolType `json:"protocol"` @@ -233,6 +244,9 @@ 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 Port PortNumber `json:"port"` // SupportedKinds is the list indicating the Kinds supported by this diff --git a/applyconfiguration/internal/internal.go b/applyconfiguration/internal/internal.go index 7a69ee3876..58971ef5e2 100644 --- a/applyconfiguration/internal/internal.go +++ b/applyconfiguration/internal/internal.go @@ -1753,7 +1753,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 2454521c2e..3a11328f48 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 @@ -539,7 +545,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 96ac164691..872cb60785 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -7747,8 +7747,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", }, @@ -7774,7 +7773,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 +}