diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index 198a77bad9e..77c56165ac7 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -12025,7 +12025,7 @@ "description": "Endpoint represents a single logical \"backend\" implementing a service.", "properties": { "addresses": { - "description": "addresses of this endpoint. The contents of this field are interpreted according to the corresponding EndpointSlice addressType field. This allows for cases like dual-stack (IPv4 and IPv6) networking. Consumers (e.g. kube-proxy) must handle different types of addresses in the context of their own capabilities. This must contain at least one address but no more than 100.", + "description": "addresses of this endpoint. The contents of this field are interpreted according to the corresponding EndpointSlice addressType field. This allows for cases like dual-stack networking where both IPv4 and IPv6 addresses would be included with the IP addressType. Consumers (e.g. kube-proxy) must handle different types of addresses in the context of their own capabilities. This must contain at least one address but no more than 100.", "items": { "type": "string" }, @@ -12090,7 +12090,7 @@ "description": "EndpointSlice represents a subset of the endpoints that implement a service. For a given service there may be multiple EndpointSlice objects, selected by labels, which must be joined to produce the full set of endpoints.", "properties": { "addressType": { - "description": "addressType specifies the type of address carried by this EndpointSlice. All addresses in this slice must be the same type. Default is IP", + "description": "addressType specifies the type of address carried by this EndpointSlice. All addresses in this slice must be the same type. The following address types are currently supported: * IP: Represents an IP Address. This can include both IPv4 and IPv6\n addresses.\n* FQDN: Represents a Fully Qualified Domain Name. Default is IP", "type": "string" }, "apiVersion": { diff --git a/pkg/apis/discovery/types.go b/pkg/apis/discovery/types.go index 09af2ea7300..2d4c573ce66 100644 --- a/pkg/apis/discovery/types.go +++ b/pkg/apis/discovery/types.go @@ -32,7 +32,11 @@ type EndpointSlice struct { // +optional metav1.ObjectMeta // addressType specifies the type of address carried by this EndpointSlice. - // All addresses in this slice must be the same type. + // All addresses in this slice must be the same type. The following address + // types are currently supported: + // * IP: Represents an IP Address. This can include both IPv4 and IPv6 + // addresses. + // * FQDN: Represents a Fully Qualified Domain Name. // +optional AddressType *AddressType // endpoints is a list of unique endpoints in this slice. Each slice may @@ -53,17 +57,21 @@ type EndpointSlice struct { type AddressType string const ( - // AddressTypeIP represents an IP Address. + // AddressTypeIP represents an IP Address. Inclusive of IPv4 and IPv6 + // addresses. AddressTypeIP = AddressType("IP") + // AddressTypeFQDN represents a Fully Qualified Domain Name. + AddressTypeFQDN = AddressType("FQDN") ) // Endpoint represents a single logical "backend" implementing a service. type Endpoint struct { // addresses of this endpoint. The contents of this field are interpreted // according to the corresponding EndpointSlice addressType field. This - // allows for cases like dual-stack (IPv4 and IPv6) networking. Consumers - // (e.g. kube-proxy) must handle different types of addresses in the context - // of their own capabilities. This must contain at least one address but no + // allows for cases like dual-stack networking where both IPv4 and IPv6 + // addresses would be included with the IP addressType. Consumers (e.g. + // kube-proxy) must handle different types of addresses in the context of + // their own capabilities. This must contain at least one address but no // more than 100. // +listType=set Addresses []string diff --git a/pkg/apis/discovery/validation/validation.go b/pkg/apis/discovery/validation/validation.go index 329e4e2f82b..96d9038b7bb 100644 --- a/pkg/apis/discovery/validation/validation.go +++ b/pkg/apis/discovery/validation/validation.go @@ -28,7 +28,7 @@ import ( ) var ( - supportedAddressTypes = sets.NewString(string(discovery.AddressTypeIP)) + supportedAddressTypes = sets.NewString(string(discovery.AddressTypeIP), string(discovery.AddressTypeFQDN)) supportedPortProtocols = sets.NewString(string(api.ProtocolTCP), string(api.ProtocolUDP), string(api.ProtocolSCTP)) maxTopologyLabels = 16 maxAddresses = 100 @@ -45,6 +45,8 @@ var ValidateEndpointSliceName = apimachineryvalidation.NameIsDNSSubdomain func ValidateEndpointSlice(endpointSlice *discovery.EndpointSlice) field.ErrorList { allErrs := apivalidation.ValidateObjectMeta(&endpointSlice.ObjectMeta, true, ValidateEndpointSliceName, field.NewPath("metadata")) + // AddressType should have had a default value set at this point, this is + // just a safety check if for some reason that changes or doesn't work. addrType := discovery.AddressType("") if endpointSlice.AddressType == nil { allErrs = append(allErrs, field.Required(field.NewPath("addressType"), "")) @@ -52,8 +54,8 @@ func ValidateEndpointSlice(endpointSlice *discovery.EndpointSlice) field.ErrorLi addrType = *endpointSlice.AddressType } - if endpointSlice.AddressType != nil && !supportedAddressTypes.Has(string(*endpointSlice.AddressType)) { - allErrs = append(allErrs, field.NotSupported(field.NewPath("addressType"), *endpointSlice.AddressType, supportedAddressTypes.List())) + if !supportedAddressTypes.Has(string(addrType)) { + allErrs = append(allErrs, field.NotSupported(field.NewPath("addressType"), addrType, supportedAddressTypes.List())) } allErrs = append(allErrs, validateEndpoints(endpointSlice.Endpoints, addrType, field.NewPath("endpoints"))...) @@ -83,17 +85,20 @@ func validateEndpoints(endpoints []discovery.Endpoint, addrType discovery.Addres idxPath := fldPath.Index(i) addressPath := idxPath.Child("addresses") - if addrType == discovery.AddressTypeIP { - if len(endpoint.Addresses) == 0 { - allErrs = append(allErrs, field.Required(addressPath, "must contain at least 1 address")) - } else if len(endpoint.Addresses) > maxAddresses { - allErrs = append(allErrs, field.TooMany(addressPath, len(endpoint.Addresses), maxAddresses)) - } + if len(endpoint.Addresses) == 0 { + allErrs = append(allErrs, field.Required(addressPath, "must contain at least 1 address")) + } else if len(endpoint.Addresses) > maxAddresses { + allErrs = append(allErrs, field.TooMany(addressPath, len(endpoint.Addresses), maxAddresses)) + } - for i, address := range endpoint.Addresses { + for i, address := range endpoint.Addresses { + switch addrType { + case discovery.AddressTypeIP: for _, msg := range validation.IsValidIP(address) { allErrs = append(allErrs, field.Invalid(addressPath.Index(i), address, msg)) } + case discovery.AddressTypeFQDN: + allErrs = append(allErrs, validation.IsFullyQualifiedDomainName(addressPath.Index(i), address)...) } } diff --git a/pkg/apis/discovery/validation/validation_test.go b/pkg/apis/discovery/validation/validation_test.go index 01469f6e427..907dd7d2134 100644 --- a/pkg/apis/discovery/validation/validation_test.go +++ b/pkg/apis/discovery/validation/validation_test.go @@ -46,7 +46,22 @@ func TestValidateEndpointSlice(t *testing.T) { Protocol: protocolPtr(api.ProtocolTCP), }}, Endpoints: []discovery.Endpoint{{ - Addresses: generateAddresses(1), + Addresses: generateIPAddresses(1), + Hostname: utilpointer.StringPtr("valid-123"), + }}, + }, + }, + "good-fqdns": { + expectedErrors: 0, + endpointSlice: &discovery.EndpointSlice{ + ObjectMeta: standardMeta, + AddressType: addressTypePtr(discovery.AddressTypeFQDN), + Ports: []discovery.EndpointPort{{ + Name: utilpointer.StringPtr("http"), + Protocol: protocolPtr(api.ProtocolTCP), + }}, + Endpoints: []discovery.Endpoint{{ + Addresses: []string{"foo.example.com", "example.com", "example.com.", "hyphens-are-good.example.com"}, Hostname: utilpointer.StringPtr("valid-123"), }}, }, @@ -67,7 +82,7 @@ func TestValidateEndpointSlice(t *testing.T) { Protocol: protocolPtr(api.ProtocolSCTP), }}, Endpoints: []discovery.Endpoint{{ - Addresses: generateAddresses(1), + Addresses: generateIPAddresses(1), Hostname: utilpointer.StringPtr("valid-123"), }}, }, @@ -85,7 +100,7 @@ func TestValidateEndpointSlice(t *testing.T) { Protocol: protocolPtr(api.ProtocolTCP), }}, Endpoints: []discovery.Endpoint{{ - Addresses: generateAddresses(1), + Addresses: generateIPAddresses(1), }}, }, }, @@ -125,7 +140,7 @@ func TestValidateEndpointSlice(t *testing.T) { Protocol: protocolPtr(api.ProtocolTCP), }}, Endpoints: []discovery.Endpoint{{ - Addresses: generateAddresses(maxAddresses), + Addresses: generateIPAddresses(maxAddresses), }}, }, }, @@ -139,7 +154,7 @@ func TestValidateEndpointSlice(t *testing.T) { Protocol: protocolPtr(api.ProtocolTCP), }}, Endpoints: []discovery.Endpoint{{ - Addresses: generateAddresses(1), + Addresses: generateIPAddresses(1), Topology: generateTopology(maxTopologyLabels), }}, }, @@ -223,7 +238,7 @@ func TestValidateEndpointSlice(t *testing.T) { Protocol: protocolPtr(api.ProtocolTCP), }}, Endpoints: []discovery.Endpoint{{ - Addresses: generateAddresses(0), + Addresses: generateIPAddresses(0), }}, }, }, @@ -237,7 +252,7 @@ func TestValidateEndpointSlice(t *testing.T) { Protocol: protocolPtr(api.ProtocolTCP), }}, Endpoints: []discovery.Endpoint{{ - Addresses: generateAddresses(maxAddresses + 1), + Addresses: generateIPAddresses(maxAddresses + 1), }}, }, }, @@ -251,7 +266,7 @@ func TestValidateEndpointSlice(t *testing.T) { Protocol: protocolPtr(api.ProtocolTCP), }}, Endpoints: []discovery.Endpoint{{ - Addresses: generateAddresses(1), + Addresses: generateIPAddresses(1), }}, }, }, @@ -265,7 +280,7 @@ func TestValidateEndpointSlice(t *testing.T) { Protocol: protocolPtr(api.ProtocolTCP), }}, Endpoints: []discovery.Endpoint{{ - Addresses: generateAddresses(1), + Addresses: generateIPAddresses(1), Topology: map[string]string{"--INVALID": "example"}, }}, }, @@ -280,7 +295,7 @@ func TestValidateEndpointSlice(t *testing.T) { Protocol: protocolPtr(api.ProtocolTCP), }}, Endpoints: []discovery.Endpoint{{ - Addresses: generateAddresses(1), + Addresses: generateIPAddresses(1), Topology: generateTopology(maxTopologyLabels + 1), }}, }, @@ -295,7 +310,7 @@ func TestValidateEndpointSlice(t *testing.T) { Protocol: protocolPtr(api.ProtocolTCP), }}, Endpoints: []discovery.Endpoint{{ - Addresses: generateAddresses(1), + Addresses: generateIPAddresses(1), Hostname: utilpointer.StringPtr("--INVALID"), }}, }, @@ -313,14 +328,46 @@ func TestValidateEndpointSlice(t *testing.T) { Protocol: protocolPtr(api.ProtocolTCP), }}, Endpoints: []discovery.Endpoint{{ - Addresses: generateAddresses(1), + Addresses: generateIPAddresses(1), + Hostname: utilpointer.StringPtr("valid-123"), + }}, + }, + }, + "bad-ip": { + expectedErrors: 1, + endpointSlice: &discovery.EndpointSlice{ + ObjectMeta: standardMeta, + AddressType: addressTypePtr(discovery.AddressTypeIP), + Ports: []discovery.EndpointPort{{ + Name: utilpointer.StringPtr("http"), + Protocol: protocolPtr(api.ProtocolTCP), + }}, + Endpoints: []discovery.Endpoint{{ + Addresses: []string{"123.456.789.012"}, + Hostname: utilpointer.StringPtr("valid-123"), + }}, + }, + }, + "bad-fqdns": { + expectedErrors: 4, + endpointSlice: &discovery.EndpointSlice{ + ObjectMeta: standardMeta, + AddressType: addressTypePtr(discovery.AddressTypeFQDN), + Ports: []discovery.EndpointPort{{ + Name: utilpointer.StringPtr("http"), + Protocol: protocolPtr(api.ProtocolTCP), + }}, + Endpoints: []discovery.Endpoint{{ + Addresses: []string{"foo.*", "FOO.example.com", "underscores_are_bad.example.com", "*.example.com"}, Hostname: utilpointer.StringPtr("valid-123"), }}, }, }, "empty-everything": { expectedErrors: 3, - endpointSlice: &discovery.EndpointSlice{}, + endpointSlice: &discovery.EndpointSlice{ + AddressType: addressTypePtr(""), + }, }, } @@ -422,7 +469,7 @@ func generateEndpoints(n int) []discovery.Endpoint { return endpoints } -func generateAddresses(n int) []string { +func generateIPAddresses(n int) []string { addresses := []string{} for i := 0; i < n; i++ { addresses = append(addresses, fmt.Sprintf("10.1.2.%d", i%255)) diff --git a/staging/src/k8s.io/api/discovery/v1alpha1/generated.proto b/staging/src/k8s.io/api/discovery/v1alpha1/generated.proto index f14f37fd046..47282cd786f 100644 --- a/staging/src/k8s.io/api/discovery/v1alpha1/generated.proto +++ b/staging/src/k8s.io/api/discovery/v1alpha1/generated.proto @@ -33,9 +33,10 @@ option go_package = "v1alpha1"; message Endpoint { // addresses of this endpoint. The contents of this field are interpreted // according to the corresponding EndpointSlice addressType field. This - // allows for cases like dual-stack (IPv4 and IPv6) networking. Consumers - // (e.g. kube-proxy) must handle different types of addresses in the context - // of their own capabilities. This must contain at least one address but no + // allows for cases like dual-stack networking where both IPv4 and IPv6 + // addresses would be included with the IP addressType. Consumers (e.g. + // kube-proxy) must handle different types of addresses in the context of + // their own capabilities. This must contain at least one address but no // more than 100. // +listType=set repeated string addresses = 1; @@ -115,7 +116,11 @@ message EndpointSlice { optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; // addressType specifies the type of address carried by this EndpointSlice. - // All addresses in this slice must be the same type. + // All addresses in this slice must be the same type. The following address + // types are currently supported: + // * IP: Represents an IP Address. This can include both IPv4 and IPv6 + // addresses. + // * FQDN: Represents a Fully Qualified Domain Name. // Default is IP // +optional optional string addressType = 4; diff --git a/staging/src/k8s.io/api/discovery/v1alpha1/types.go b/staging/src/k8s.io/api/discovery/v1alpha1/types.go index 0de6082aeef..638c9bf517d 100644 --- a/staging/src/k8s.io/api/discovery/v1alpha1/types.go +++ b/staging/src/k8s.io/api/discovery/v1alpha1/types.go @@ -33,7 +33,11 @@ type EndpointSlice struct { // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // addressType specifies the type of address carried by this EndpointSlice. - // All addresses in this slice must be the same type. + // All addresses in this slice must be the same type. The following address + // types are currently supported: + // * IP: Represents an IP Address. This can include both IPv4 and IPv6 + // addresses. + // * FQDN: Represents a Fully Qualified Domain Name. // Default is IP // +optional AddressType *AddressType `json:"addressType" protobuf:"bytes,4,rep,name=addressType"` @@ -55,17 +59,21 @@ type EndpointSlice struct { type AddressType string const ( - // AddressTypeIP represents an IP Address. + // AddressTypeIP represents an IP Address. Inclusive of IPv4 and IPv6 + // addresses. AddressTypeIP = AddressType("IP") + // AddressTypeFQDN represents a Fully Qualified Domain Name. + AddressTypeFQDN = AddressType("FQDN") ) // Endpoint represents a single logical "backend" implementing a service. type Endpoint struct { // addresses of this endpoint. The contents of this field are interpreted // according to the corresponding EndpointSlice addressType field. This - // allows for cases like dual-stack (IPv4 and IPv6) networking. Consumers - // (e.g. kube-proxy) must handle different types of addresses in the context - // of their own capabilities. This must contain at least one address but no + // allows for cases like dual-stack networking where both IPv4 and IPv6 + // addresses would be included with the IP addressType. Consumers (e.g. + // kube-proxy) must handle different types of addresses in the context of + // their own capabilities. This must contain at least one address but no // more than 100. // +listType=set Addresses []string `json:"addresses" protobuf:"bytes,1,rep,name=addresses"` diff --git a/staging/src/k8s.io/api/discovery/v1alpha1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/discovery/v1alpha1/types_swagger_doc_generated.go index a524bcd68e5..85f80397349 100644 --- a/staging/src/k8s.io/api/discovery/v1alpha1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/discovery/v1alpha1/types_swagger_doc_generated.go @@ -29,7 +29,7 @@ package v1alpha1 // AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT. var map_Endpoint = map[string]string{ "": "Endpoint represents a single logical \"backend\" implementing a service.", - "addresses": "addresses of this endpoint. The contents of this field are interpreted according to the corresponding EndpointSlice addressType field. This allows for cases like dual-stack (IPv4 and IPv6) networking. Consumers (e.g. kube-proxy) must handle different types of addresses in the context of their own capabilities. This must contain at least one address but no more than 100.", + "addresses": "addresses of this endpoint. The contents of this field are interpreted according to the corresponding EndpointSlice addressType field. This allows for cases like dual-stack networking where both IPv4 and IPv6 addresses would be included with the IP addressType. Consumers (e.g. kube-proxy) must handle different types of addresses in the context of their own capabilities. This must contain at least one address but no more than 100.", "conditions": "conditions contains information about the current status of the endpoint.", "hostname": "hostname of this endpoint. This field may be used by consumers of endpoints to distinguish endpoints from each other (e.g. in DNS names). Multiple endpoints which use the same hostname should be considered fungible (e.g. multiple A values in DNS). Must pass DNS Label (RFC 1123) validation.", "targetRef": "targetRef is a reference to a Kubernetes object that represents this endpoint.", @@ -63,7 +63,7 @@ func (EndpointPort) SwaggerDoc() map[string]string { var map_EndpointSlice = map[string]string{ "": "EndpointSlice represents a subset of the endpoints that implement a service. For a given service there may be multiple EndpointSlice objects, selected by labels, which must be joined to produce the full set of endpoints.", "metadata": "Standard object's metadata.", - "addressType": "addressType specifies the type of address carried by this EndpointSlice. All addresses in this slice must be the same type. Default is IP", + "addressType": "addressType specifies the type of address carried by this EndpointSlice. All addresses in this slice must be the same type. The following address types are currently supported: * IP: Represents an IP Address. This can include both IPv4 and IPv6\n addresses.\n* FQDN: Represents a Fully Qualified Domain Name. Default is IP", "endpoints": "endpoints is a list of unique endpoints in this slice. Each slice may include a maximum of 1000 endpoints.", "ports": "ports specifies the list of network ports exposed by each endpoint in this slice. Each port must have a unique name. When ports is empty, it indicates that there are no defined ports. When a port is defined with a nil port value, it indicates \"all ports\". Each slice may include a maximum of 100 ports.", } diff --git a/staging/src/k8s.io/apimachinery/pkg/util/validation/validation.go b/staging/src/k8s.io/apimachinery/pkg/util/validation/validation.go index 2dd99992dca..a336c2872d9 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/validation/validation.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/validation/validation.go @@ -70,7 +70,11 @@ func IsQualifiedName(value string) []string { return errs } -// IsFullyQualifiedName checks if the name is fully qualified. +// IsFullyQualifiedName checks if the name is fully qualified. This is similar +// to IsFullyQualifiedDomainName but requires a minimum of 3 segments instead of +// 2 and does not accept a trailing . as valid. +// TODO: This function is deprecated and preserved until all callers migrate to +// IsFullyQualifiedDomainName; please don't add new callers. func IsFullyQualifiedName(fldPath *field.Path, name string) field.ErrorList { var allErrors field.ErrorList if len(name) == 0 { @@ -85,6 +89,26 @@ func IsFullyQualifiedName(fldPath *field.Path, name string) field.ErrorList { return allErrors } +// IsFullyQualifiedDomainName checks if the domain name is fully qualified. This +// is similar to IsFullyQualifiedName but only requires a minimum of 2 segments +// instead of 3 and accepts a trailing . as valid. +func IsFullyQualifiedDomainName(fldPath *field.Path, name string) field.ErrorList { + var allErrors field.ErrorList + if len(name) == 0 { + return append(allErrors, field.Required(fldPath, "")) + } + if strings.HasSuffix(name, ".") { + name = name[:len(name)-1] + } + if errs := IsDNS1123Subdomain(name); len(errs) > 0 { + return append(allErrors, field.Invalid(fldPath, name, strings.Join(errs, ","))) + } + if len(strings.Split(name, ".")) < 2 { + return append(allErrors, field.Invalid(fldPath, name, "should be a domain with at least two segments separated by dots")) + } + return allErrors +} + const labelValueFmt string = "(" + qualifiedNameFmt + ")?" const labelValueErrMsg string = "a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character" diff --git a/staging/src/k8s.io/apimachinery/pkg/util/validation/validation_test.go b/staging/src/k8s.io/apimachinery/pkg/util/validation/validation_test.go index c4d5aba2978..4c02cba1d0a 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/validation/validation_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/validation/validation_test.go @@ -477,8 +477,85 @@ func TestIsWildcardDNS1123Subdomain(t *testing.T) { } } +func TestIsFullyQualifiedDomainName(t *testing.T) { + goodValues := []string{ + "a.com", + "k8s.io", + "dev.k8s.io", + "dev.k8s.io.", + "foo.example.com", + "this.is.a.really.long.fqdn", + "bbc.co.uk", + "10.0.0.1", // DNS labels can start with numbers and there is no requirement for letters. + "hyphens-are-good.k8s.io", + strings.Repeat("a", 246) + ".k8s.io", + } + for _, val := range goodValues { + if err := IsFullyQualifiedDomainName(field.NewPath(""), val).ToAggregate(); err != nil { + t.Errorf("expected no errors for %q: %v", val, err) + } + } + + badValues := []string{ + ".", + "...", + ".io", + "com", + ".com", + "Dev.k8s.io", + ".foo.example.com", + "*.example.com", + "*.bar.com", + "*.foo.bar.com", + "underscores_are_bad.k8s.io", + "foo@bar.example.com", + "http://foo.example.com", + strings.Repeat("a", 247) + ".k8s.io", + } + for _, val := range badValues { + if err := IsFullyQualifiedDomainName(field.NewPath(""), val).ToAggregate(); err == nil { + t.Errorf("expected errors for %q", val) + } + } +} + func TestIsFullyQualifiedName(t *testing.T) { - tests := []struct { + goodValues := []string{ + "dev.k8s.io", + "foo.example.com", + "this.is.a.really.long.fqdn", + "bbc.co.uk", + "10.0.0.1", // DNS labels can start with numbers and there is no requirement for letters. + "hyphens-are-good.k8s.io", + strings.Repeat("a", 246) + ".k8s.io", + } + for _, val := range goodValues { + if err := IsFullyQualifiedName(field.NewPath(""), val).ToAggregate(); err != nil { + t.Errorf("expected no errors for %q: %v", val, err) + } + } + + badValues := []string{ + "...", + "dev.k8s.io.", + ".io", + "Dev.k8s.io", + "k8s.io", + "*.example.com", + "*.bar.com", + "*.foo.bar.com", + "underscores_are_bad.k8s.io", + "foo@bar.example.com", + "http://foo.example.com", + strings.Repeat("a", 247) + ".k8s.io", + } + for _, val := range badValues { + if err := IsFullyQualifiedName(field.NewPath(""), val).ToAggregate(); err == nil { + t.Errorf("expected errors for %q", val) + } + } + + messageTests := []struct { name string targetName string err string @@ -488,6 +565,16 @@ func TestIsFullyQualifiedName(t *testing.T) { targetName: "k8s.io", err: "should be a domain with at least three segments separated by dots", }, + { + name: "name should not include scheme", + targetName: "http://foo.k8s.io", + err: "a DNS-1123 subdomain must consist of lower case alphanumeric characters", + }, + { + name: "email should be invalid", + targetName: "example@foo.k8s.io", + err: "a DNS-1123 subdomain must consist of lower case alphanumeric characters", + }, { name: "name cannot be empty", targetName: "", @@ -499,7 +586,7 @@ func TestIsFullyQualifiedName(t *testing.T) { err: "a DNS-1123 subdomain must consist of lower case alphanumeric characters", }, } - for _, tc := range tests { + for _, tc := range messageTests { err := IsFullyQualifiedName(field.NewPath(""), tc.targetName).ToAggregate() switch { case tc.err == "" && err != nil: