From 47cb9559be87264becd721889f26304505510c9b Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Thu, 14 Feb 2019 00:29:03 -0500 Subject: [PATCH] Move internal Ingress type from extensions to networking --- hack/.golint_failures | 2 - pkg/apis/extensions/register.go | 4 +- pkg/apis/extensions/types.go | 171 ---------- pkg/apis/extensions/v1beta1/doc.go | 1 + pkg/apis/extensions/validation/validation.go | 171 ---------- .../extensions/validation/validation_test.go | 294 ------------------ pkg/apis/networking/types.go | 169 ++++++++++ pkg/apis/networking/validation/validation.go | 146 +++++++++ .../networking/validation/validation_test.go | 268 ++++++++++++++++ pkg/printers/internalversion/printers.go | 11 +- pkg/printers/internalversion/printers_test.go | 10 +- .../extensions/rest/storage_extensions.go | 2 +- .../{extensions => networking}/ingress/BUILD | 0 .../{extensions => networking}/ingress/doc.go | 2 +- .../ingress/storage/BUILD | 0 .../ingress/storage/storage.go | 12 +- .../ingress/storage/storage_test.go | 45 +-- .../ingress/strategy.go | 25 +- .../ingress/strategy_test.go | 26 +- test/test_owners.csv | 3 - 20 files changed, 654 insertions(+), 708 deletions(-) delete mode 100644 pkg/apis/extensions/validation/validation.go delete mode 100644 pkg/apis/extensions/validation/validation_test.go rename pkg/registry/{extensions => networking}/ingress/BUILD (100%) rename pkg/registry/{extensions => networking}/ingress/doc.go (87%) rename pkg/registry/{extensions => networking}/ingress/storage/BUILD (100%) rename pkg/registry/{extensions => networking}/ingress/storage/storage.go (88%) rename pkg/registry/{extensions => networking}/ingress/storage/storage_test.go (83%) rename pkg/registry/{extensions => networking}/ingress/strategy.go (82%) rename pkg/registry/{extensions => networking}/ingress/strategy_test.go (86%) diff --git a/hack/.golint_failures b/hack/.golint_failures index 4bc9f0c10a1..2b4777780b3 100644 --- a/hack/.golint_failures +++ b/hack/.golint_failures @@ -303,8 +303,6 @@ pkg/registry/core/service/storage pkg/registry/core/serviceaccount/storage pkg/registry/events/rest pkg/registry/extensions/controller/storage -pkg/registry/extensions/ingress -pkg/registry/extensions/ingress/storage pkg/registry/extensions/rest pkg/registry/networking/networkpolicy/storage pkg/registry/networking/rest diff --git a/pkg/apis/extensions/register.go b/pkg/apis/extensions/register.go index d4644ffaee3..14f59753071 100644 --- a/pkg/apis/extensions/register.go +++ b/pkg/apis/extensions/register.go @@ -56,8 +56,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &ReplicationControllerDummy{}, &apps.DaemonSetList{}, &apps.DaemonSet{}, - &Ingress{}, - &IngressList{}, + &networking.Ingress{}, + &networking.IngressList{}, &apps.ReplicaSet{}, &apps.ReplicaSetList{}, &policy.PodSecurityPolicy{}, diff --git a/pkg/apis/extensions/types.go b/pkg/apis/extensions/types.go index 20637e74e46..472ed99dabc 100644 --- a/pkg/apis/extensions/types.go +++ b/pkg/apis/extensions/types.go @@ -30,8 +30,6 @@ package extensions import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - api "k8s.io/kubernetes/pkg/apis/core" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -40,172 +38,3 @@ import ( type ReplicationControllerDummy struct { metav1.TypeMeta } - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// Ingress is a collection of rules that allow inbound connections to reach the -// endpoints defined by a backend. An Ingress can be configured to give services -// externally-reachable urls, load balance traffic, terminate SSL, offer name -// based virtual hosting etc. -type Ingress struct { - metav1.TypeMeta - // Standard object's metadata. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata - // +optional - metav1.ObjectMeta - - // Spec is the desired state of the Ingress. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status - // +optional - Spec IngressSpec - - // Status is the current state of the Ingress. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status - // +optional - Status IngressStatus -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// IngressList is a collection of Ingress. -type IngressList struct { - metav1.TypeMeta - // Standard object's metadata. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata - // +optional - metav1.ListMeta - - // Items is the list of Ingress. - Items []Ingress -} - -// IngressSpec describes the Ingress the user wishes to exist. -type IngressSpec struct { - // A default backend capable of servicing requests that don't match any - // rule. At least one of 'backend' or 'rules' must be specified. This field - // is optional to allow the loadbalancer controller or defaulting logic to - // specify a global default. - // +optional - Backend *IngressBackend - - // TLS configuration. Currently the Ingress only supports a single TLS - // port, 443. If multiple members of this list specify different hosts, they - // will be multiplexed on the same port according to the hostname specified - // through the SNI TLS extension, if the ingress controller fulfilling the - // ingress supports SNI. - // +optional - TLS []IngressTLS - - // A list of host rules used to configure the Ingress. If unspecified, or - // no rule matches, all traffic is sent to the default backend. - // +optional - Rules []IngressRule - // TODO: Add the ability to specify load-balancer IP through claims -} - -// IngressTLS describes the transport layer security associated with an Ingress. -type IngressTLS struct { - // Hosts are a list of hosts included in the TLS certificate. The values in - // this list must match the name/s used in the tlsSecret. Defaults to the - // wildcard host setting for the loadbalancer controller fulfilling this - // Ingress, if left unspecified. - // +optional - Hosts []string - // SecretName is the name of the secret used to terminate SSL traffic on 443. - // Field is left optional to allow SSL routing based on SNI hostname alone. - // If the SNI host in a listener conflicts with the "Host" header field used - // by an IngressRule, the SNI host is used for termination and value of the - // Host header is used for routing. - // +optional - SecretName string - // TODO: Consider specifying different modes of termination, protocols etc. -} - -// IngressStatus describe the current state of the Ingress. -type IngressStatus struct { - // LoadBalancer contains the current status of the load-balancer. - // +optional - LoadBalancer api.LoadBalancerStatus -} - -// IngressRule represents the rules mapping the paths under a specified host to -// the related backend services. Incoming requests are first evaluated for a host -// match, then routed to the backend associated with the matching IngressRuleValue. -type IngressRule struct { - // Host is the fully qualified domain name of a network host, as defined - // by RFC 3986. Note the following deviations from the "host" part of the - // URI as defined in the RFC: - // 1. IPs are not allowed. Currently an IngressRuleValue can only apply to the - // IP in the Spec of the parent Ingress. - // 2. The `:` delimiter is not respected because ports are not allowed. - // Currently the port of an Ingress is implicitly :80 for http and - // :443 for https. - // Both these may change in the future. - // Incoming requests are matched against the host before the IngressRuleValue. - // If the host is unspecified, the Ingress routes all traffic based on the - // specified IngressRuleValue. - // +optional - Host string - // IngressRuleValue represents a rule to route requests for this IngressRule. - // If unspecified, the rule defaults to a http catch-all. Whether that sends - // just traffic matching the host to the default backend or all traffic to the - // default backend, is left to the controller fulfilling the Ingress. Http is - // currently the only supported IngressRuleValue. - // +optional - IngressRuleValue -} - -// IngressRuleValue represents a rule to apply against incoming requests. If the -// rule is satisfied, the request is routed to the specified backend. Currently -// mixing different types of rules in a single Ingress is disallowed, so exactly -// one of the following must be set. -type IngressRuleValue struct { - //TODO: - // 1. Consider renaming this resource and the associated rules so they - // aren't tied to Ingress. They can be used to route intra-cluster traffic. - // 2. Consider adding fields for ingress-type specific global options - // usable by a loadbalancer, like http keep-alive. - - // +optional - HTTP *HTTPIngressRuleValue -} - -// HTTPIngressRuleValue is a list of http selectors pointing to backends. -// In the example: http:///? -> backend where -// where parts of the url correspond to RFC 3986, this resource will be used -// to match against everything after the last '/' and before the first '?' -// or '#'. -type HTTPIngressRuleValue struct { - // A collection of paths that map requests to backends. - Paths []HTTPIngressPath - // TODO: Consider adding fields for ingress-type specific global - // options usable by a loadbalancer, like http keep-alive. -} - -// HTTPIngressPath associates a path regex with a backend. Incoming urls matching -// the path are forwarded to the backend. -type HTTPIngressPath struct { - // Path is an extended POSIX regex as defined by IEEE Std 1003.1, - // (i.e this follows the egrep/unix syntax, not the perl syntax) - // matched against the path of an incoming request. Currently it can - // contain characters disallowed from the conventional "path" - // part of a URL as defined by RFC 3986. Paths must begin with - // a '/'. If unspecified, the path defaults to a catch all sending - // traffic to the backend. - // +optional - Path string - - // Backend defines the referenced service endpoint to which the traffic - // will be forwarded to. - Backend IngressBackend -} - -// IngressBackend describes all endpoints for a given service and port. -type IngressBackend struct { - // Specifies the name of the referenced service. - ServiceName string - - // Specifies the port of the referenced service. - ServicePort intstr.IntOrString -} diff --git a/pkg/apis/extensions/v1beta1/doc.go b/pkg/apis/extensions/v1beta1/doc.go index 463bceb1a9a..b21d59ab40f 100644 --- a/pkg/apis/extensions/v1beta1/doc.go +++ b/pkg/apis/extensions/v1beta1/doc.go @@ -16,6 +16,7 @@ limitations under the License. // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/apps // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/policy +// +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/networking // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/extensions // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/autoscaling // +k8s:conversion-gen-external-types=k8s.io/api/extensions/v1beta1 diff --git a/pkg/apis/extensions/validation/validation.go b/pkg/apis/extensions/validation/validation.go deleted file mode 100644 index 8c848a1489f..00000000000 --- a/pkg/apis/extensions/validation/validation.go +++ /dev/null @@ -1,171 +0,0 @@ -/* -Copyright 2015 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 validation - -import ( - "net" - "regexp" - "strings" - - apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" - "k8s.io/apimachinery/pkg/util/validation" - "k8s.io/apimachinery/pkg/util/validation/field" - apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" - "k8s.io/kubernetes/pkg/apis/extensions" -) - -// ValidateIngress tests if required fields in the Ingress are set. -func ValidateIngress(ingress *extensions.Ingress) field.ErrorList { - allErrs := apivalidation.ValidateObjectMeta(&ingress.ObjectMeta, true, ValidateIngressName, field.NewPath("metadata")) - allErrs = append(allErrs, ValidateIngressSpec(&ingress.Spec, field.NewPath("spec"))...) - return allErrs -} - -// ValidateIngressName validates that the given name can be used as an Ingress name. -var ValidateIngressName = apimachineryvalidation.NameIsDNSSubdomain - -func validateIngressTLS(spec *extensions.IngressSpec, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - // TODO: Perform a more thorough validation of spec.TLS.Hosts that takes - // the wildcard spec from RFC 6125 into account. - for _, itls := range spec.TLS { - for i, host := range itls.Hosts { - if strings.Contains(host, "*") { - for _, msg := range validation.IsWildcardDNS1123Subdomain(host) { - allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("hosts"), host, msg)) - } - continue - } - for _, msg := range validation.IsDNS1123Subdomain(host) { - allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("hosts"), host, msg)) - } - } - } - - return allErrs -} - -// ValidateIngressSpec tests if required fields in the IngressSpec are set. -func ValidateIngressSpec(spec *extensions.IngressSpec, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - // TODO: Is a default backend mandatory? - if spec.Backend != nil { - allErrs = append(allErrs, validateIngressBackend(spec.Backend, fldPath.Child("backend"))...) - } else if len(spec.Rules) == 0 { - allErrs = append(allErrs, field.Invalid(fldPath, spec.Rules, "either `backend` or `rules` must be specified")) - } - if len(spec.Rules) > 0 { - allErrs = append(allErrs, validateIngressRules(spec.Rules, fldPath.Child("rules"))...) - } - if len(spec.TLS) > 0 { - allErrs = append(allErrs, validateIngressTLS(spec, fldPath.Child("tls"))...) - } - return allErrs -} - -// ValidateIngressUpdate tests if required fields in the Ingress are set. -func ValidateIngressUpdate(ingress, oldIngress *extensions.Ingress) field.ErrorList { - allErrs := apivalidation.ValidateObjectMetaUpdate(&ingress.ObjectMeta, &oldIngress.ObjectMeta, field.NewPath("metadata")) - allErrs = append(allErrs, ValidateIngressSpec(&ingress.Spec, field.NewPath("spec"))...) - return allErrs -} - -// ValidateIngressStatusUpdate tests if required fields in the Ingress are set when updating status. -func ValidateIngressStatusUpdate(ingress, oldIngress *extensions.Ingress) field.ErrorList { - allErrs := apivalidation.ValidateObjectMetaUpdate(&ingress.ObjectMeta, &oldIngress.ObjectMeta, field.NewPath("metadata")) - allErrs = append(allErrs, apivalidation.ValidateLoadBalancerStatus(&ingress.Status.LoadBalancer, field.NewPath("status", "loadBalancer"))...) - return allErrs -} - -func validateIngressRules(ingressRules []extensions.IngressRule, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(ingressRules) == 0 { - return append(allErrs, field.Required(fldPath, "")) - } - for i, ih := range ingressRules { - if len(ih.Host) > 0 { - if isIP := (net.ParseIP(ih.Host) != nil); isIP { - allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, "must be a DNS name, not an IP address")) - } - // TODO: Ports and ips are allowed in the host part of a url - // according to RFC 3986, consider allowing them. - if strings.Contains(ih.Host, "*") { - for _, msg := range validation.IsWildcardDNS1123Subdomain(ih.Host) { - allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, msg)) - } - continue - } - for _, msg := range validation.IsDNS1123Subdomain(ih.Host) { - allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, msg)) - } - } - allErrs = append(allErrs, validateIngressRuleValue(&ih.IngressRuleValue, fldPath.Index(0))...) - } - return allErrs -} - -func validateIngressRuleValue(ingressRule *extensions.IngressRuleValue, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if ingressRule.HTTP != nil { - allErrs = append(allErrs, validateHTTPIngressRuleValue(ingressRule.HTTP, fldPath.Child("http"))...) - } - return allErrs -} - -func validateHTTPIngressRuleValue(httpIngressRuleValue *extensions.HTTPIngressRuleValue, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(httpIngressRuleValue.Paths) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("paths"), "")) - } - for i, rule := range httpIngressRuleValue.Paths { - if len(rule.Path) > 0 { - if !strings.HasPrefix(rule.Path, "/") { - allErrs = append(allErrs, field.Invalid(fldPath.Child("paths").Index(i).Child("path"), rule.Path, "must be an absolute path")) - } - // TODO: More draconian path regex validation. - // Path must be a valid regex. This is the basic requirement. - // In addition to this any characters not allowed in a path per - // RFC 3986 section-3.3 cannot appear as a literal in the regex. - // Consider the example: http://host/valid?#bar, everything after - // the last '/' is a valid regex that matches valid#bar, which - // isn't a valid path, because the path terminates at the first ? - // or #. A more sophisticated form of validation would detect that - // the user is confusing url regexes with path regexes. - _, err := regexp.CompilePOSIX(rule.Path) - if err != nil { - allErrs = append(allErrs, field.Invalid(fldPath.Child("paths").Index(i).Child("path"), rule.Path, "must be a valid regex")) - } - } - allErrs = append(allErrs, validateIngressBackend(&rule.Backend, fldPath.Child("backend"))...) - } - return allErrs -} - -// validateIngressBackend tests if a given backend is valid. -func validateIngressBackend(backend *extensions.IngressBackend, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - // All backends must reference a single local service by name, and a single service port by name or number. - if len(backend.ServiceName) == 0 { - return append(allErrs, field.Required(fldPath.Child("serviceName"), "")) - } - for _, msg := range apivalidation.ValidateServiceName(backend.ServiceName, false) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("serviceName"), backend.ServiceName, msg)) - } - allErrs = append(allErrs, apivalidation.ValidatePortNumOrName(backend.ServicePort, fldPath.Child("servicePort"))...) - return allErrs -} diff --git a/pkg/apis/extensions/validation/validation_test.go b/pkg/apis/extensions/validation/validation_test.go deleted file mode 100644 index 2d996837889..00000000000 --- a/pkg/apis/extensions/validation/validation_test.go +++ /dev/null @@ -1,294 +0,0 @@ -/* -Copyright 2014 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 validation - -import ( - "fmt" - "strings" - "testing" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - api "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/apis/extensions" -) - -func TestValidateIngress(t *testing.T) { - defaultBackend := extensions.IngressBackend{ - ServiceName: "default-backend", - ServicePort: intstr.FromInt(80), - } - - newValid := func() extensions.Ingress { - return extensions.Ingress{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - }, - Spec: extensions.IngressSpec{ - Backend: &extensions.IngressBackend{ - ServiceName: "default-backend", - ServicePort: intstr.FromInt(80), - }, - Rules: []extensions.IngressRule{ - { - Host: "foo.bar.com", - IngressRuleValue: extensions.IngressRuleValue{ - HTTP: &extensions.HTTPIngressRuleValue{ - Paths: []extensions.HTTPIngressPath{ - { - Path: "/foo", - Backend: defaultBackend, - }, - }, - }, - }, - }, - }, - }, - Status: extensions.IngressStatus{ - LoadBalancer: api.LoadBalancerStatus{ - Ingress: []api.LoadBalancerIngress{ - {IP: "127.0.0.1"}, - }, - }, - }, - } - } - servicelessBackend := newValid() - servicelessBackend.Spec.Backend.ServiceName = "" - invalidNameBackend := newValid() - invalidNameBackend.Spec.Backend.ServiceName = "defaultBackend" - noPortBackend := newValid() - noPortBackend.Spec.Backend = &extensions.IngressBackend{ServiceName: defaultBackend.ServiceName} - noForwardSlashPath := newValid() - noForwardSlashPath.Spec.Rules[0].IngressRuleValue.HTTP.Paths = []extensions.HTTPIngressPath{ - { - Path: "invalid", - Backend: defaultBackend, - }, - } - noPaths := newValid() - noPaths.Spec.Rules[0].IngressRuleValue.HTTP.Paths = []extensions.HTTPIngressPath{} - badHost := newValid() - badHost.Spec.Rules[0].Host = "foobar:80" - badRegexPath := newValid() - badPathExpr := "/invalid[" - badRegexPath.Spec.Rules[0].IngressRuleValue.HTTP.Paths = []extensions.HTTPIngressPath{ - { - Path: badPathExpr, - Backend: defaultBackend, - }, - } - badPathErr := fmt.Sprintf("spec.rules[0].http.paths[0].path: Invalid value: '%v'", badPathExpr) - hostIP := "127.0.0.1" - badHostIP := newValid() - badHostIP.Spec.Rules[0].Host = hostIP - badHostIPErr := fmt.Sprintf("spec.rules[0].host: Invalid value: '%v'", hostIP) - - errorCases := map[string]extensions.Ingress{ - "spec.backend.serviceName: Required value": servicelessBackend, - "spec.backend.serviceName: Invalid value": invalidNameBackend, - "spec.backend.servicePort: Invalid value": noPortBackend, - "spec.rules[0].host: Invalid value": badHost, - "spec.rules[0].http.paths: Required value": noPaths, - "spec.rules[0].http.paths[0].path: Invalid value": noForwardSlashPath, - } - errorCases[badPathErr] = badRegexPath - errorCases[badHostIPErr] = badHostIP - - wildcardHost := "foo.*.bar.com" - badWildcard := newValid() - badWildcard.Spec.Rules[0].Host = wildcardHost - badWildcardErr := fmt.Sprintf("spec.rules[0].host: Invalid value: '%v'", wildcardHost) - errorCases[badWildcardErr] = badWildcard - - for k, v := range errorCases { - errs := ValidateIngress(&v) - if len(errs) == 0 { - t.Errorf("expected failure for %q", k) - } else { - s := strings.Split(k, ":") - err := errs[0] - if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) { - t.Errorf("unexpected error: %q, expected: %q", err, k) - } - } - } -} - -func TestValidateIngressTLS(t *testing.T) { - defaultBackend := extensions.IngressBackend{ - ServiceName: "default-backend", - ServicePort: intstr.FromInt(80), - } - - newValid := func() extensions.Ingress { - return extensions.Ingress{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - }, - Spec: extensions.IngressSpec{ - Backend: &extensions.IngressBackend{ - ServiceName: "default-backend", - ServicePort: intstr.FromInt(80), - }, - Rules: []extensions.IngressRule{ - { - Host: "foo.bar.com", - IngressRuleValue: extensions.IngressRuleValue{ - HTTP: &extensions.HTTPIngressRuleValue{ - Paths: []extensions.HTTPIngressPath{ - { - Path: "/foo", - Backend: defaultBackend, - }, - }, - }, - }, - }, - }, - }, - Status: extensions.IngressStatus{ - LoadBalancer: api.LoadBalancerStatus{ - Ingress: []api.LoadBalancerIngress{ - {IP: "127.0.0.1"}, - }, - }, - }, - } - } - - errorCases := map[string]extensions.Ingress{} - - wildcardHost := "foo.*.bar.com" - badWildcardTLS := newValid() - badWildcardTLS.Spec.Rules[0].Host = "*.foo.bar.com" - badWildcardTLS.Spec.TLS = []extensions.IngressTLS{ - { - Hosts: []string{wildcardHost}, - }, - } - badWildcardTLSErr := fmt.Sprintf("spec.tls[0].hosts: Invalid value: '%v'", wildcardHost) - errorCases[badWildcardTLSErr] = badWildcardTLS - - for k, v := range errorCases { - errs := ValidateIngress(&v) - if len(errs) == 0 { - t.Errorf("expected failure for %q", k) - } else { - s := strings.Split(k, ":") - err := errs[0] - if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) { - t.Errorf("unexpected error: %q, expected: %q", err, k) - } - } - } -} - -func TestValidateIngressStatusUpdate(t *testing.T) { - defaultBackend := extensions.IngressBackend{ - ServiceName: "default-backend", - ServicePort: intstr.FromInt(80), - } - - newValid := func() extensions.Ingress { - return extensions.Ingress{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - ResourceVersion: "9", - }, - Spec: extensions.IngressSpec{ - Backend: &extensions.IngressBackend{ - ServiceName: "default-backend", - ServicePort: intstr.FromInt(80), - }, - Rules: []extensions.IngressRule{ - { - Host: "foo.bar.com", - IngressRuleValue: extensions.IngressRuleValue{ - HTTP: &extensions.HTTPIngressRuleValue{ - Paths: []extensions.HTTPIngressPath{ - { - Path: "/foo", - Backend: defaultBackend, - }, - }, - }, - }, - }, - }, - }, - Status: extensions.IngressStatus{ - LoadBalancer: api.LoadBalancerStatus{ - Ingress: []api.LoadBalancerIngress{ - {IP: "127.0.0.1", Hostname: "foo.bar.com"}, - }, - }, - }, - } - } - oldValue := newValid() - newValue := newValid() - newValue.Status = extensions.IngressStatus{ - LoadBalancer: api.LoadBalancerStatus{ - Ingress: []api.LoadBalancerIngress{ - {IP: "127.0.0.2", Hostname: "foo.com"}, - }, - }, - } - invalidIP := newValid() - invalidIP.Status = extensions.IngressStatus{ - LoadBalancer: api.LoadBalancerStatus{ - Ingress: []api.LoadBalancerIngress{ - {IP: "abcd", Hostname: "foo.com"}, - }, - }, - } - invalidHostname := newValid() - invalidHostname.Status = extensions.IngressStatus{ - LoadBalancer: api.LoadBalancerStatus{ - Ingress: []api.LoadBalancerIngress{ - {IP: "127.0.0.1", Hostname: "127.0.0.1"}, - }, - }, - } - - errs := ValidateIngressStatusUpdate(&newValue, &oldValue) - if len(errs) != 0 { - t.Errorf("Unexpected error %v", errs) - } - - errorCases := map[string]extensions.Ingress{ - "status.loadBalancer.ingress[0].ip: Invalid value": invalidIP, - "status.loadBalancer.ingress[0].hostname: Invalid value": invalidHostname, - } - for k, v := range errorCases { - errs := ValidateIngressStatusUpdate(&v, &oldValue) - if len(errs) == 0 { - t.Errorf("expected failure for %s", k) - } else { - s := strings.Split(k, ":") - err := errs[0] - if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) { - t.Errorf("unexpected error: %q, expected: %q", err, k) - } - } - } -} diff --git a/pkg/apis/networking/types.go b/pkg/apis/networking/types.go index 3cb70989c55..43233039d84 100644 --- a/pkg/apis/networking/types.go +++ b/pkg/apis/networking/types.go @@ -195,3 +195,172 @@ type NetworkPolicyList struct { Items []NetworkPolicy } + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Ingress is a collection of rules that allow inbound connections to reach the +// endpoints defined by a backend. An Ingress can be configured to give services +// externally-reachable urls, load balance traffic, terminate SSL, offer name +// based virtual hosting etc. +type Ingress struct { + metav1.TypeMeta + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + metav1.ObjectMeta + + // Spec is the desired state of the Ingress. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status + // +optional + Spec IngressSpec + + // Status is the current state of the Ingress. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status + // +optional + Status IngressStatus +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// IngressList is a collection of Ingress. +type IngressList struct { + metav1.TypeMeta + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + metav1.ListMeta + + // Items is the list of Ingress. + Items []Ingress +} + +// IngressSpec describes the Ingress the user wishes to exist. +type IngressSpec struct { + // A default backend capable of servicing requests that don't match any + // rule. At least one of 'backend' or 'rules' must be specified. This field + // is optional to allow the loadbalancer controller or defaulting logic to + // specify a global default. + // +optional + Backend *IngressBackend + + // TLS configuration. Currently the Ingress only supports a single TLS + // port, 443. If multiple members of this list specify different hosts, they + // will be multiplexed on the same port according to the hostname specified + // through the SNI TLS extension, if the ingress controller fulfilling the + // ingress supports SNI. + // +optional + TLS []IngressTLS + + // A list of host rules used to configure the Ingress. If unspecified, or + // no rule matches, all traffic is sent to the default backend. + // +optional + Rules []IngressRule + // TODO: Add the ability to specify load-balancer IP through claims +} + +// IngressTLS describes the transport layer security associated with an Ingress. +type IngressTLS struct { + // Hosts are a list of hosts included in the TLS certificate. The values in + // this list must match the name/s used in the tlsSecret. Defaults to the + // wildcard host setting for the loadbalancer controller fulfilling this + // Ingress, if left unspecified. + // +optional + Hosts []string + // SecretName is the name of the secret used to terminate SSL traffic on 443. + // Field is left optional to allow SSL routing based on SNI hostname alone. + // If the SNI host in a listener conflicts with the "Host" header field used + // by an IngressRule, the SNI host is used for termination and value of the + // Host header is used for routing. + // +optional + SecretName string + // TODO: Consider specifying different modes of termination, protocols etc. +} + +// IngressStatus describe the current state of the Ingress. +type IngressStatus struct { + // LoadBalancer contains the current status of the load-balancer. + // +optional + LoadBalancer api.LoadBalancerStatus +} + +// IngressRule represents the rules mapping the paths under a specified host to +// the related backend services. Incoming requests are first evaluated for a host +// match, then routed to the backend associated with the matching IngressRuleValue. +type IngressRule struct { + // Host is the fully qualified domain name of a network host, as defined + // by RFC 3986. Note the following deviations from the "host" part of the + // URI as defined in the RFC: + // 1. IPs are not allowed. Currently an IngressRuleValue can only apply to the + // IP in the Spec of the parent Ingress. + // 2. The `:` delimiter is not respected because ports are not allowed. + // Currently the port of an Ingress is implicitly :80 for http and + // :443 for https. + // Both these may change in the future. + // Incoming requests are matched against the host before the IngressRuleValue. + // If the host is unspecified, the Ingress routes all traffic based on the + // specified IngressRuleValue. + // +optional + Host string + // IngressRuleValue represents a rule to route requests for this IngressRule. + // If unspecified, the rule defaults to a http catch-all. Whether that sends + // just traffic matching the host to the default backend or all traffic to the + // default backend, is left to the controller fulfilling the Ingress. Http is + // currently the only supported IngressRuleValue. + // +optional + IngressRuleValue +} + +// IngressRuleValue represents a rule to apply against incoming requests. If the +// rule is satisfied, the request is routed to the specified backend. Currently +// mixing different types of rules in a single Ingress is disallowed, so exactly +// one of the following must be set. +type IngressRuleValue struct { + //TODO: + // 1. Consider renaming this resource and the associated rules so they + // aren't tied to Ingress. They can be used to route intra-cluster traffic. + // 2. Consider adding fields for ingress-type specific global options + // usable by a loadbalancer, like http keep-alive. + + // +optional + HTTP *HTTPIngressRuleValue +} + +// HTTPIngressRuleValue is a list of http selectors pointing to backends. +// In the example: http:///? -> backend where +// where parts of the url correspond to RFC 3986, this resource will be used +// to match against everything after the last '/' and before the first '?' +// or '#'. +type HTTPIngressRuleValue struct { + // A collection of paths that map requests to backends. + Paths []HTTPIngressPath + // TODO: Consider adding fields for ingress-type specific global + // options usable by a loadbalancer, like http keep-alive. +} + +// HTTPIngressPath associates a path regex with a backend. Incoming urls matching +// the path are forwarded to the backend. +type HTTPIngressPath struct { + // Path is an extended POSIX regex as defined by IEEE Std 1003.1, + // (i.e this follows the egrep/unix syntax, not the perl syntax) + // matched against the path of an incoming request. Currently it can + // contain characters disallowed from the conventional "path" + // part of a URL as defined by RFC 3986. Paths must begin with + // a '/'. If unspecified, the path defaults to a catch all sending + // traffic to the backend. + // +optional + Path string + + // Backend defines the referenced service endpoint to which the traffic + // will be forwarded to. + Backend IngressBackend +} + +// IngressBackend describes all endpoints for a given service and port. +type IngressBackend struct { + // Specifies the name of the referenced service. + ServiceName string + + // Specifies the port of the referenced service. + ServicePort intstr.IntOrString +} diff --git a/pkg/apis/networking/validation/validation.go b/pkg/apis/networking/validation/validation.go index c36932e44d0..6f62f66fe89 100644 --- a/pkg/apis/networking/validation/validation.go +++ b/pkg/apis/networking/validation/validation.go @@ -17,6 +17,10 @@ limitations under the License. package validation import ( + "net" + "regexp" + "strings" + apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/util/intstr" @@ -169,3 +173,145 @@ func ValidateIPBlock(ipb *networking.IPBlock, fldPath *field.Path) field.ErrorLi } return allErrs } + +// ValidateIngress tests if required fields in the Ingress are set. +func ValidateIngress(ingress *networking.Ingress) field.ErrorList { + allErrs := apivalidation.ValidateObjectMeta(&ingress.ObjectMeta, true, ValidateIngressName, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateIngressSpec(&ingress.Spec, field.NewPath("spec"))...) + return allErrs +} + +// ValidateIngressName validates that the given name can be used as an Ingress name. +var ValidateIngressName = apimachineryvalidation.NameIsDNSSubdomain + +func validateIngressTLS(spec *networking.IngressSpec, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + // TODO: Perform a more thorough validation of spec.TLS.Hosts that takes + // the wildcard spec from RFC 6125 into account. + for _, itls := range spec.TLS { + for i, host := range itls.Hosts { + if strings.Contains(host, "*") { + for _, msg := range validation.IsWildcardDNS1123Subdomain(host) { + allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("hosts"), host, msg)) + } + continue + } + for _, msg := range validation.IsDNS1123Subdomain(host) { + allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("hosts"), host, msg)) + } + } + } + + return allErrs +} + +// ValidateIngressSpec tests if required fields in the IngressSpec are set. +func ValidateIngressSpec(spec *networking.IngressSpec, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + // TODO: Is a default backend mandatory? + if spec.Backend != nil { + allErrs = append(allErrs, validateIngressBackend(spec.Backend, fldPath.Child("backend"))...) + } else if len(spec.Rules) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath, spec.Rules, "either `backend` or `rules` must be specified")) + } + if len(spec.Rules) > 0 { + allErrs = append(allErrs, validateIngressRules(spec.Rules, fldPath.Child("rules"))...) + } + if len(spec.TLS) > 0 { + allErrs = append(allErrs, validateIngressTLS(spec, fldPath.Child("tls"))...) + } + return allErrs +} + +// ValidateIngressUpdate tests if required fields in the Ingress are set. +func ValidateIngressUpdate(ingress, oldIngress *networking.Ingress) field.ErrorList { + allErrs := apivalidation.ValidateObjectMetaUpdate(&ingress.ObjectMeta, &oldIngress.ObjectMeta, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateIngressSpec(&ingress.Spec, field.NewPath("spec"))...) + return allErrs +} + +// ValidateIngressStatusUpdate tests if required fields in the Ingress are set when updating status. +func ValidateIngressStatusUpdate(ingress, oldIngress *networking.Ingress) field.ErrorList { + allErrs := apivalidation.ValidateObjectMetaUpdate(&ingress.ObjectMeta, &oldIngress.ObjectMeta, field.NewPath("metadata")) + allErrs = append(allErrs, apivalidation.ValidateLoadBalancerStatus(&ingress.Status.LoadBalancer, field.NewPath("status", "loadBalancer"))...) + return allErrs +} + +func validateIngressRules(ingressRules []networking.IngressRule, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(ingressRules) == 0 { + return append(allErrs, field.Required(fldPath, "")) + } + for i, ih := range ingressRules { + if len(ih.Host) > 0 { + if isIP := (net.ParseIP(ih.Host) != nil); isIP { + allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, "must be a DNS name, not an IP address")) + } + // TODO: Ports and ips are allowed in the host part of a url + // according to RFC 3986, consider allowing them. + if strings.Contains(ih.Host, "*") { + for _, msg := range validation.IsWildcardDNS1123Subdomain(ih.Host) { + allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, msg)) + } + continue + } + for _, msg := range validation.IsDNS1123Subdomain(ih.Host) { + allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, msg)) + } + } + allErrs = append(allErrs, validateIngressRuleValue(&ih.IngressRuleValue, fldPath.Index(0))...) + } + return allErrs +} + +func validateIngressRuleValue(ingressRule *networking.IngressRuleValue, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if ingressRule.HTTP != nil { + allErrs = append(allErrs, validateHTTPIngressRuleValue(ingressRule.HTTP, fldPath.Child("http"))...) + } + return allErrs +} + +func validateHTTPIngressRuleValue(httpIngressRuleValue *networking.HTTPIngressRuleValue, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(httpIngressRuleValue.Paths) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("paths"), "")) + } + for i, rule := range httpIngressRuleValue.Paths { + if len(rule.Path) > 0 { + if !strings.HasPrefix(rule.Path, "/") { + allErrs = append(allErrs, field.Invalid(fldPath.Child("paths").Index(i).Child("path"), rule.Path, "must be an absolute path")) + } + // TODO: More draconian path regex validation. + // Path must be a valid regex. This is the basic requirement. + // In addition to this any characters not allowed in a path per + // RFC 3986 section-3.3 cannot appear as a literal in the regex. + // Consider the example: http://host/valid?#bar, everything after + // the last '/' is a valid regex that matches valid#bar, which + // isn't a valid path, because the path terminates at the first ? + // or #. A more sophisticated form of validation would detect that + // the user is confusing url regexes with path regexes. + _, err := regexp.CompilePOSIX(rule.Path) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("paths").Index(i).Child("path"), rule.Path, "must be a valid regex")) + } + } + allErrs = append(allErrs, validateIngressBackend(&rule.Backend, fldPath.Child("backend"))...) + } + return allErrs +} + +// validateIngressBackend tests if a given backend is valid. +func validateIngressBackend(backend *networking.IngressBackend, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + // All backends must reference a single local service by name, and a single service port by name or number. + if len(backend.ServiceName) == 0 { + return append(allErrs, field.Required(fldPath.Child("serviceName"), "")) + } + for _, msg := range apivalidation.ValidateServiceName(backend.ServiceName, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("serviceName"), backend.ServiceName, msg)) + } + allErrs = append(allErrs, apivalidation.ValidatePortNumOrName(backend.ServicePort, fldPath.Child("servicePort"))...) + return allErrs +} diff --git a/pkg/apis/networking/validation/validation_test.go b/pkg/apis/networking/validation/validation_test.go index bacffbfa387..83787dcb8a5 100644 --- a/pkg/apis/networking/validation/validation_test.go +++ b/pkg/apis/networking/validation/validation_test.go @@ -17,6 +17,8 @@ limitations under the License. package validation import ( + "fmt" + "strings" "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -733,3 +735,269 @@ func TestValidateNetworkPolicyUpdate(t *testing.T) { } } } + +func TestValidateIngress(t *testing.T) { + defaultBackend := networking.IngressBackend{ + ServiceName: "default-backend", + ServicePort: intstr.FromInt(80), + } + + newValid := func() networking.Ingress { + return networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: metav1.NamespaceDefault, + }, + Spec: networking.IngressSpec{ + Backend: &networking.IngressBackend{ + ServiceName: "default-backend", + ServicePort: intstr.FromInt(80), + }, + Rules: []networking.IngressRule{ + { + Host: "foo.bar.com", + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ + { + Path: "/foo", + Backend: defaultBackend, + }, + }, + }, + }, + }, + }, + }, + Status: networking.IngressStatus{ + LoadBalancer: api.LoadBalancerStatus{ + Ingress: []api.LoadBalancerIngress{ + {IP: "127.0.0.1"}, + }, + }, + }, + } + } + servicelessBackend := newValid() + servicelessBackend.Spec.Backend.ServiceName = "" + invalidNameBackend := newValid() + invalidNameBackend.Spec.Backend.ServiceName = "defaultBackend" + noPortBackend := newValid() + noPortBackend.Spec.Backend = &networking.IngressBackend{ServiceName: defaultBackend.ServiceName} + noForwardSlashPath := newValid() + noForwardSlashPath.Spec.Rules[0].IngressRuleValue.HTTP.Paths = []networking.HTTPIngressPath{ + { + Path: "invalid", + Backend: defaultBackend, + }, + } + noPaths := newValid() + noPaths.Spec.Rules[0].IngressRuleValue.HTTP.Paths = []networking.HTTPIngressPath{} + badHost := newValid() + badHost.Spec.Rules[0].Host = "foobar:80" + badRegexPath := newValid() + badPathExpr := "/invalid[" + badRegexPath.Spec.Rules[0].IngressRuleValue.HTTP.Paths = []networking.HTTPIngressPath{ + { + Path: badPathExpr, + Backend: defaultBackend, + }, + } + badPathErr := fmt.Sprintf("spec.rules[0].http.paths[0].path: Invalid value: '%v'", badPathExpr) + hostIP := "127.0.0.1" + badHostIP := newValid() + badHostIP.Spec.Rules[0].Host = hostIP + badHostIPErr := fmt.Sprintf("spec.rules[0].host: Invalid value: '%v'", hostIP) + + errorCases := map[string]networking.Ingress{ + "spec.backend.serviceName: Required value": servicelessBackend, + "spec.backend.serviceName: Invalid value": invalidNameBackend, + "spec.backend.servicePort: Invalid value": noPortBackend, + "spec.rules[0].host: Invalid value": badHost, + "spec.rules[0].http.paths: Required value": noPaths, + "spec.rules[0].http.paths[0].path: Invalid value": noForwardSlashPath, + } + errorCases[badPathErr] = badRegexPath + errorCases[badHostIPErr] = badHostIP + + wildcardHost := "foo.*.bar.com" + badWildcard := newValid() + badWildcard.Spec.Rules[0].Host = wildcardHost + badWildcardErr := fmt.Sprintf("spec.rules[0].host: Invalid value: '%v'", wildcardHost) + errorCases[badWildcardErr] = badWildcard + + for k, v := range errorCases { + errs := ValidateIngress(&v) + if len(errs) == 0 { + t.Errorf("expected failure for %q", k) + } else { + s := strings.Split(k, ":") + err := errs[0] + if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) { + t.Errorf("unexpected error: %q, expected: %q", err, k) + } + } + } +} + +func TestValidateIngressTLS(t *testing.T) { + defaultBackend := networking.IngressBackend{ + ServiceName: "default-backend", + ServicePort: intstr.FromInt(80), + } + + newValid := func() networking.Ingress { + return networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: metav1.NamespaceDefault, + }, + Spec: networking.IngressSpec{ + Backend: &networking.IngressBackend{ + ServiceName: "default-backend", + ServicePort: intstr.FromInt(80), + }, + Rules: []networking.IngressRule{ + { + Host: "foo.bar.com", + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ + { + Path: "/foo", + Backend: defaultBackend, + }, + }, + }, + }, + }, + }, + }, + Status: networking.IngressStatus{ + LoadBalancer: api.LoadBalancerStatus{ + Ingress: []api.LoadBalancerIngress{ + {IP: "127.0.0.1"}, + }, + }, + }, + } + } + + errorCases := map[string]networking.Ingress{} + + wildcardHost := "foo.*.bar.com" + badWildcardTLS := newValid() + badWildcardTLS.Spec.Rules[0].Host = "*.foo.bar.com" + badWildcardTLS.Spec.TLS = []networking.IngressTLS{ + { + Hosts: []string{wildcardHost}, + }, + } + badWildcardTLSErr := fmt.Sprintf("spec.tls[0].hosts: Invalid value: '%v'", wildcardHost) + errorCases[badWildcardTLSErr] = badWildcardTLS + + for k, v := range errorCases { + errs := ValidateIngress(&v) + if len(errs) == 0 { + t.Errorf("expected failure for %q", k) + } else { + s := strings.Split(k, ":") + err := errs[0] + if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) { + t.Errorf("unexpected error: %q, expected: %q", err, k) + } + } + } +} + +func TestValidateIngressStatusUpdate(t *testing.T) { + defaultBackend := networking.IngressBackend{ + ServiceName: "default-backend", + ServicePort: intstr.FromInt(80), + } + + newValid := func() networking.Ingress { + return networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: metav1.NamespaceDefault, + ResourceVersion: "9", + }, + Spec: networking.IngressSpec{ + Backend: &networking.IngressBackend{ + ServiceName: "default-backend", + ServicePort: intstr.FromInt(80), + }, + Rules: []networking.IngressRule{ + { + Host: "foo.bar.com", + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ + { + Path: "/foo", + Backend: defaultBackend, + }, + }, + }, + }, + }, + }, + }, + Status: networking.IngressStatus{ + LoadBalancer: api.LoadBalancerStatus{ + Ingress: []api.LoadBalancerIngress{ + {IP: "127.0.0.1", Hostname: "foo.bar.com"}, + }, + }, + }, + } + } + oldValue := newValid() + newValue := newValid() + newValue.Status = networking.IngressStatus{ + LoadBalancer: api.LoadBalancerStatus{ + Ingress: []api.LoadBalancerIngress{ + {IP: "127.0.0.2", Hostname: "foo.com"}, + }, + }, + } + invalidIP := newValid() + invalidIP.Status = networking.IngressStatus{ + LoadBalancer: api.LoadBalancerStatus{ + Ingress: []api.LoadBalancerIngress{ + {IP: "abcd", Hostname: "foo.com"}, + }, + }, + } + invalidHostname := newValid() + invalidHostname.Status = networking.IngressStatus{ + LoadBalancer: api.LoadBalancerStatus{ + Ingress: []api.LoadBalancerIngress{ + {IP: "127.0.0.1", Hostname: "127.0.0.1"}, + }, + }, + } + + errs := ValidateIngressStatusUpdate(&newValue, &oldValue) + if len(errs) != 0 { + t.Errorf("Unexpected error %v", errs) + } + + errorCases := map[string]networking.Ingress{ + "status.loadBalancer.ingress[0].ip: Invalid value": invalidIP, + "status.loadBalancer.ingress[0].hostname: Invalid value": invalidHostname, + } + for k, v := range errorCases { + errs := ValidateIngressStatusUpdate(&v, &oldValue) + if len(errs) == 0 { + t.Errorf("expected failure for %s", k) + } else { + s := strings.Split(k, ":") + err := errs[0] + if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) { + t.Errorf("unexpected error: %q, expected: %q", err, k) + } + } + } +} diff --git a/pkg/printers/internalversion/printers.go b/pkg/printers/internalversion/printers.go index ada804f7e39..bb87e91221d 100644 --- a/pkg/printers/internalversion/printers.go +++ b/pkg/printers/internalversion/printers.go @@ -52,7 +52,6 @@ import ( "k8s.io/kubernetes/pkg/apis/coordination" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/core/helper" - "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/networking" "k8s.io/kubernetes/pkg/apis/policy" "k8s.io/kubernetes/pkg/apis/rbac" @@ -992,14 +991,14 @@ func printServiceList(list *api.ServiceList, options printers.PrintOptions) ([]m } // backendStringer behaves just like a string interface and converts the given backend to a string. -func backendStringer(backend *extensions.IngressBackend) string { +func backendStringer(backend *networking.IngressBackend) string { if backend == nil { return "" } return fmt.Sprintf("%v:%v", backend.ServiceName, backend.ServicePort.String()) } -func formatHosts(rules []extensions.IngressRule) string { +func formatHosts(rules []networking.IngressRule) string { list := []string{} max := 3 more := false @@ -1021,14 +1020,14 @@ func formatHosts(rules []extensions.IngressRule) string { return ret } -func formatPorts(tls []extensions.IngressTLS) string { +func formatPorts(tls []networking.IngressTLS) string { if len(tls) != 0 { return "80, 443" } return "80" } -func printIngress(obj *extensions.Ingress, options printers.PrintOptions) ([]metav1beta1.TableRow, error) { +func printIngress(obj *networking.Ingress, options printers.PrintOptions) ([]metav1beta1.TableRow, error) { row := metav1beta1.TableRow{ Object: runtime.RawExtension{Object: obj}, } @@ -1040,7 +1039,7 @@ func printIngress(obj *extensions.Ingress, options printers.PrintOptions) ([]met return []metav1beta1.TableRow{row}, nil } -func printIngressList(list *extensions.IngressList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) { +func printIngressList(list *networking.IngressList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) { rows := make([]metav1beta1.TableRow, 0, len(list.Items)) for i := range list.Items { r, err := printIngress(&list.Items[i], options) diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index bdc1757d82f..2bf55354caa 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -50,7 +50,7 @@ import ( "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/coordination" api "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/networking" "k8s.io/kubernetes/pkg/apis/policy" "k8s.io/kubernetes/pkg/apis/scheduling" "k8s.io/kubernetes/pkg/apis/storage" @@ -1069,7 +1069,7 @@ func contains(fields []string, field string) bool { } func TestPrintHunmanReadableIngressWithColumnLabels(t *testing.T) { - ingress := extensions.Ingress{ + ingress := networking.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: "test1", CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, @@ -1077,13 +1077,13 @@ func TestPrintHunmanReadableIngressWithColumnLabels(t *testing.T) { "app_name": "kubectl_test_ingress", }, }, - Spec: extensions.IngressSpec{ - Backend: &extensions.IngressBackend{ + Spec: networking.IngressSpec{ + Backend: &networking.IngressBackend{ ServiceName: "svc", ServicePort: intstr.FromInt(93), }, }, - Status: extensions.IngressStatus{ + Status: networking.IngressStatus{ LoadBalancer: api.LoadBalancerStatus{ Ingress: []api.LoadBalancerIngress{ { diff --git a/pkg/registry/extensions/rest/storage_extensions.go b/pkg/registry/extensions/rest/storage_extensions.go index 83bdce27c31..0a1fa286dcb 100644 --- a/pkg/registry/extensions/rest/storage_extensions.go +++ b/pkg/registry/extensions/rest/storage_extensions.go @@ -28,7 +28,7 @@ import ( deploymentstore "k8s.io/kubernetes/pkg/registry/apps/deployment/storage" replicasetstore "k8s.io/kubernetes/pkg/registry/apps/replicaset/storage" expcontrollerstore "k8s.io/kubernetes/pkg/registry/extensions/controller/storage" - ingressstore "k8s.io/kubernetes/pkg/registry/extensions/ingress/storage" + ingressstore "k8s.io/kubernetes/pkg/registry/networking/ingress/storage" networkpolicystore "k8s.io/kubernetes/pkg/registry/networking/networkpolicy/storage" pspstore "k8s.io/kubernetes/pkg/registry/policy/podsecuritypolicy/storage" ) diff --git a/pkg/registry/extensions/ingress/BUILD b/pkg/registry/networking/ingress/BUILD similarity index 100% rename from pkg/registry/extensions/ingress/BUILD rename to pkg/registry/networking/ingress/BUILD diff --git a/pkg/registry/extensions/ingress/doc.go b/pkg/registry/networking/ingress/doc.go similarity index 87% rename from pkg/registry/extensions/ingress/doc.go rename to pkg/registry/networking/ingress/doc.go index b8c38ed3854..c89d1f09e4a 100644 --- a/pkg/registry/extensions/ingress/doc.go +++ b/pkg/registry/networking/ingress/doc.go @@ -14,4 +14,4 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ingress // import "k8s.io/kubernetes/pkg/registry/extensions/ingress" +package ingress // import "k8s.io/kubernetes/pkg/registry/networking/ingress" diff --git a/pkg/registry/extensions/ingress/storage/BUILD b/pkg/registry/networking/ingress/storage/BUILD similarity index 100% rename from pkg/registry/extensions/ingress/storage/BUILD rename to pkg/registry/networking/ingress/storage/BUILD diff --git a/pkg/registry/extensions/ingress/storage/storage.go b/pkg/registry/networking/ingress/storage/storage.go similarity index 88% rename from pkg/registry/extensions/ingress/storage/storage.go rename to pkg/registry/networking/ingress/storage/storage.go index da7da8574ab..bf19d3d0f0a 100644 --- a/pkg/registry/extensions/ingress/storage/storage.go +++ b/pkg/registry/networking/ingress/storage/storage.go @@ -25,13 +25,14 @@ import ( genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/networking" "k8s.io/kubernetes/pkg/printers" printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" printerstorage "k8s.io/kubernetes/pkg/printers/storage" - "k8s.io/kubernetes/pkg/registry/extensions/ingress" + "k8s.io/kubernetes/pkg/registry/networking/ingress" ) -// rest implements a RESTStorage for replication controllers +// REST implements a RESTStorage for replication controllers type REST struct { *genericregistry.Store } @@ -39,8 +40,8 @@ type REST struct { // NewREST returns a RESTStorage object that will work against replication controllers. func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST) { store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &extensions.Ingress{} }, - NewListFunc: func() runtime.Object { return &extensions.IngressList{} }, + NewFunc: func() runtime.Object { return &networking.Ingress{} }, + NewListFunc: func() runtime.Object { return &networking.IngressList{} }, DefaultQualifiedResource: extensions.Resource("ingresses"), CreateStrategy: ingress.Strategy, @@ -72,8 +73,9 @@ type StatusREST struct { store *genericregistry.Store } +// New creates an instance of the StatusREST object func (r *StatusREST) New() runtime.Object { - return &extensions.Ingress{} + return &networking.Ingress{} } // Get retrieves the object from the storage. It is required to support Patch. diff --git a/pkg/registry/extensions/ingress/storage/storage_test.go b/pkg/registry/networking/ingress/storage/storage_test.go similarity index 83% rename from pkg/registry/extensions/ingress/storage/storage_test.go rename to pkg/registry/networking/ingress/storage/storage_test.go index f1643f59de6..cb38d59bd72 100644 --- a/pkg/registry/extensions/ingress/storage/storage_test.go +++ b/pkg/registry/networking/ingress/storage/storage_test.go @@ -29,6 +29,7 @@ import ( etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/networking" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -53,19 +54,19 @@ var ( defaultLoadBalancer = "127.0.0.1" defaultPath = "/foo" defaultPathMap = map[string]string{defaultPath: defaultBackendName} - defaultTLS = []extensions.IngressTLS{ + defaultTLS = []networking.IngressTLS{ {Hosts: []string{"foo.bar.com", "*.bar.com"}, SecretName: "fooSecret"}, } ) type IngressRuleValues map[string]string -func toHTTPIngressPaths(pathMap map[string]string) []extensions.HTTPIngressPath { - httpPaths := []extensions.HTTPIngressPath{} +func toHTTPIngressPaths(pathMap map[string]string) []networking.HTTPIngressPath { + httpPaths := []networking.HTTPIngressPath{} for path, backend := range pathMap { - httpPaths = append(httpPaths, extensions.HTTPIngressPath{ + httpPaths = append(httpPaths, networking.HTTPIngressPath{ Path: path, - Backend: extensions.IngressBackend{ + Backend: networking.IngressBackend{ ServiceName: backend, ServicePort: defaultBackendPort, }, @@ -74,13 +75,13 @@ func toHTTPIngressPaths(pathMap map[string]string) []extensions.HTTPIngressPath return httpPaths } -func toIngressRules(hostRules map[string]IngressRuleValues) []extensions.IngressRule { - rules := []extensions.IngressRule{} +func toIngressRules(hostRules map[string]IngressRuleValues) []networking.IngressRule { + rules := []networking.IngressRule{} for host, pathMap := range hostRules { - rules = append(rules, extensions.IngressRule{ + rules = append(rules, networking.IngressRule{ Host: host, - IngressRuleValue: extensions.IngressRuleValue{ - HTTP: &extensions.HTTPIngressRuleValue{ + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ Paths: toHTTPIngressPaths(pathMap), }, }, @@ -89,14 +90,14 @@ func toIngressRules(hostRules map[string]IngressRuleValues) []extensions.Ingress return rules } -func newIngress(pathMap map[string]string) *extensions.Ingress { - return &extensions.Ingress{ +func newIngress(pathMap map[string]string) *networking.Ingress { + return &networking.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, }, - Spec: extensions.IngressSpec{ - Backend: &extensions.IngressBackend{ + Spec: networking.IngressSpec{ + Backend: &networking.IngressBackend{ ServiceName: defaultBackendName, ServicePort: defaultBackendPort, }, @@ -105,7 +106,7 @@ func newIngress(pathMap map[string]string) *extensions.Ingress { }), TLS: defaultTLS, }, - Status: extensions.IngressStatus{ + Status: networking.IngressStatus{ LoadBalancer: api.LoadBalancerStatus{ Ingress: []api.LoadBalancerIngress{ {IP: defaultLoadBalancer}, @@ -115,7 +116,7 @@ func newIngress(pathMap map[string]string) *extensions.Ingress { } } -func validIngress() *extensions.Ingress { +func validIngress() *networking.Ingress { return newIngress(defaultPathMap) } @@ -126,8 +127,8 @@ func TestCreate(t *testing.T) { test := genericregistrytest.New(t, storage.Store) ingress := validIngress() noDefaultBackendAndRules := validIngress() - noDefaultBackendAndRules.Spec.Backend = &extensions.IngressBackend{} - noDefaultBackendAndRules.Spec.Rules = []extensions.IngressRule{} + noDefaultBackendAndRules.Spec.Backend = &networking.IngressBackend{} + noDefaultBackendAndRules.Spec.Rules = []networking.IngressRule{} badPath := validIngress() badPath.Spec.Rules = toIngressRules(map[string]IngressRuleValues{ "foo.bar.com": {"/invalid[": "svc"}}) @@ -149,11 +150,11 @@ func TestUpdate(t *testing.T) { validIngress(), // updateFunc func(obj runtime.Object) runtime.Object { - object := obj.(*extensions.Ingress) + object := obj.(*networking.Ingress) object.Spec.Rules = toIngressRules(map[string]IngressRuleValues{ "bar.foo.com": {"/bar": defaultBackendName}, }) - object.Spec.TLS = append(object.Spec.TLS, extensions.IngressTLS{ + object.Spec.TLS = append(object.Spec.TLS, networking.IngressTLS{ Hosts: []string{"*.google.com"}, SecretName: "googleSecret", }) @@ -161,13 +162,13 @@ func TestUpdate(t *testing.T) { }, // invalid updateFunc: ObjeceMeta is not to be tampered with. func(obj runtime.Object) runtime.Object { - object := obj.(*extensions.Ingress) + object := obj.(*networking.Ingress) object.Name = "" return object }, func(obj runtime.Object) runtime.Object { - object := obj.(*extensions.Ingress) + object := obj.(*networking.Ingress) object.Spec.Rules = toIngressRules(map[string]IngressRuleValues{ "foo.bar.com": {"/invalid[": "svc"}}) return object diff --git a/pkg/registry/extensions/ingress/strategy.go b/pkg/registry/networking/ingress/strategy.go similarity index 82% rename from pkg/registry/extensions/ingress/strategy.go rename to pkg/registry/networking/ingress/strategy.go index 1dc2efbd47e..a8c3696ce89 100644 --- a/pkg/registry/extensions/ingress/strategy.go +++ b/pkg/registry/networking/ingress/strategy.go @@ -24,8 +24,8 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apiserver/pkg/storage/names" "k8s.io/kubernetes/pkg/api/legacyscheme" - "k8s.io/kubernetes/pkg/apis/extensions" - "k8s.io/kubernetes/pkg/apis/extensions/validation" + "k8s.io/kubernetes/pkg/apis/networking" + "k8s.io/kubernetes/pkg/apis/networking/validation" ) // ingressStrategy implements verification logic for Replication Ingresss. @@ -44,17 +44,17 @@ func (ingressStrategy) NamespaceScoped() bool { // PrepareForCreate clears the status of an Ingress before creation. func (ingressStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { - ingress := obj.(*extensions.Ingress) + ingress := obj.(*networking.Ingress) // create cannot set status - ingress.Status = extensions.IngressStatus{} + ingress.Status = networking.IngressStatus{} ingress.Generation = 1 } // PrepareForUpdate clears fields that are not allowed to be set by end users on update. func (ingressStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { - newIngress := obj.(*extensions.Ingress) - oldIngress := old.(*extensions.Ingress) + newIngress := obj.(*networking.Ingress) + oldIngress := old.(*networking.Ingress) // Update is not allowed to set status newIngress.Status = oldIngress.Status @@ -69,7 +69,7 @@ func (ingressStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Ob // Validate validates a new Ingress. func (ingressStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { - ingress := obj.(*extensions.Ingress) + ingress := obj.(*networking.Ingress) err := validation.ValidateIngress(ingress) return err } @@ -85,8 +85,8 @@ func (ingressStrategy) AllowCreateOnUpdate() bool { // ValidateUpdate is the default update validation for an end user. func (ingressStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { - validationErrorList := validation.ValidateIngress(obj.(*extensions.Ingress)) - updateErrorList := validation.ValidateIngressUpdate(obj.(*extensions.Ingress), old.(*extensions.Ingress)) + validationErrorList := validation.ValidateIngress(obj.(*networking.Ingress)) + updateErrorList := validation.ValidateIngressUpdate(obj.(*networking.Ingress), old.(*networking.Ingress)) return append(validationErrorList, updateErrorList...) } @@ -99,17 +99,18 @@ type ingressStatusStrategy struct { ingressStrategy } +// StatusStrategy implements logic used to validate and prepare for updates of the status subresource var StatusStrategy = ingressStatusStrategy{Strategy} // PrepareForUpdate clears fields that are not allowed to be set by end users on update of status func (ingressStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { - newIngress := obj.(*extensions.Ingress) - oldIngress := old.(*extensions.Ingress) + newIngress := obj.(*networking.Ingress) + oldIngress := old.(*networking.Ingress) // status changes are not allowed to update spec newIngress.Spec = oldIngress.Spec } // ValidateUpdate is the default update validation for an end user updating status func (ingressStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { - return validation.ValidateIngressStatusUpdate(obj.(*extensions.Ingress), old.(*extensions.Ingress)) + return validation.ValidateIngressStatusUpdate(obj.(*networking.Ingress), old.(*networking.Ingress)) } diff --git a/pkg/registry/extensions/ingress/strategy_test.go b/pkg/registry/networking/ingress/strategy_test.go similarity index 86% rename from pkg/registry/extensions/ingress/strategy_test.go rename to pkg/registry/networking/ingress/strategy_test.go index e12b79ff912..ed8ff18de85 100644 --- a/pkg/registry/extensions/ingress/strategy_test.go +++ b/pkg/registry/networking/ingress/strategy_test.go @@ -23,30 +23,30 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" api "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/networking" ) -func newIngress() extensions.Ingress { - defaultBackend := extensions.IngressBackend{ +func newIngress() networking.Ingress { + defaultBackend := networking.IngressBackend{ ServiceName: "default-backend", ServicePort: intstr.FromInt(80), } - return extensions.Ingress{ + return networking.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: metav1.NamespaceDefault, }, - Spec: extensions.IngressSpec{ - Backend: &extensions.IngressBackend{ + Spec: networking.IngressSpec{ + Backend: &networking.IngressBackend{ ServiceName: "default-backend", ServicePort: intstr.FromInt(80), }, - Rules: []extensions.IngressRule{ + Rules: []networking.IngressRule{ { Host: "foo.bar.com", - IngressRuleValue: extensions.IngressRuleValue{ - HTTP: &extensions.HTTPIngressRuleValue{ - Paths: []extensions.HTTPIngressPath{ + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ { Path: "/foo", Backend: defaultBackend, @@ -57,7 +57,7 @@ func newIngress() extensions.Ingress { }, }, }, - Status: extensions.IngressStatus{ + Status: networking.IngressStatus{ LoadBalancer: api.LoadBalancerStatus{ Ingress: []api.LoadBalancerIngress{ {IP: "127.0.0.1"}, @@ -87,7 +87,7 @@ func TestIngressStrategy(t *testing.T) { } invalidIngress := newIngress() invalidIngress.ResourceVersion = "4" - invalidIngress.Spec = extensions.IngressSpec{} + invalidIngress.Spec = networking.IngressSpec{} Strategy.PrepareForUpdate(ctx, &invalidIngress, &ingress) errs = Strategy.ValidateUpdate(ctx, &invalidIngress, &ingress) if len(errs) == 0 { @@ -111,7 +111,7 @@ func TestIngressStatusStrategy(t *testing.T) { oldIngress.ResourceVersion = "4" newIngress.ResourceVersion = "4" newIngress.Spec.Backend.ServiceName = "ignore" - newIngress.Status = extensions.IngressStatus{ + newIngress.Status = networking.IngressStatus{ LoadBalancer: api.LoadBalancerStatus{ Ingress: []api.LoadBalancerIngress{ {IP: "127.0.0.2"}, diff --git a/test/test_owners.csv b/test/test_owners.csv index a7729d72d34..6ee4400bc33 100644 --- a/test/test_owners.csv +++ b/test/test_owners.csv @@ -569,7 +569,6 @@ k8s.io/kubernetes/pkg/apis/batch/validation,erictune,0, k8s.io/kubernetes/pkg/apis/componentconfig,jbeda,1, k8s.io/kubernetes/pkg/apis/extensions,bgrant0607,1, k8s.io/kubernetes/pkg/apis/extensions/v1beta1,madhusudancs,1, -k8s.io/kubernetes/pkg/apis/extensions/validation,nikhiljindal,1, k8s.io/kubernetes/pkg/apis/policy/validation,deads2k,1, k8s.io/kubernetes/pkg/apis/rbac/v1alpha1,liggitt,0, k8s.io/kubernetes/pkg/apis/rbac/validation,erictune,0, @@ -745,8 +744,6 @@ k8s.io/kubernetes/pkg/registry/extensions/daemonset,nikhiljindal,1, k8s.io/kubernetes/pkg/registry/extensions/daemonset/storage,kevin-wangzefeng,1, k8s.io/kubernetes/pkg/registry/extensions/deployment,dchen1107,1, k8s.io/kubernetes/pkg/registry/extensions/deployment/storage,timothysc,1, -k8s.io/kubernetes/pkg/registry/extensions/ingress,apelisse,1, -k8s.io/kubernetes/pkg/registry/extensions/ingress/storage,luxas,1, k8s.io/kubernetes/pkg/registry/extensions/replicaset,rrati,0, k8s.io/kubernetes/pkg/registry/extensions/replicaset/storage,wojtek-t,1, k8s.io/kubernetes/pkg/registry/extensions/rest,rrati,0,