diff --git a/apis/v1alpha2/gateway_types.go b/apis/v1alpha2/gateway_types.go index ecf8ff10c7..4749f1ea0a 100644 --- a/apis/v1alpha2/gateway_types.go +++ b/apis/v1alpha2/gateway_types.go @@ -454,7 +454,6 @@ type GatewayAddress struct { // Type of the address. // // +optional - // +kubebuilder:validation:Enum=IPAddress;Hostname;NamedAddress // +kubebuilder:default=IPAddress Type *AddressType `json:"type,omitempty"` diff --git a/apis/v1alpha2/shared_types.go b/apis/v1alpha2/shared_types.go index 8611096b1e..0599489a83 100644 --- a/apis/v1alpha2/shared_types.go +++ b/apis/v1alpha2/shared_types.go @@ -480,6 +480,19 @@ type AnnotationKey string type AnnotationValue string // AddressType defines how a network address is represented as a text string. +// This may take two possible forms: +// +// * A predefined CamelCase string identifier (currently limited to `IPAddress` or `Hostname`) +// * A domain-prefixed string identifier (like `acme.io/CustomAddressType`) +// +// Values `IPAddress` and `Hostname` have Extended support. +// +// All other values, including domain-prefixed values have Custom support, +// which are used in implementation-specific behaviors. +// +// +kubebuilder:validation:MinLength=1 +// +kubebuilder:validation:MaxLength=253 +// +kubebuilder:validation:Pattern=`^([a-zA-Z0-9])+$|^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])\/[a-zA-Z0-9]+$` type AddressType string const ( @@ -502,11 +515,4 @@ const ( // // Support: Extended HostnameAddressType AddressType = "Hostname" - - // A NamedAddress provides a way to reference a specific IP address by name. - // For example, this may be a name or other unique identifier that refers - // to a resource on a cloud provider such as a static IP. - // - // Support: Implementation-Specific - NamedAddressType AddressType = "NamedAddress" ) diff --git a/apis/v1alpha2/validation/gateway.go b/apis/v1alpha2/validation/gateway.go index 33f5d5ac3a..7392e0e03a 100644 --- a/apis/v1alpha2/validation/gateway.go +++ b/apis/v1alpha2/validation/gateway.go @@ -18,6 +18,7 @@ package validation import ( "fmt" + "regexp" "k8s.io/apimachinery/pkg/util/validation/field" @@ -36,6 +37,11 @@ var ( gatewayv1a2.UDPProtocolType: {}, gatewayv1a2.TCPProtocolType: {}, } + + addressTypesValid = map[gatewayv1a2.AddressType]struct{}{ + gatewayv1a2.HostnameAddressType: {}, + gatewayv1a2.IPAddressType: {}, + } ) // ValidateGateway validates gw according to the Gateway API specification. @@ -51,7 +57,10 @@ func ValidateGateway(gw *gatewayv1a2.Gateway) field.ErrorList { // validateGatewaySpec validates whether required fields of spec are set according to the // Gateway API specification. func validateGatewaySpec(spec *gatewayv1a2.GatewaySpec, path *field.Path) field.ErrorList { - return validateGatewayListeners(spec.Listeners, path.Child("listeners")) + var errs field.ErrorList + errs = append(errs, validateGatewayListeners(spec.Listeners, path.Child("listeners"))...) + errs = append(errs, validateAddresses(spec.Addresses, path.Child("addresses"))...) + return errs } // validateGatewayListeners validates whether required fields of listeners are set according @@ -89,3 +98,31 @@ func validateListenerHostname(listeners []gatewayv1a2.Listener, path *field.Path } return errs } + +// domainPrefixedStringRegex is a regex used in validation to determine whether +// a provided string is a domain-prefixed string. Domain-prefixed strings are used +// to indicate custom (implementation-specific) address types. +var domainPrefixedStringRegex = regexp.MustCompile(`^([a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])\/[a-zA-Z0-9]+$`) + +// validateAddresses validates each listener address +// if there are addresses set. Otherwise, returns no error. +func validateAddresses(addresses []gatewayv1a2.GatewayAddress, path *field.Path) field.ErrorList { + var errs field.ErrorList + + for i, a := range addresses { + if a.Type == nil { + continue + } + _, ok := addressTypesValid[*a.Type] + if !ok { + // Found something that's not one of the upstream AddressTypes + // Next, check for a domain-prefixed string + match := domainPrefixedStringRegex.Match([]byte(*a.Type)) + if !match { + errs = append(errs, field.Invalid(path.Index(i).Child("type"), a.Type, "should either be a defined constant or a domain-prefixed string (example.com/Type)")) + } + } + + } + return errs +} diff --git a/apis/v1alpha2/validation/gateway_test.go b/apis/v1alpha2/validation/gateway_test.go index 1ce7fc0d0b..449a0b61bb 100644 --- a/apis/v1alpha2/validation/gateway_test.go +++ b/apis/v1alpha2/validation/gateway_test.go @@ -30,6 +30,11 @@ func TestValidateGateway(t *testing.T) { Hostname: nil, }, } + addresses := []gatewayv1a2.GatewayAddress{ + { + Type: nil, + }, + } baseGateway := gatewayv1a2.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -38,6 +43,7 @@ func TestValidateGateway(t *testing.T) { Spec: gatewayv1a2.GatewaySpec{ GatewayClassName: "foo", Listeners: listeners, + Addresses: addresses, }, } tlsConfig := gatewayv1a2.GatewayTLSConfig{} @@ -76,6 +82,34 @@ func TestValidateGateway(t *testing.T) { }, expectErrsOnFields: []string{"spec.listeners[0].hostname"}, }, + "Address present with IPAddress": { + mutate: func(gw *gatewayv1a2.Gateway) { + ip := gatewayv1a2.IPAddressType + gw.Spec.Addresses[0].Type = &ip + }, + expectErrsOnFields: []string{}, + }, + "Address present with Hostname": { + mutate: func(gw *gatewayv1a2.Gateway) { + host := gatewayv1a2.HostnameAddressType + gw.Spec.Addresses[0].Type = &host + }, + expectErrsOnFields: []string{}, + }, + "Address present with example.com/CustomAddress": { + mutate: func(gw *gatewayv1a2.Gateway) { + customAddress := gatewayv1a2.AddressType("example.com/CustomAddress") + gw.Spec.Addresses[0].Type = &customAddress + }, + expectErrsOnFields: []string{}, + }, + "Address present with invalid Type": { + mutate: func(gw *gatewayv1a2.Gateway) { + customAddress := gatewayv1a2.AddressType("CustomAddress") + gw.Spec.Addresses[0].Type = &customAddress + }, + expectErrsOnFields: []string{"spec.addresses[0].type"}, + }, } for name, tc := range testCases { diff --git a/config/crd/experimental/gateway.networking.k8s.io_gateways.yaml b/config/crd/experimental/gateway.networking.k8s.io_gateways.yaml index 563ea1271d..e01c67d575 100644 --- a/config/crd/experimental/gateway.networking.k8s.io_gateways.yaml +++ b/config/crd/experimental/gateway.networking.k8s.io_gateways.yaml @@ -78,10 +78,9 @@ spec: type: default: IPAddress description: Type of the address. - enum: - - IPAddress - - Hostname - - NamedAddress + maxLength: 253 + minLength: 1 + pattern: ^([a-zA-Z0-9])+$|^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])\/[a-zA-Z0-9]+$ type: string value: description: "Value of the address. The validity of the values @@ -467,10 +466,9 @@ spec: type: default: IPAddress description: Type of the address. - enum: - - IPAddress - - Hostname - - NamedAddress + maxLength: 253 + minLength: 1 + pattern: ^([a-zA-Z0-9])+$|^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])\/[a-zA-Z0-9]+$ type: string value: description: "Value of the address. The validity of the values diff --git a/config/crd/stable/gateway.networking.k8s.io_gateways.yaml b/config/crd/stable/gateway.networking.k8s.io_gateways.yaml index de0070c2d8..24b06247a5 100644 --- a/config/crd/stable/gateway.networking.k8s.io_gateways.yaml +++ b/config/crd/stable/gateway.networking.k8s.io_gateways.yaml @@ -78,10 +78,9 @@ spec: type: default: IPAddress description: Type of the address. - enum: - - IPAddress - - Hostname - - NamedAddress + maxLength: 253 + minLength: 1 + pattern: ^([a-zA-Z0-9])+$|^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])\/[a-zA-Z0-9]+$ type: string value: description: "Value of the address. The validity of the values @@ -467,10 +466,9 @@ spec: type: default: IPAddress description: Type of the address. - enum: - - IPAddress - - Hostname - - NamedAddress + maxLength: 253 + minLength: 1 + pattern: ^([a-zA-Z0-9])+$|^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])\/[a-zA-Z0-9]+$ type: string value: description: "Value of the address. The validity of the values