diff --git a/provider/kubernetes/builder_configuration_test.go b/provider/kubernetes/builder_configuration_test.go new file mode 100644 index 000000000..7d94c800c --- /dev/null +++ b/provider/kubernetes/builder_configuration_test.go @@ -0,0 +1,320 @@ +package kubernetes + +import ( + "testing" + + "github.com/containous/traefik/types" + "github.com/stretchr/testify/assert" +) + +func buildConfiguration(opts ...func(*types.Configuration)) *types.Configuration { + conf := &types.Configuration{} + for _, opt := range opts { + opt(conf) + } + return conf +} + +// Backend + +func backends(opts ...func(*types.Backend) string) func(*types.Configuration) { + return func(c *types.Configuration) { + c.Backends = make(map[string]*types.Backend) + for _, opt := range opts { + b := &types.Backend{} + name := opt(b) + c.Backends[name] = b + } + } +} + +func backend(name string, opts ...func(*types.Backend)) func(*types.Backend) string { + return func(b *types.Backend) string { + for _, opt := range opts { + opt(b) + } + return name + } +} + +func servers(opts ...func(*types.Server) string) func(*types.Backend) { + return func(b *types.Backend) { + b.Servers = make(map[string]types.Server) + for _, opt := range opts { + s := &types.Server{} + name := opt(s) + b.Servers[name] = *s + } + } +} + +func server(url string, opts ...func(*types.Server)) func(*types.Server) string { + return func(s *types.Server) string { + for _, opt := range opts { + opt(s) + } + s.URL = url + return url + } +} + +func weight(value int) func(*types.Server) { + return func(s *types.Server) { + s.Weight = value + } +} + +func lbMethod(method string) func(*types.Backend) { + return func(b *types.Backend) { + if b.LoadBalancer == nil { + b.LoadBalancer = &types.LoadBalancer{} + } + b.LoadBalancer.Method = method + } +} + +func lbSticky() func(*types.Backend) { + return func(b *types.Backend) { + if b.LoadBalancer == nil { + b.LoadBalancer = &types.LoadBalancer{} + } + b.LoadBalancer.Sticky = true + } +} + +func circuitBreaker(exp string) func(*types.Backend) { + return func(b *types.Backend) { + b.CircuitBreaker = &types.CircuitBreaker{} + b.CircuitBreaker.Expression = exp + } +} + +// Frontend + +func buildFrontends(opts ...func(*types.Frontend) string) map[string]*types.Frontend { + fronts := make(map[string]*types.Frontend) + for _, opt := range opts { + f := &types.Frontend{} + name := opt(f) + fronts[name] = f + } + return fronts +} + +func frontends(opts ...func(*types.Frontend) string) func(*types.Configuration) { + return func(c *types.Configuration) { + c.Frontends = make(map[string]*types.Frontend) + for _, opt := range opts { + f := &types.Frontend{} + name := opt(f) + c.Frontends[name] = f + } + } +} + +func frontend(backend string, opts ...func(*types.Frontend)) func(*types.Frontend) string { + return func(f *types.Frontend) string { + for _, opt := range opts { + opt(f) + } + f.Backend = backend + return backend + } +} + +func passHostHeader() func(*types.Frontend) { + return func(f *types.Frontend) { + f.PassHostHeader = true + } +} + +func entryPoints(eps ...string) func(*types.Frontend) { + return func(f *types.Frontend) { + f.EntryPoints = eps + } +} + +func basicAuth(auth ...string) func(*types.Frontend) { + return func(f *types.Frontend) { + f.BasicAuth = auth + } +} + +func whitelistSourceRange(ranges ...string) func(*types.Frontend) { + return func(f *types.Frontend) { + f.WhitelistSourceRange = ranges + } +} + +func priority(value int) func(*types.Frontend) { + return func(f *types.Frontend) { + f.Priority = value + } +} + +func redirect(value string) func(*types.Frontend) { + return func(f *types.Frontend) { + f.Redirect = value + } +} + +func passTLSCert() func(*types.Frontend) { + return func(f *types.Frontend) { + f.PassTLSCert = true + } +} + +func routes(opts ...func(*types.Route) string) func(*types.Frontend) { + return func(f *types.Frontend) { + f.Routes = make(map[string]types.Route) + for _, opt := range opts { + s := &types.Route{} + name := opt(s) + f.Routes[name] = *s + } + } +} + +func route(name string, rule string) func(*types.Route) string { + return func(r *types.Route) string { + r.Rule = rule + return name + } +} + +// Test + +func TestBuildConfiguration(t *testing.T) { + actual := buildConfiguration( + backends( + backend("foo/bar", + servers( + server("http://10.10.0.1:8080", weight(1)), + server("http://10.21.0.1:8080", weight(1)), + ), + lbMethod("wrr"), + ), + backend("foo/namedthing", + servers(server("https://example.com", weight(1))), + lbMethod("wrr"), + ), + backend("bar", + servers( + server("https://10.15.0.1:8443", weight(1)), + server("https://10.15.0.2:9443", weight(1)), + ), + lbMethod("wrr"), + ), + ), + frontends( + frontend("foo/bar", + passHostHeader(), + routes( + route("/bar", "PathPrefix:/bar"), + route("foo", "Host:foo"), + ), + ), + frontend("foo/namedthing", + passHostHeader(), + routes( + route("/namedthing", "PathPrefix:/namedthing"), + route("foo", "Host:foo"), + ), + ), + frontend("bar", + passHostHeader(), + routes( + route("bar", "Host:bar"), + ), + ), + ), + ) + + assert.EqualValues(t, sampleConfiguration(), actual) +} + +func sampleConfiguration() *types.Configuration { + return &types.Configuration{ + Backends: map[string]*types.Backend{ + "foo/bar": { + Servers: map[string]types.Server{ + "http://10.10.0.1:8080": { + URL: "http://10.10.0.1:8080", + Weight: 1, + }, + "http://10.21.0.1:8080": { + URL: "http://10.21.0.1:8080", + Weight: 1, + }, + }, + CircuitBreaker: nil, + LoadBalancer: &types.LoadBalancer{ + Method: "wrr", + }, + }, + "foo/namedthing": { + Servers: map[string]types.Server{ + "https://example.com": { + URL: "https://example.com", + Weight: 1, + }, + }, + CircuitBreaker: nil, + LoadBalancer: &types.LoadBalancer{ + Method: "wrr", + }, + }, + "bar": { + Servers: map[string]types.Server{ + "https://10.15.0.1:8443": { + URL: "https://10.15.0.1:8443", + Weight: 1, + }, + "https://10.15.0.2:9443": { + URL: "https://10.15.0.2:9443", + Weight: 1, + }, + }, + CircuitBreaker: nil, + LoadBalancer: &types.LoadBalancer{ + Method: "wrr", + }, + }, + }, + Frontends: map[string]*types.Frontend{ + "foo/bar": { + Backend: "foo/bar", + PassHostHeader: true, + Routes: map[string]types.Route{ + "/bar": { + Rule: "PathPrefix:/bar", + }, + "foo": { + Rule: "Host:foo", + }, + }, + }, + "foo/namedthing": { + Backend: "foo/namedthing", + PassHostHeader: true, + Routes: map[string]types.Route{ + "/namedthing": { + Rule: "PathPrefix:/namedthing", + }, + "foo": { + Rule: "Host:foo", + }, + }, + }, + "bar": { + Backend: "bar", + PassHostHeader: true, + Routes: map[string]types.Route{ + "bar": { + Rule: "Host:bar", + }, + }, + }, + }, + } +} diff --git a/provider/kubernetes/builder_endpoint_test.go b/provider/kubernetes/builder_endpoint_test.go new file mode 100644 index 000000000..f4cf5e084 --- /dev/null +++ b/provider/kubernetes/builder_endpoint_test.go @@ -0,0 +1,150 @@ +package kubernetes + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/client-go/pkg/api/v1" + "k8s.io/client-go/pkg/types" +) + +func buildEndpoint(opts ...func(*v1.Endpoints)) *v1.Endpoints { + e := &v1.Endpoints{} + for _, opt := range opts { + opt(e) + } + return e +} + +func eNamespace(value string) func(*v1.Endpoints) { + return func(i *v1.Endpoints) { + i.Namespace = value + } +} + +func eName(value string) func(*v1.Endpoints) { + return func(i *v1.Endpoints) { + i.Name = value + } +} + +func eUID(value types.UID) func(*v1.Endpoints) { + return func(i *v1.Endpoints) { + i.UID = value + } +} + +func subset(opts ...func(*v1.EndpointSubset)) func(*v1.Endpoints) { + return func(e *v1.Endpoints) { + s := &v1.EndpointSubset{} + for _, opt := range opts { + opt(s) + } + e.Subsets = append(e.Subsets, *s) + } +} + +func eAddresses(opts ...func(*v1.EndpointAddress)) func(*v1.EndpointSubset) { + return func(subset *v1.EndpointSubset) { + a := &v1.EndpointAddress{} + for _, opt := range opts { + opt(a) + } + subset.Addresses = append(subset.Addresses, *a) + } +} + +func eAddress(ip string) func(*v1.EndpointAddress) { + return func(address *v1.EndpointAddress) { + address.IP = ip + } +} + +func ePorts(opts ...func(port *v1.EndpointPort)) func(*v1.EndpointSubset) { + return func(spec *v1.EndpointSubset) { + for _, opt := range opts { + p := &v1.EndpointPort{} + opt(p) + spec.Ports = append(spec.Ports, *p) + } + } +} + +func ePort(port int32, name string) func(*v1.EndpointPort) { + return func(sp *v1.EndpointPort) { + sp.Port = port + sp.Name = name + } +} + +// Test + +func TestBuildEndpoint(t *testing.T) { + actual := buildEndpoint( + eNamespace("testing"), + eName("service3"), + eUID("3"), + subset( + eAddresses(eAddress("10.15.0.1")), + ePorts( + ePort(8080, "http"), + ePort(8443, "https"), + ), + ), + subset( + eAddresses(eAddress("10.15.0.2")), + ePorts( + ePort(9080, "http"), + ePort(9443, "https"), + ), + ), + ) + + assert.EqualValues(t, sampleEndpoint1(), actual) +} + +func sampleEndpoint1() *v1.Endpoints { + return &v1.Endpoints{ + ObjectMeta: v1.ObjectMeta{ + Name: "service3", + UID: "3", + Namespace: "testing", + }, + Subsets: []v1.EndpointSubset{ + { + Addresses: []v1.EndpointAddress{ + { + IP: "10.15.0.1", + }, + }, + Ports: []v1.EndpointPort{ + { + Name: "http", + Port: 8080, + }, + { + Name: "https", + Port: 8443, + }, + }, + }, + { + Addresses: []v1.EndpointAddress{ + { + IP: "10.15.0.2", + }, + }, + Ports: []v1.EndpointPort{ + { + Name: "http", + Port: 9080, + }, + { + Name: "https", + Port: 9443, + }, + }, + }, + }, + } +} diff --git a/provider/kubernetes/builder_ingress_test.go b/provider/kubernetes/builder_ingress_test.go new file mode 100644 index 000000000..5c54cdfbb --- /dev/null +++ b/provider/kubernetes/builder_ingress_test.go @@ -0,0 +1,169 @@ +package kubernetes + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/client-go/pkg/api/v1" + "k8s.io/client-go/pkg/apis/extensions/v1beta1" + "k8s.io/client-go/pkg/util/intstr" +) + +func buildIngress(opts ...func(*v1beta1.Ingress)) *v1beta1.Ingress { + i := &v1beta1.Ingress{} + for _, opt := range opts { + opt(i) + } + return i +} + +func iNamespace(value string) func(*v1beta1.Ingress) { + return func(i *v1beta1.Ingress) { + i.Namespace = value + } +} + +func iAnnotation(name string, value string) func(*v1beta1.Ingress) { + return func(i *v1beta1.Ingress) { + if i.Annotations == nil { + i.Annotations = make(map[string]string) + } + i.Annotations[name] = value + } +} + +func iRules(opts ...func(*v1beta1.IngressSpec)) func(*v1beta1.Ingress) { + return func(i *v1beta1.Ingress) { + s := &v1beta1.IngressSpec{} + for _, opt := range opts { + opt(s) + } + i.Spec = *s + } +} + +func iRule(opts ...func(*v1beta1.IngressRule)) func(*v1beta1.IngressSpec) { + return func(spec *v1beta1.IngressSpec) { + r := &v1beta1.IngressRule{} + for _, opt := range opts { + opt(r) + } + spec.Rules = append(spec.Rules, *r) + } +} + +func iHost(name string) func(*v1beta1.IngressRule) { + return func(rule *v1beta1.IngressRule) { + rule.Host = name + } +} + +func iPaths(opts ...func(*v1beta1.HTTPIngressRuleValue)) func(*v1beta1.IngressRule) { + return func(rule *v1beta1.IngressRule) { + rule.HTTP = &v1beta1.HTTPIngressRuleValue{} + for _, opt := range opts { + opt(rule.HTTP) + } + } +} + +func onePath(opts ...func(*v1beta1.HTTPIngressPath)) func(*v1beta1.HTTPIngressRuleValue) { + return func(irv *v1beta1.HTTPIngressRuleValue) { + p := &v1beta1.HTTPIngressPath{} + for _, opt := range opts { + opt(p) + } + irv.Paths = append(irv.Paths, *p) + } +} + +func iPath(name string) func(*v1beta1.HTTPIngressPath) { + return func(p *v1beta1.HTTPIngressPath) { + p.Path = name + } +} + +func iBackend(name string, port intstr.IntOrString) func(*v1beta1.HTTPIngressPath) { + return func(p *v1beta1.HTTPIngressPath) { + p.Backend = v1beta1.IngressBackend{ + ServiceName: name, + ServicePort: port, + } + } +} + +// Test + +func TestBuildIngress(t *testing.T) { + i := buildIngress( + iNamespace("testing"), + iRules( + iRule(iHost("foo"), iPaths( + onePath(iPath("/bar"), iBackend("service1", intstr.FromInt(80))), + onePath(iPath("/namedthing"), iBackend("service4", intstr.FromString("https")))), + ), + iRule(iHost("bar"), iPaths( + onePath(iBackend("service3", intstr.FromString("https"))), + onePath(iBackend("service2", intstr.FromInt(802))), + ), + ), + )) + + assert.EqualValues(t, sampleIngress(), i) +} + +func sampleIngress() *v1beta1.Ingress { + return &v1beta1.Ingress{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "testing", + }, + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ + { + Host: "foo", + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ + { + Path: "/bar", + Backend: v1beta1.IngressBackend{ + ServiceName: "service1", + ServicePort: intstr.FromInt(80), + }, + }, + { + Path: "/namedthing", + Backend: v1beta1.IngressBackend{ + ServiceName: "service4", + ServicePort: intstr.FromString("https"), + }, + }, + }, + }, + }, + }, + { + Host: "bar", + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ + { + Backend: v1beta1.IngressBackend{ + ServiceName: "service3", + ServicePort: intstr.FromString("https"), + }, + }, + { + Backend: v1beta1.IngressBackend{ + ServiceName: "service2", + ServicePort: intstr.FromInt(802), + }, + }, + }, + }, + }, + }, + }, + }, + } +} diff --git a/provider/kubernetes/builder_service_test.go b/provider/kubernetes/builder_service_test.go new file mode 100644 index 000000000..d7e817e53 --- /dev/null +++ b/provider/kubernetes/builder_service_test.go @@ -0,0 +1,165 @@ +package kubernetes + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/client-go/pkg/api/v1" + "k8s.io/client-go/pkg/types" +) + +func buildService(opts ...func(*v1.Service)) *v1.Service { + s := &v1.Service{} + for _, opt := range opts { + opt(s) + } + return s +} + +func sNamespace(value string) func(*v1.Service) { + return func(i *v1.Service) { + i.Namespace = value + } +} + +func sName(value string) func(*v1.Service) { + return func(i *v1.Service) { + i.Name = value + } +} + +func sUID(value types.UID) func(*v1.Service) { + return func(i *v1.Service) { + i.UID = value + } +} + +func sAnnotation(name string, value string) func(*v1.Service) { + return func(s *v1.Service) { + if s.Annotations == nil { + s.Annotations = make(map[string]string) + } + s.Annotations[name] = value + } +} + +func sSpec(opts ...func(*v1.ServiceSpec)) func(*v1.Service) { + return func(i *v1.Service) { + spec := &v1.ServiceSpec{} + for _, opt := range opts { + opt(spec) + } + i.Spec = *spec + } +} + +func clusterIP(ip string) func(*v1.ServiceSpec) { + return func(spec *v1.ServiceSpec) { + spec.ClusterIP = ip + } +} + +func sType(value v1.ServiceType) func(*v1.ServiceSpec) { + return func(spec *v1.ServiceSpec) { + spec.Type = value + } +} + +func sExternalName(name string) func(*v1.ServiceSpec) { + return func(spec *v1.ServiceSpec) { + spec.ExternalName = name + } +} + +func sPorts(opts ...func(*v1.ServicePort)) func(*v1.ServiceSpec) { + return func(spec *v1.ServiceSpec) { + for _, opt := range opts { + p := &v1.ServicePort{} + opt(p) + spec.Ports = append(spec.Ports, *p) + } + } +} + +func sPort(port int32, name string) func(*v1.ServicePort) { + return func(sp *v1.ServicePort) { + sp.Port = port + sp.Name = name + } +} + +// Test + +func TestBuildService(t *testing.T) { + actual1 := buildService( + sName("service1"), + sNamespace("testing"), + sUID("1"), + sSpec( + clusterIP("10.0.0.1"), + sPorts(sPort(80, "")), + ), + ) + + assert.EqualValues(t, sampleService1(), actual1) + + actual2 := buildService( + sName("service3"), + sNamespace("testing"), + sUID("3"), + sSpec( + clusterIP("10.0.0.3"), + sType("ExternalName"), + sExternalName("example.com"), + sPorts( + sPort(80, "http"), + sPort(443, "https"), + ), + ), + ) + + assert.EqualValues(t, sampleService2(), actual2) +} + +func sampleService1() *v1.Service { + return &v1.Service{ + ObjectMeta: v1.ObjectMeta{ + Name: "service1", + UID: "1", + Namespace: "testing", + }, + Spec: v1.ServiceSpec{ + ClusterIP: "10.0.0.1", + Ports: []v1.ServicePort{ + { + Port: 80, + }, + }, + }, + } +} + +func sampleService2() *v1.Service { + return &v1.Service{ + ObjectMeta: v1.ObjectMeta{ + Name: "service3", + UID: "3", + Namespace: "testing", + }, + Spec: v1.ServiceSpec{ + ClusterIP: "10.0.0.3", + Type: "ExternalName", + ExternalName: "example.com", + Ports: []v1.ServicePort{ + { + Name: "http", + Port: 80, + }, + { + Name: "https", + Port: 443, + }, + }, + }, + } +} diff --git a/provider/kubernetes/kubernetes_test.go b/provider/kubernetes/kubernetes_test.go index f62c8c404..7b3d23937 100644 --- a/provider/kubernetes/kubernetes_test.go +++ b/provider/kubernetes/kubernetes_test.go @@ -7,215 +7,103 @@ import ( "testing" "github.com/containous/traefik/provider/label" - "github.com/containous/traefik/types" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/pkg/apis/extensions/v1beta1" "k8s.io/client-go/pkg/util/intstr" ) func TestLoadIngresses(t *testing.T) { - ingresses := []*v1beta1.Ingress{{ - ObjectMeta: v1.ObjectMeta{ - Namespace: "testing", - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "foo", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/bar", - Backend: v1beta1.IngressBackend{ - ServiceName: "service1", - ServicePort: intstr.FromInt(80), - }, - }, - { - Path: "/namedthing", - Backend: v1beta1.IngressBackend{ - ServiceName: "service4", - ServicePort: intstr.FromString("https"), - }, - }, - }, - }, - }, - }, - { - Host: "bar", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Backend: v1beta1.IngressBackend{ - ServiceName: "service3", - ServicePort: intstr.FromString("https"), - }, - }, - { - Backend: v1beta1.IngressBackend{ - ServiceName: "service2", - ServicePort: intstr.FromInt(802), - }, - }, - }, - }, - }, - }, - }, - }, - }} + ingresses := []*v1beta1.Ingress{ + buildIngress( + iNamespace("testing"), + iRules( + iRule(iHost("foo"), + iPaths( + onePath(iPath("/bar"), iBackend("service1", intstr.FromInt(80))), + onePath(iPath("/namedthing"), iBackend("service4", intstr.FromString("https")))), + ), + iRule(iHost("bar"), + iPaths( + onePath(iBackend("service3", intstr.FromString("https"))), + onePath(iBackend("service2", intstr.FromInt(802)))), + ), + ), + ), + } + services := []*v1.Service{ - { - ObjectMeta: v1.ObjectMeta{ - Name: "service1", - UID: "1", - Namespace: "testing", - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.1", - Ports: []v1.ServicePort{ - { - Port: 80, - }, - }, - }, - }, - { - ObjectMeta: v1.ObjectMeta{ - Name: "service2", - UID: "2", - Namespace: "testing", - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.2", - Ports: []v1.ServicePort{ - { - Port: 802, - }, - }, - }, - }, - { - ObjectMeta: v1.ObjectMeta{ - Name: "service3", - UID: "3", - Namespace: "testing", - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.3", - Ports: []v1.ServicePort{ - { - Name: "http", - Port: 80, - }, - { - Name: "https", - Port: 443, - }, - }, - }, - }, - { - ObjectMeta: v1.ObjectMeta{ - Name: "service4", - UID: "4", - Namespace: "testing", - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.4", - Type: "ExternalName", - ExternalName: "example.com", - Ports: []v1.ServicePort{ - { - Name: "https", - Port: 443, - }, - }, - }, - }, + buildService( + sName("service1"), + sNamespace("testing"), + sUID("1"), + sSpec( + clusterIP("10.0.0.1"), + sPorts(sPort(80, ""))), + ), + buildService( + sName("service2"), + sNamespace("testing"), + sUID("2"), + sSpec( + clusterIP("10.0.0.2"), + sPorts(sPort(802, ""))), + ), + buildService( + sName("service3"), + sNamespace("testing"), + sUID("3"), + sSpec( + clusterIP("10.0.0.3"), + sPorts( + sPort(80, "http"), + sPort(443, "https")), + ), + ), + buildService( + sName("service4"), + sNamespace("testing"), + sUID("4"), + sSpec( + clusterIP("10.0.0.4"), + sType("ExternalName"), + sExternalName("example.com"), + sPorts(sPort(443, "https"))), + ), } + endpoints := []*v1.Endpoints{ - { - ObjectMeta: v1.ObjectMeta{ - Name: "service1", - UID: "1", - Namespace: "testing", - }, - Subsets: []v1.EndpointSubset{ - { - Addresses: []v1.EndpointAddress{ - { - IP: "10.10.0.1", - }, - }, - Ports: []v1.EndpointPort{ - { - Port: 8080, - }, - }, - }, - { - Addresses: []v1.EndpointAddress{ - { - IP: "10.21.0.1", - }, - }, - Ports: []v1.EndpointPort{ - { - Port: 8080, - }, - }, - }, - }, - }, - { - ObjectMeta: v1.ObjectMeta{ - Name: "service3", - UID: "3", - Namespace: "testing", - }, - Subsets: []v1.EndpointSubset{ - { - Addresses: []v1.EndpointAddress{ - { - IP: "10.15.0.1", - }, - }, - Ports: []v1.EndpointPort{ - { - Name: "http", - Port: 8080, - }, - { - Name: "https", - Port: 8443, - }, - }, - }, - { - Addresses: []v1.EndpointAddress{ - { - IP: "10.15.0.2", - }, - }, - Ports: []v1.EndpointPort{ - { - Name: "http", - Port: 9080, - }, - { - Name: "https", - Port: 9443, - }, - }, - }, - }, - }, + buildEndpoint( + eNamespace("testing"), + eName("service1"), + eUID("1"), + subset( + eAddresses(eAddress("10.10.0.1")), + ePorts(ePort(8080, ""))), + subset( + eAddresses(eAddress("10.21.0.1")), + ePorts(ePort(8080, ""))), + ), + buildEndpoint( + eNamespace("testing"), + eName("service3"), + eUID("3"), + subset( + eAddresses(eAddress("10.15.0.1")), + ePorts( + ePort(8080, "http"), + ePort(8443, "https")), + ), + subset( + eAddresses(eAddress("10.15.0.2")), + ePorts( + ePort(9080, "http"), + ePort(9443, "https")), + ), + ), } + watchChan := make(chan interface{}) client := clientMock{ ingresses: ingresses, @@ -224,94 +112,48 @@ func TestLoadIngresses(t *testing.T) { watchChan: watchChan, } provider := Provider{} - actual, err := provider.loadIngresses(client) - if err != nil { - t.Fatalf("error %+v", err) - } - expected := &types.Configuration{ - Backends: map[string]*types.Backend{ - "foo/bar": { - Servers: map[string]types.Server{ - "http://10.10.0.1:8080": { - URL: "http://10.10.0.1:8080", - Weight: 1, - }, - "http://10.21.0.1:8080": { - URL: "http://10.21.0.1:8080", - Weight: 1, - }, - }, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - "foo/namedthing": { - Servers: map[string]types.Server{ - "https://example.com": { - URL: "https://example.com", - Weight: 1, - }, - }, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - "bar": { - Servers: map[string]types.Server{ - "https://10.15.0.1:8443": { - URL: "https://10.15.0.1:8443", - Weight: 1, - }, - "https://10.15.0.2:9443": { - URL: "https://10.15.0.2:9443", - Weight: 1, - }, - }, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - }, - Frontends: map[string]*types.Frontend{ - "foo/bar": { - Backend: "foo/bar", - PassHostHeader: true, - Routes: map[string]types.Route{ - "/bar": { - Rule: "PathPrefix:/bar", - }, - "foo": { - Rule: "Host:foo", - }, - }, - }, - "foo/namedthing": { - Backend: "foo/namedthing", - PassHostHeader: true, - Routes: map[string]types.Route{ - "/namedthing": { - Rule: "PathPrefix:/namedthing", - }, - "foo": { - Rule: "Host:foo", - }, - }, - }, - "bar": { - Backend: "bar", - PassHostHeader: true, - Routes: map[string]types.Route{ - "bar": { - Rule: "Host:bar", - }, - }, - }, - }, - } + actual, err := provider.loadIngresses(client) + require.NoError(t, err, "error loading ingresses") + + expected := buildConfiguration( + backends( + backend("foo/bar", + lbMethod("wrr"), + servers( + server("http://10.10.0.1:8080", weight(1)), + server("http://10.21.0.1:8080", weight(1))), + ), + backend("foo/namedthing", + lbMethod("wrr"), + servers(server("https://example.com", weight(1))), + ), + backend("bar", + lbMethod("wrr"), + servers( + server("https://10.15.0.1:8443", weight(1)), + server("https://10.15.0.2:9443", weight(1))), + ), + ), + frontends( + frontend("foo/bar", + passHostHeader(), + routes( + route("/bar", "PathPrefix:/bar"), + route("foo", "Host:foo")), + ), + frontend("foo/namedthing", + passHostHeader(), + routes( + route("/namedthing", "PathPrefix:/namedthing"), + route("foo", "Host:foo")), + ), + frontend("bar", + passHostHeader(), + routes(route("bar", "Host:bar")), + ), + ), + ) assert.Equal(t, expected, actual) } @@ -348,28 +190,13 @@ func TestRuleType(t *testing.T) { test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() - ingress := &v1beta1.Ingress{ - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "host", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/path", - Backend: v1beta1.IngressBackend{ - ServiceName: "service", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - }, - }, - } + + ingress := buildIngress(iRules(iRule( + iHost("host"), + iPaths( + onePath(iPath("/path"), iBackend("service", intstr.FromInt(80))), + ), + ))) if test.ingressRuleType != "" { ingress.ObjectMeta.Annotations = map[string]string{ @@ -377,21 +204,11 @@ func TestRuleType(t *testing.T) { } } - service := &v1.Service{ - ObjectMeta: v1.ObjectMeta{ - Name: "service", - UID: "1", - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.1", - Ports: []v1.ServicePort{ - { - Name: "http", - Port: 801, - }, - }, - }, - } + service := buildService( + sName("service"), + sUID("1"), + sSpec(sPorts(sPort(801, "http"))), + ) watchChan := make(chan interface{}) client := clientMock{ @@ -400,75 +217,41 @@ func TestRuleType(t *testing.T) { watchChan: watchChan, } provider := Provider{DisablePassHostHeaders: true} + actualConfig, err := provider.loadIngresses(client) - if err != nil { - t.Fatalf("error loading ingresses: %+v", err) - } + require.NoError(t, err, "error loading ingresses") - actual := actualConfig.Frontends - expected := map[string]*types.Frontend{ - "host/path": { - Backend: "host/path", - Routes: map[string]types.Route{ - "/path": { - Rule: fmt.Sprintf("%s:/path", test.frontendRuleType), - }, - "host": { - Rule: "Host:host", - }, - }, - }, - } + expected := buildFrontends(frontend("host/path", + routes( + route("/path", fmt.Sprintf("%s:/path", test.frontendRuleType)), + route("host", "Host:host")), + )) - assert.Equal(t, expected, actual) + assert.Equal(t, expected, actualConfig.Frontends) }) } } func TestGetPassHostHeader(t *testing.T) { - ingresses := []*v1beta1.Ingress{{ - ObjectMeta: v1.ObjectMeta{ - Namespace: "awesome", - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "foo", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/bar", - Backend: v1beta1.IngressBackend{ - ServiceName: "service1", - ServicePort: intstr.FromInt(801), - }, - }, - }, - }, - }, - }, - }, - }, - }} - services := []*v1.Service{ - { - ObjectMeta: v1.ObjectMeta{ - Name: "service1", - Namespace: "awesome", - UID: "1", - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.1", - Ports: []v1.ServicePort{ - { - Name: "http", - Port: 801, - }, - }, - }, - }, + ingresses := []*v1beta1.Ingress{ + buildIngress( + iNamespace("awesome"), + iRules(iRule( + iHost("foo"), + iPaths(onePath( + iPath("/bar"), + iBackend("service1", intstr.FromInt(801)))), + )), + ), } + + services := []*v1.Service{ + buildService( + sNamespace("awesome"), sName("service1"), sUID("1"), + sSpec(sPorts(sPort(801, "http"))), + ), + } + watchChan := make(chan interface{}) client := clientMock{ ingresses: ingresses, @@ -476,83 +259,43 @@ func TestGetPassHostHeader(t *testing.T) { watchChan: watchChan, } provider := Provider{DisablePassHostHeaders: true} - actual, err := provider.loadIngresses(client) - if err != nil { - t.Fatalf("error %+v", err) - } - expected := &types.Configuration{ - Backends: map[string]*types.Backend{ - "foo/bar": { - Servers: map[string]types.Server{}, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - }, - Frontends: map[string]*types.Frontend{ - "foo/bar": { - Backend: "foo/bar", - Routes: map[string]types.Route{ - "/bar": { - Rule: "PathPrefix:/bar", - }, - "foo": { - Rule: "Host:foo", - }, - }, - }, - }, - } + actual, err := provider.loadIngresses(client) + require.NoError(t, err, "error loading ingresses") + + expected := buildConfiguration( + backends(backend("foo/bar", lbMethod("wrr"), servers())), + frontends( + frontend("foo/bar", + routes( + route("/bar", "PathPrefix:/bar"), + route("foo", "Host:foo")), + ), + ), + ) assert.Equal(t, expected, actual) } func TestGetPassTLSCert(t *testing.T) { - ingresses := []*v1beta1.Ingress{{ - ObjectMeta: v1.ObjectMeta{ - Namespace: "awesome", - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "foo", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/bar", - Backend: v1beta1.IngressBackend{ - ServiceName: "service1", - ServicePort: intstr.FromInt(801), - }, - }, - }, - }, - }, - }, - }, - }, - }} - services := []*v1.Service{ - { - ObjectMeta: v1.ObjectMeta{ - Name: "service1", - Namespace: "awesome", - UID: "1", - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.1", - Ports: []v1.ServicePort{ - { - Name: "http", - Port: 801, - }, - }, - }, - }, + ingresses := []*v1beta1.Ingress{ + buildIngress(iNamespace("awesome"), + iRules(iRule( + iHost("foo"), + iPaths(onePath(iPath("/bar"), iBackend("service1", intstr.FromInt(80))))), + ), + ), } + + services := []*v1.Service{ + buildService( + sName("service1"), + sNamespace("awesome"), + sUID("1"), + sSpec(sPorts(sPort(801, "http"))), + ), + } + watchChan := make(chan interface{}) client := clientMock{ ingresses: ingresses, @@ -560,102 +303,53 @@ func TestGetPassTLSCert(t *testing.T) { watchChan: watchChan, } provider := Provider{EnablePassTLSCert: true} - actual, err := provider.loadIngresses(client) - if err != nil { - t.Fatalf("error %+v", err) - } - expected := &types.Configuration{ - Backends: map[string]*types.Backend{ - "foo/bar": { - Servers: map[string]types.Server{}, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - }, - Frontends: map[string]*types.Frontend{ - "foo/bar": { - Backend: "foo/bar", - PassTLSCert: true, - PassHostHeader: true, - Routes: map[string]types.Route{ - "/bar": { - Rule: "PathPrefix:/bar", - }, - "foo": { - Rule: "Host:foo", - }, - }, - }, - }, - } + actual, err := provider.loadIngresses(client) + require.NoError(t, err, "error loading ingresses") + + expected := buildConfiguration( + backends(backend("foo/bar", lbMethod("wrr"), servers())), + frontends(frontend("foo/bar", + passHostHeader(), + passTLSCert(), + routes( + route("/bar", "PathPrefix:/bar"), + route("foo", "Host:foo")), + )), + ) assert.Equal(t, expected, actual) } func TestOnlyReferencesServicesFromOwnNamespace(t *testing.T) { ingresses := []*v1beta1.Ingress{ - { - ObjectMeta: v1.ObjectMeta{ - Namespace: "awesome", - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "foo", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Backend: v1beta1.IngressBackend{ - ServiceName: "service", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - }, - }, - }, + buildIngress(iNamespace("awesome"), + iRules(iRule( + iHost("foo"), + iPaths(onePath(iBackend("service", intstr.FromInt(80))))), + ), + ), } + services := []*v1.Service{ - { - ObjectMeta: v1.ObjectMeta{ - Name: "service", - UID: "1", - Namespace: "awesome", - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.1", - Ports: []v1.ServicePort{ - { - Name: "http", - Port: 80, - }, - }, - }, - }, - { - ObjectMeta: v1.ObjectMeta{ - Name: "service", - UID: "2", - Namespace: "not-awesome", - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.2", - Ports: []v1.ServicePort{ - { - Name: "http", - Port: 80, - }, - }, - }, - }, + buildService( + sNamespace("awesome"), + sName("service"), + sUID("1"), + sSpec( + clusterIP("10.0.0.1"), + sPorts(sPort(80, "http"))), + ), + buildService( + sNamespace("not-awesome"), + sName("service"), + sUID("2"), + sSpec( + clusterIP("10.0.0.2"), + sPorts(sPort(80, "http"))), + ), } + watchChan := make(chan interface{}) client := clientMock{ ingresses: ingresses, @@ -663,80 +357,41 @@ func TestOnlyReferencesServicesFromOwnNamespace(t *testing.T) { watchChan: watchChan, } provider := Provider{} - actual, err := provider.loadIngresses(client) - if err != nil { - t.Fatalf("error %+v", err) - } - expected := &types.Configuration{ - Backends: map[string]*types.Backend{ - "foo": { - Servers: map[string]types.Server{}, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - }, - Frontends: map[string]*types.Frontend{ - "foo": { - Backend: "foo", - PassHostHeader: true, - Routes: map[string]types.Route{ - "foo": { - Rule: "Host:foo", - }, - }, - }, - }, - } + actual, err := provider.loadIngresses(client) + require.NoError(t, err, "error loading ingresses") + + expected := buildConfiguration( + backends(backend("foo", lbMethod("wrr"), servers())), + frontends(frontend("foo", + passHostHeader(), + routes(route("foo", "Host:foo")), + )), + ) assert.Equal(t, expected, actual) } func TestHostlessIngress(t *testing.T) { - ingresses := []*v1beta1.Ingress{{ - ObjectMeta: v1.ObjectMeta{ - Namespace: "awesome", - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/bar", - Backend: v1beta1.IngressBackend{ - ServiceName: "service1", - ServicePort: intstr.FromInt(801), - }, - }, - }, - }, - }, - }, - }, - }, - }} - services := []*v1.Service{ - { - ObjectMeta: v1.ObjectMeta{ - Name: "service1", - Namespace: "awesome", - UID: "1", - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.1", - Ports: []v1.ServicePort{ - { - Name: "http", - Port: 801, - }, - }, - }, - }, + ingresses := []*v1beta1.Ingress{ + buildIngress(iNamespace("awesome"), + iRules(iRule( + iPaths(onePath(iPath("/bar"), iBackend("service1", intstr.FromInt(801))))), + ), + ), } + + services := []*v1.Service{ + buildService( + sName("service1"), + sNamespace("awesome"), + sUID("1"), + sSpec( + clusterIP("10.0.0.1"), + sPorts(sPort(801, "http"))), + ), + } + watchChan := make(chan interface{}) client := clientMock{ ingresses: ingresses, @@ -744,187 +399,80 @@ func TestHostlessIngress(t *testing.T) { watchChan: watchChan, } provider := Provider{DisablePassHostHeaders: true} - actual, err := provider.loadIngresses(client) - if err != nil { - t.Fatalf("error %+v", err) - } - expected := &types.Configuration{ - Backends: map[string]*types.Backend{ - "/bar": { - Servers: map[string]types.Server{}, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - }, - Frontends: map[string]*types.Frontend{ - "/bar": { - Backend: "/bar", - Routes: map[string]types.Route{ - "/bar": { - Rule: "PathPrefix:/bar", - }, - }, - }, - }, - } + actual, err := provider.loadIngresses(client) + require.NoError(t, err, "error loading ingresses") + + expected := buildConfiguration( + backends(backend("/bar", lbMethod("wrr"), servers())), + frontends(frontend("/bar", routes(route("/bar", "PathPrefix:/bar")))), + ) assert.Equal(t, expected, actual) } func TestServiceAnnotations(t *testing.T) { - ingresses := []*v1beta1.Ingress{{ - ObjectMeta: v1.ObjectMeta{ - Namespace: "testing", - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "foo", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/bar", - Backend: v1beta1.IngressBackend{ - ServiceName: "service1", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - { - Host: "bar", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Backend: v1beta1.IngressBackend{ - ServiceName: "service2", - ServicePort: intstr.FromInt(802), - }, - }, - }, - }, - }, - }, - }, - }, - }} + ingresses := []*v1beta1.Ingress{ + buildIngress(iNamespace("testing"), + iRules( + iRule( + iHost("foo"), + iPaths(onePath(iPath("/bar"), iBackend("service1", intstr.FromInt(80))))), + iRule( + iHost("bar"), + iPaths(onePath(iBackend("service2", intstr.FromInt(802))))), + ), + ), + } + services := []*v1.Service{ - { - ObjectMeta: v1.ObjectMeta{ - Name: "service1", - UID: "1", - Namespace: "testing", - Annotations: map[string]string{ - label.TraefikBackendCircuitBreaker: "NetworkErrorRatio() > 0.5", - label.TraefikBackendLoadBalancerMethod: "drr", - }, - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.1", - Ports: []v1.ServicePort{ - { - Port: 80, - }, - }, - }, - }, - { - ObjectMeta: v1.ObjectMeta{ - Name: "service2", - UID: "2", - Namespace: "testing", - Annotations: map[string]string{ - label.TraefikBackendCircuitBreaker: "", - label.TraefikBackendLoadBalancerSticky: "true", - }, - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.2", - Ports: []v1.ServicePort{ - { - Port: 802, - }, - }, - }, - }, + buildService( + sName("service1"), + sNamespace("testing"), + sUID("1"), + sAnnotation(label.TraefikBackendCircuitBreaker, "NetworkErrorRatio() > 0.5"), + sAnnotation(label.TraefikBackendLoadBalancerMethod, "drr"), + sSpec( + clusterIP("10.0.0.1"), + sPorts(sPort(80, ""))), + ), + buildService( + sName("service2"), + sNamespace("testing"), + sUID("2"), + sAnnotation(label.TraefikBackendCircuitBreaker, ""), + sAnnotation(label.TraefikBackendLoadBalancerSticky, "true"), + sSpec( + clusterIP("10.0.0.2"), + sPorts(sPort(802, ""))), + ), } + endpoints := []*v1.Endpoints{ - { - ObjectMeta: v1.ObjectMeta{ - Name: "service1", - UID: "1", - Namespace: "testing", - }, - Subsets: []v1.EndpointSubset{ - { - Addresses: []v1.EndpointAddress{ - { - IP: "10.10.0.1", - }, - }, - Ports: []v1.EndpointPort{ - { - Port: 8080, - }, - }, - }, - { - Addresses: []v1.EndpointAddress{ - { - IP: "10.21.0.1", - }, - }, - Ports: []v1.EndpointPort{ - { - Port: 8080, - }, - }, - }, - }, - }, - { - ObjectMeta: v1.ObjectMeta{ - Name: "service2", - UID: "2", - Namespace: "testing", - }, - Subsets: []v1.EndpointSubset{ - { - Addresses: []v1.EndpointAddress{ - { - IP: "10.15.0.1", - }, - }, - Ports: []v1.EndpointPort{ - { - Name: "http", - Port: 8080, - }, - }, - }, - { - Addresses: []v1.EndpointAddress{ - { - IP: "10.15.0.2", - }, - }, - Ports: []v1.EndpointPort{ - { - Name: "http", - Port: 8080, - }, - }, - }, - }, - }, + buildEndpoint( + eNamespace("testing"), + eName("service1"), + eUID("1"), + subset( + eAddresses(eAddress("10.10.0.1")), + ePorts(ePort(8080, ""))), + subset( + eAddresses(eAddress("10.21.0.1")), + ePorts(ePort(8080, ""))), + ), + buildEndpoint( + eNamespace("testing"), + eName("service2"), + eUID("2"), + subset( + eAddresses(eAddress("10.15.0.1")), + ePorts(ePort(8080, "http"))), + subset( + eAddresses(eAddress("10.15.0.2")), + ePorts(ePort(8080, "http"))), + ), } + watchChan := make(chan interface{}) client := clientMock{ ingresses: ingresses, @@ -933,684 +481,328 @@ func TestServiceAnnotations(t *testing.T) { watchChan: watchChan, } provider := Provider{} - actual, err := provider.loadIngresses(client) - if err != nil { - t.Fatalf("error %+v", err) - } - expected := &types.Configuration{ - Backends: map[string]*types.Backend{ - "foo/bar": { - Servers: map[string]types.Server{ - "http://10.10.0.1:8080": { - URL: "http://10.10.0.1:8080", - Weight: 1, - }, - "http://10.21.0.1:8080": { - URL: "http://10.21.0.1:8080", - Weight: 1, - }, - }, - CircuitBreaker: &types.CircuitBreaker{ - Expression: "NetworkErrorRatio() > 0.5", - }, - LoadBalancer: &types.LoadBalancer{ - Method: "drr", - }, - }, - "bar": { - Servers: map[string]types.Server{ - "http://10.15.0.1:8080": { - URL: "http://10.15.0.1:8080", - Weight: 1, - }, - "http://10.15.0.2:8080": { - URL: "http://10.15.0.2:8080", - Weight: 1, - }, - }, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - Sticky: true, - }, - }, - }, - Frontends: map[string]*types.Frontend{ - "foo/bar": { - Backend: "foo/bar", - PassHostHeader: true, - Routes: map[string]types.Route{ - "/bar": { - Rule: "PathPrefix:/bar", - }, - "foo": { - Rule: "Host:foo", - }, - }, - }, - "bar": { - Backend: "bar", - PassHostHeader: true, - Routes: map[string]types.Route{ - "bar": { - Rule: "Host:bar", - }, - }, - }, - }, - } + actual, err := provider.loadIngresses(client) + require.NoError(t, err, "error loading ingresses") + + expected := buildConfiguration( + backends( + backend("foo/bar", + servers( + server("http://10.10.0.1:8080", weight(1)), + server("http://10.21.0.1:8080", weight(1))), + lbMethod("drr"), + circuitBreaker("NetworkErrorRatio() > 0.5"), + ), + backend("bar", + servers( + server("http://10.15.0.1:8080", weight(1)), + server("http://10.15.0.2:8080", weight(1))), + lbMethod("wrr"), lbSticky(), + ), + ), + frontends( + frontend("foo/bar", + passHostHeader(), + routes( + route("/bar", "PathPrefix:/bar"), + route("foo", "Host:foo")), + ), + frontend("bar", + passHostHeader(), + routes(route("bar", "Host:bar"))), + ), + ) assert.EqualValues(t, expected, actual) } func TestIngressAnnotations(t *testing.T) { ingresses := []*v1beta1.Ingress{ - { - ObjectMeta: v1.ObjectMeta{ - Namespace: "testing", - Annotations: map[string]string{ - label.TraefikFrontendPassHostHeader: "false", - }, - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "foo", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/bar", - Backend: v1beta1.IngressBackend{ - ServiceName: "service1", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - ObjectMeta: v1.ObjectMeta{ - Namespace: "testing", - Annotations: map[string]string{ - "kubernetes.io/ingress.class": "traefik", - label.TraefikFrontendPassHostHeader: "true", - }, - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "other", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/stuff", - Backend: v1beta1.IngressBackend{ - ServiceName: "service1", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - ObjectMeta: v1.ObjectMeta{ - Namespace: "testing", - Annotations: map[string]string{ - "kubernetes.io/ingress.class": "traefik", - label.TraefikFrontendPassTLSCert: "true", - }, - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "other", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/sslstuff", - Backend: v1beta1.IngressBackend{ - ServiceName: "service1", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - ObjectMeta: v1.ObjectMeta{ - Namespace: "testing", - Annotations: map[string]string{ - "kubernetes.io/ingress.class": "traefik", - label.TraefikFrontendEntryPoints: "http,https", - }, - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "other", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/", - Backend: v1beta1.IngressBackend{ - ServiceName: "service1", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - ObjectMeta: v1.ObjectMeta{ - Namespace: "testing", - Annotations: map[string]string{ - "ingress.kubernetes.io/auth-type": "basic", - "ingress.kubernetes.io/auth-secret": "mySecret", - }, - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "basic", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/auth", - Backend: v1beta1.IngressBackend{ - ServiceName: "service1", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - ObjectMeta: v1.ObjectMeta{ - Namespace: "testing", - Annotations: map[string]string{ - "kubernetes.io/ingress.class": "somethingOtherThanTraefik", - }, - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "herp", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/derp", - Backend: v1beta1.IngressBackend{ - ServiceName: "service2", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - ObjectMeta: v1.ObjectMeta{ - Namespace: "testing", - Annotations: map[string]string{ - "kubernetes.io/ingress.class": "traefik", - "ingress.kubernetes.io/whitelist-source-range": "1.1.1.1/24, 1234:abcd::42/32", - }, - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "test", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/whitelist-source-range", - Backend: v1beta1.IngressBackend{ - ServiceName: "service1", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - }, - }, - }, { - ObjectMeta: v1.ObjectMeta{ - Namespace: "testing", - Annotations: map[string]string{ - "ingress.kubernetes.io/rewrite-target": "/", - }, - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "rewrite", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/api", - Backend: v1beta1.IngressBackend{ - ServiceName: "service1", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - ObjectMeta: v1.ObjectMeta{ - Namespace: "testing", - Annotations: map[string]string{ - "ingress.kubernetes.io/auth-realm": "customized", - }, - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "auth-realm-customized", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/auth-realm-customized", - - Backend: v1beta1.IngressBackend{ - ServiceName: "service1", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - ObjectMeta: v1.ObjectMeta{ - Namespace: "testing", - Annotations: map[string]string{ - "kubernetes.io/ingress.class": "traefik", - label.TraefikFrontendRedirect: "https", - }, - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "redirect", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/https", - Backend: v1beta1.IngressBackend{ - ServiceName: "service1", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - }, - }, - }, + buildIngress( + iNamespace("testing"), + iAnnotation(label.TraefikFrontendPassHostHeader, "false"), + iRules( + iRule( + iHost("foo"), + iPaths(onePath(iPath("/bar"), iBackend("service1", intstr.FromInt(80))))), + ), + ), + buildIngress( + iNamespace("testing"), + iAnnotation(label.TraefikFrontendPassHostHeader, "true"), + iAnnotation(annotationKubernetesIngressClass, "traefik"), + iRules( + iRule( + iHost("other"), + iPaths(onePath(iPath("/stuff"), iBackend("service1", intstr.FromInt(80))))), + ), + ), + buildIngress( + iNamespace("testing"), + iAnnotation(label.TraefikFrontendPassTLSCert, "true"), + iAnnotation(annotationKubernetesIngressClass, "traefik"), + iRules( + iRule( + iHost("other"), + iPaths(onePath(iPath("/sslstuff"), iBackend("service1", intstr.FromInt(80))))), + ), + ), + buildIngress( + iNamespace("testing"), + iAnnotation(label.TraefikFrontendEntryPoints, "http,https"), + iAnnotation(annotationKubernetesIngressClass, "traefik"), + iRules( + iRule( + iHost("other"), + iPaths(onePath(iPath("/"), iBackend("service1", intstr.FromInt(80))))), + ), + ), + buildIngress( + iNamespace("testing"), + iAnnotation(annotationKubernetesAuthType, "basic"), + iAnnotation(annotationKubernetesAuthSecret, "mySecret"), + iRules( + iRule( + iHost("basic"), + iPaths(onePath(iPath("/auth"), iBackend("service1", intstr.FromInt(80))))), + ), + ), + buildIngress( + iNamespace("testing"), + iAnnotation(annotationKubernetesIngressClass, "somethingOtherThanTraefik"), + iRules( + iRule( + iHost("herp"), + iPaths(onePath(iPath("/derp"), iBackend("service2", intstr.FromInt(80))))), + ), + ), + buildIngress( + iNamespace("testing"), + iAnnotation(annotationKubernetesIngressClass, "traefik"), + iAnnotation(annotationKubernetesWhitelistSourceRange, "1.1.1.1/24, 1234:abcd::42/32"), + iRules( + iRule( + iHost("test"), + iPaths(onePath(iPath("/whitelist-source-range"), iBackend("service1", intstr.FromInt(80))))), + ), + ), + buildIngress( + iNamespace("testing"), + iAnnotation(annotationKubernetesRewriteTarget, "/"), + iRules( + iRule( + iHost("rewrite"), + iPaths(onePath(iPath("/api"), iBackend("service1", intstr.FromInt(80))))), + ), + ), + buildIngress( + iNamespace("testing"), + iAnnotation(annotationKubernetesAuthRealm, "customized"), + iRules( + iRule( + iHost("auth-realm-customized"), + iPaths(onePath(iPath("/auth-realm-customized"), iBackend("service1", intstr.FromInt(80))))), + ), + ), + buildIngress( + iNamespace("testing"), + iAnnotation(annotationKubernetesIngressClass, "traefik"), + iAnnotation(label.TraefikFrontendRedirect, "https"), + iRules( + iRule( + iHost("redirect"), + iPaths(onePath(iPath("/https"), iBackend("service1", intstr.FromInt(80))))), + ), + ), } + services := []*v1.Service{ - { - ObjectMeta: v1.ObjectMeta{ - Name: "service1", - UID: "1", - Namespace: "testing", - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.1", - Type: "ExternalName", - ExternalName: "example.com", - Ports: []v1.ServicePort{ - { - Name: "http", - Port: 80, - }, - }, - }, - }, - } - secrets := []*v1.Secret{ - { - ObjectMeta: v1.ObjectMeta{ - Name: "mySecret", - UID: "1", - Namespace: "testing", - }, - Data: map[string][]byte{ - "auth": []byte("myUser:myEncodedPW"), - }, - }, + buildService( + sName("service1"), + sNamespace("testing"), + sUID("1"), + sSpec( + clusterIP("10.0.0.1"), + sType("ExternalName"), + sExternalName("example.com"), + sPorts(sPort(80, "http"))), + ), + buildService( + sName("service2"), + sNamespace("testing"), + sUID("2"), + sSpec( + clusterIP("10.0.0.2"), + sPorts(sPort(802, ""))), + ), } - endpoints := []*v1.Endpoints{} + secrets := []*v1.Secret{{ + ObjectMeta: v1.ObjectMeta{ + Name: "mySecret", + UID: "1", + Namespace: "testing", + }, + Data: map[string][]byte{"auth": []byte("myUser:myEncodedPW")}, + }} + watchChan := make(chan interface{}) client := clientMock{ ingresses: ingresses, services: services, secrets: secrets, - endpoints: endpoints, watchChan: watchChan, } provider := Provider{} + actual, err := provider.loadIngresses(client) - if err != nil { - t.Fatalf("error %+v", err) - } + require.NoError(t, err, "error loading ingresses") - expected := &types.Configuration{ - Backends: map[string]*types.Backend{ - "foo/bar": { - Servers: map[string]types.Server{ - "http://example.com": { - URL: "http://example.com", - Weight: 1, - }, - }, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - "other/stuff": { - Servers: map[string]types.Server{ - "http://example.com": { - URL: "http://example.com", - Weight: 1, - }, - }, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - "other/": { - Servers: map[string]types.Server{ - "http://example.com": { - URL: "http://example.com", - Weight: 1, - }, - }, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - "other/sslstuff": { - Servers: map[string]types.Server{ - "http://example.com": { - URL: "http://example.com", - Weight: 1, - }, - }, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - "basic/auth": { - Servers: map[string]types.Server{ - "http://example.com": { - URL: "http://example.com", - Weight: 1, - }, - }, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - "redirect/https": { - Servers: map[string]types.Server{ - "http://example.com": { - URL: "http://example.com", - Weight: 1, - }, - }, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Sticky: false, - Method: "wrr", - }, - }, - "test/whitelist-source-range": { - Servers: map[string]types.Server{ - "http://example.com": { - URL: "http://example.com", - Weight: 1, - }, - }, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - "rewrite/api": { - Servers: map[string]types.Server{ - "http://example.com": { - URL: "http://example.com", - Weight: 1, - }, - }, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - }, - Frontends: map[string]*types.Frontend{ - "foo/bar": { - Backend: "foo/bar", - PassHostHeader: false, - Routes: map[string]types.Route{ - "/bar": { - Rule: "PathPrefix:/bar", - }, - "foo": { - Rule: "Host:foo", - }, - }, - Redirect: "", - }, - "other/stuff": { - Backend: "other/stuff", - PassHostHeader: true, - Routes: map[string]types.Route{ - "/stuff": { - Rule: "PathPrefix:/stuff", - }, - "other": { - Rule: "Host:other", - }, - }, - Redirect: "", - }, - "other/": { - Backend: "other/", - PassHostHeader: true, - EntryPoints: []string{"http", "https"}, - Routes: map[string]types.Route{ - "/": { - Rule: "PathPrefix:/", - }, - "other": { - Rule: "Host:other", - }, - }, - }, - "other/sslstuff": { - Backend: "other/sslstuff", - PassHostHeader: true, - PassTLSCert: true, - Routes: map[string]types.Route{ - "/sslstuff": { - Rule: "PathPrefix:/sslstuff", - }, - "other": { - Rule: "Host:other", - }, - }, - }, - "basic/auth": { - Backend: "basic/auth", - PassHostHeader: true, - Routes: map[string]types.Route{ - "/auth": { - Rule: "PathPrefix:/auth", - }, - "basic": { - Rule: "Host:basic", - }, - }, - BasicAuth: []string{"myUser:myEncodedPW"}, - Redirect: "", - }, - "redirect/https": { - Backend: "redirect/https", - PassHostHeader: true, - Routes: map[string]types.Route{ - "/https": { - Rule: "PathPrefix:/https", - }, - "redirect": { - Rule: "Host:redirect", - }, - }, - Redirect: "https", - }, - - "test/whitelist-source-range": { - Backend: "test/whitelist-source-range", - PassHostHeader: true, - WhitelistSourceRange: []string{ - "1.1.1.1/24", - "1234:abcd::42/32", - }, - Routes: map[string]types.Route{ - "/whitelist-source-range": { - Rule: "PathPrefix:/whitelist-source-range", - }, - "test": { - Rule: "Host:test", - }, - }, - Redirect: "", - }, - "rewrite/api": { - Backend: "rewrite/api", - PassHostHeader: true, - Routes: map[string]types.Route{ - "/api": { - Rule: "PathPrefix:/api;ReplacePath:/", - }, - "rewrite": { - Rule: "Host:rewrite", - }, - }, - Redirect: "", - }, - }, - } + expected := buildConfiguration( + backends( + backend("foo/bar", + servers( + server("http://example.com", weight(1)), + server("http://example.com", weight(1))), + lbMethod("wrr"), + ), + backend("other/stuff", + servers( + server("http://example.com", weight(1)), + server("http://example.com", weight(1))), + lbMethod("wrr"), + ), + backend("other/", + servers( + server("http://example.com", weight(1)), + server("http://example.com", weight(1))), + lbMethod("wrr"), + ), + backend("other/sslstuff", + servers( + server("http://example.com", weight(1)), + server("http://example.com", weight(1))), + lbMethod("wrr"), + ), + backend("basic/auth", + servers( + server("http://example.com", weight(1)), + server("http://example.com", weight(1))), + lbMethod("wrr"), + ), + backend("redirect/https", + servers( + server("http://example.com", weight(1)), + server("http://example.com", weight(1))), + lbMethod("wrr"), + ), + backend("test/whitelist-source-range", + servers( + server("http://example.com", weight(1)), + server("http://example.com", weight(1))), + lbMethod("wrr"), + ), + backend("rewrite/api", + servers( + server("http://example.com", weight(1)), + server("http://example.com", weight(1))), + lbMethod("wrr"), + ), + ), + frontends( + frontend("foo/bar", + routes( + route("/bar", "PathPrefix:/bar"), + route("foo", "Host:foo")), + ), + frontend("other/stuff", + passHostHeader(), + routes( + route("/stuff", "PathPrefix:/stuff"), + route("other", "Host:other")), + ), + frontend("other/", + passHostHeader(), + entryPoints("http", "https"), + routes( + route("/", "PathPrefix:/"), + route("other", "Host:other")), + ), + frontend("other/sslstuff", + passHostHeader(), + passTLSCert(), + routes( + route("/sslstuff", "PathPrefix:/sslstuff"), + route("other", "Host:other")), + ), + frontend("other/sslstuff", + passHostHeader(), + passTLSCert(), + routes( + route("/sslstuff", "PathPrefix:/sslstuff"), + route("other", "Host:other")), + ), + frontend("basic/auth", + passHostHeader(), + basicAuth("myUser:myEncodedPW"), + routes( + route("/auth", "PathPrefix:/auth"), + route("basic", "Host:basic")), + ), + frontend("redirect/https", + passHostHeader(), + redirect("https"), + routes( + route("/https", "PathPrefix:/https"), + route("redirect", "Host:redirect")), + ), + frontend("test/whitelist-source-range", + passHostHeader(), + whitelistSourceRange("1.1.1.1/24", "1234:abcd::42/32"), + routes( + route("/whitelist-source-range", "PathPrefix:/whitelist-source-range"), + route("test", "Host:test")), + ), + frontend("rewrite/api", + passHostHeader(), + routes( + route("/api", "PathPrefix:/api;ReplacePath:/"), + route("rewrite", "Host:rewrite")), + ), + ), + ) assert.Equal(t, expected, actual) } func TestPriorityHeaderValue(t *testing.T) { ingresses := []*v1beta1.Ingress{ - { - ObjectMeta: v1.ObjectMeta{ - Namespace: "testing", - Annotations: map[string]string{ - label.TraefikFrontendPriority: "1337", - }, - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "foo", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/bar", - Backend: v1beta1.IngressBackend{ - ServiceName: "service1", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - }, - }, - }, - } - services := []*v1.Service{ - { - ObjectMeta: v1.ObjectMeta{ - Name: "service1", - UID: "1", - Namespace: "testing", - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.1", - Type: "ExternalName", - ExternalName: "example.com", - Ports: []v1.ServicePort{ - { - Name: "http", - Port: 80, - }, - }, - }, - }, + buildIngress( + iNamespace("testing"), + iAnnotation(label.TraefikFrontendPriority, "1337"), + iRules( + iRule( + iHost("foo"), + iPaths(onePath(iPath("/bar"), iBackend("service1", intstr.FromInt(80))))), + ), + ), } - endpoints := []*v1.Endpoints{} + services := []*v1.Service{ + buildService( + sName("service1"), + sNamespace("testing"), + sUID("1"), + sSpec( + clusterIP("10.0.0.1"), + sType("ExternalName"), + sExternalName("example.com"), + sPorts(sPort(80, "http"))), + ), + } + + var endpoints []*v1.Endpoints watchChan := make(chan interface{}) client := clientMock{ ingresses: ingresses, @@ -1619,291 +811,168 @@ func TestPriorityHeaderValue(t *testing.T) { watchChan: watchChan, } provider := Provider{} - actual, err := provider.loadIngresses(client) - if err != nil { - t.Fatalf("error %+v", err) - } - expected := &types.Configuration{ - Backends: map[string]*types.Backend{ - "foo/bar": { - Servers: map[string]types.Server{ - "http://example.com": { - URL: "http://example.com", - Weight: 1, - }, - }, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - }, - Frontends: map[string]*types.Frontend{ - "foo/bar": { - Backend: "foo/bar", - PassHostHeader: true, - Priority: 1337, - Routes: map[string]types.Route{ - "/bar": { - Rule: "PathPrefix:/bar", - }, - "foo": { - Rule: "Host:foo", - }, - }, - }, - }, - } + actual, err := provider.loadIngresses(client) + require.NoError(t, err, "error loading ingresses") + + expected := buildConfiguration( + backends( + backend("foo/bar", + servers(server("http://example.com", weight(1))), + lbMethod("wrr"), + ), + ), + frontends( + frontend("foo/bar", + passHostHeader(), + priority(1337), + routes( + route("/bar", "PathPrefix:/bar"), + route("foo", "Host:foo")), + ), + ), + ) assert.Equal(t, expected, actual) } func TestInvalidPassTLSCertValue(t *testing.T) { ingresses := []*v1beta1.Ingress{ - { - ObjectMeta: v1.ObjectMeta{ - Namespace: "testing", - Annotations: map[string]string{ - label.TraefikFrontendPassTLSCert: "herpderp", - }, - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "foo", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/bar", - Backend: v1beta1.IngressBackend{ - ServiceName: "service1", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - }, - }, - }, - } - services := []*v1.Service{ - { - ObjectMeta: v1.ObjectMeta{ - Name: "service1", - UID: "1", - Namespace: "testing", - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.1", - Type: "ExternalName", - ExternalName: "example.com", - Ports: []v1.ServicePort{ - { - Name: "http", - Port: 80, - }, - }, - }, - }, + buildIngress( + iNamespace("testing"), + iAnnotation(label.TraefikFrontendPassTLSCert, "herpderp"), + iRules( + iRule( + iHost("foo"), + iPaths(onePath(iPath("/bar"), iBackend("service1", intstr.FromInt(80))))), + ), + ), + } + + services := []*v1.Service{ + buildService( + sName("service1"), + sNamespace("testing"), + sUID("1"), + sSpec( + clusterIP("10.0.0.1"), + sType("ExternalName"), + sExternalName("example.com"), + sPorts(sPort(80, "http"))), + ), } - endpoints := []*v1.Endpoints{} watchChan := make(chan interface{}) client := clientMock{ ingresses: ingresses, services: services, - endpoints: endpoints, watchChan: watchChan, } provider := Provider{} - actual, err := provider.loadIngresses(client) - if err != nil { - t.Fatalf("error %+v", err) - } - expected := &types.Configuration{ - Backends: map[string]*types.Backend{ - "foo/bar": { - Servers: map[string]types.Server{ - "http://example.com": { - URL: "http://example.com", - Weight: 1, - }, - }, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - }, - Frontends: map[string]*types.Frontend{ - "foo/bar": { - Backend: "foo/bar", - PassTLSCert: false, - PassHostHeader: true, - Routes: map[string]types.Route{ - "/bar": { - Rule: "PathPrefix:/bar", - }, - "foo": { - Rule: "Host:foo", - }, - }, - }, - }, - } + actual, err := provider.loadIngresses(client) + require.NoError(t, err, "error loading ingresses") + + expected := buildConfiguration( + backends( + backend("foo/bar", + servers(server("http://example.com", weight(1))), + lbMethod("wrr"), + ), + ), + frontends( + frontend("foo/bar", + passHostHeader(), + routes( + route("/bar", "PathPrefix:/bar"), + route("foo", "Host:foo")), + ), + ), + ) assert.Equal(t, expected, actual) } func TestInvalidPassHostHeaderValue(t *testing.T) { ingresses := []*v1beta1.Ingress{ - { - ObjectMeta: v1.ObjectMeta{ - Namespace: "testing", - Annotations: map[string]string{ - label.TraefikFrontendPassHostHeader: "herpderp", - }, - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "foo", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/bar", - Backend: v1beta1.IngressBackend{ - ServiceName: "service1", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - }, - }, - }, - } - services := []*v1.Service{ - { - ObjectMeta: v1.ObjectMeta{ - Name: "service1", - UID: "1", - Namespace: "testing", - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.1", - Type: "ExternalName", - ExternalName: "example.com", - Ports: []v1.ServicePort{ - { - Name: "http", - Port: 80, - }, - }, - }, - }, + buildIngress( + iNamespace("testing"), + iAnnotation(label.TraefikFrontendPassHostHeader, "herpderp"), + iRules( + iRule( + iHost("foo"), + iPaths(onePath(iPath("/bar"), iBackend("service1", intstr.FromInt(80))))), + ), + ), + } + + services := []*v1.Service{ + buildService( + sName("service1"), + sNamespace("testing"), + sUID("1"), + sSpec( + clusterIP("10.0.0.1"), + sType("ExternalName"), + sExternalName("example.com"), + sPorts(sPort(80, "http"))), + ), } - endpoints := []*v1.Endpoints{} watchChan := make(chan interface{}) client := clientMock{ ingresses: ingresses, services: services, - endpoints: endpoints, watchChan: watchChan, } provider := Provider{} - actual, err := provider.loadIngresses(client) - if err != nil { - t.Fatalf("error %+v", err) - } - expected := &types.Configuration{ - Backends: map[string]*types.Backend{ - "foo/bar": { - Servers: map[string]types.Server{ - "http://example.com": { - URL: "http://example.com", - Weight: 1, - }, - }, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - }, - Frontends: map[string]*types.Frontend{ - "foo/bar": { - Backend: "foo/bar", - PassHostHeader: true, - Routes: map[string]types.Route{ - "/bar": { - Rule: "PathPrefix:/bar", - }, - "foo": { - Rule: "Host:foo", - }, - }, - }, - }, - } + actual, err := provider.loadIngresses(client) + require.NoError(t, err, "error loading ingresses") + + expected := buildConfiguration( + backends( + backend("foo/bar", + servers(server("http://example.com", weight(1))), + lbMethod("wrr"), + ), + ), + frontends( + frontend("foo/bar", + passHostHeader(), + routes( + route("/bar", "PathPrefix:/bar"), + route("foo", "Host:foo")), + ), + ), + ) assert.Equal(t, expected, actual) } func TestKubeAPIErrors(t *testing.T) { - ingresses := []*v1beta1.Ingress{{ - ObjectMeta: v1.ObjectMeta{ - Namespace: "testing", - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "foo", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/bar", - Backend: v1beta1.IngressBackend{ - ServiceName: "service1", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - }, - }, - }} + ingresses := []*v1beta1.Ingress{ + buildIngress( + iNamespace("testing"), + iRules( + iRule( + iHost("foo"), + iPaths(onePath(iPath("/bar"), iBackend("service1", intstr.FromInt(80))))), + ), + ), + } - services := []*v1.Service{{ - ObjectMeta: v1.ObjectMeta{ - Name: "service1", - UID: "1", - Namespace: "testing", - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.1", - Ports: []v1.ServicePort{ - { - Port: 80, - }, - }, - }, - }} + services := []*v1.Service{ + buildService( + sName("service1"), + sNamespace("testing"), + sUID("1"), + sSpec( + clusterIP("10.0.0.1"), + sPorts(sPort(80, ""))), + ), + } - endpoints := []*v1.Endpoints{} watchChan := make(chan interface{}) apiErr := errors.New("failed kube api call") @@ -1930,13 +999,13 @@ func TestKubeAPIErrors(t *testing.T) { client := clientMock{ ingresses: ingresses, services: services, - endpoints: endpoints, watchChan: watchChan, apiServiceError: tc.apiServiceErr, apiEndpointsError: tc.apiEndpointsErr, } provider := Provider{} + if _, err := provider.loadIngresses(client); err != apiErr { t.Errorf("Got error %v, wanted error %v", err, apiErr) } @@ -1945,152 +1014,68 @@ func TestKubeAPIErrors(t *testing.T) { } func TestMissingResources(t *testing.T) { - ingresses := []*v1beta1.Ingress{{ - ObjectMeta: v1.ObjectMeta{ - Namespace: "testing", - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "fully_working", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Backend: v1beta1.IngressBackend{ - ServiceName: "fully_working_service", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - { - Host: "missing_service", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Backend: v1beta1.IngressBackend{ - ServiceName: "missing_service_service", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - { - Host: "missing_endpoints", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Backend: v1beta1.IngressBackend{ - ServiceName: "missing_endpoints_service", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - { - Host: "missing_endpoint_subsets", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Backend: v1beta1.IngressBackend{ - ServiceName: "missing_endpoint_subsets_service", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - }, - }, - }} - services := []*v1.Service{ - { - ObjectMeta: v1.ObjectMeta{ - Name: "fully_working_service", - UID: "1", - Namespace: "testing", - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.1", - Ports: []v1.ServicePort{ - { - Port: 80, - }, - }, - }, - }, - { - ObjectMeta: v1.ObjectMeta{ - Name: "missing_endpoints_service", - UID: "3", - Namespace: "testing", - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.3", - Ports: []v1.ServicePort{ - { - Port: 80, - }, - }, - }, - }, - { - ObjectMeta: v1.ObjectMeta{ - Name: "missing_endpoint_subsets_service", - UID: "4", - Namespace: "testing", - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.4", - Ports: []v1.ServicePort{ - { - Port: 80, - }, - }, - }, - }, + ingresses := []*v1beta1.Ingress{ + buildIngress( + iNamespace("testing"), + iRules( + iRule( + iHost("fully_working"), + iPaths(onePath(iBackend("fully_working_service", intstr.FromInt(80))))), + iRule( + iHost("missing_service"), + iPaths(onePath(iBackend("missing_service_service", intstr.FromInt(80))))), + iRule( + iHost("missing_endpoints"), + iPaths(onePath(iBackend("missing_endpoints_service", intstr.FromInt(80))))), + iRule( + iHost("missing_endpoint_subsets"), + iPaths(onePath(iBackend("missing_endpoint_subsets_service", intstr.FromInt(80))))), + ), + ), } + + services := []*v1.Service{ + buildService( + sName("fully_working_service"), + sNamespace("testing"), + sUID("1"), + sSpec( + clusterIP("10.0.0.1"), + sPorts(sPort(80, ""))), + ), + buildService( + sName("missing_endpoints_service"), + sNamespace("testing"), + sUID("3"), + sSpec( + clusterIP("10.0.0.3"), + sPorts(sPort(80, ""))), + ), + buildService( + sName("missing_endpoint_subsets_service"), + sNamespace("testing"), + sUID("4"), + sSpec( + clusterIP("10.0.0.4"), + sPorts(sPort(80, ""))), + ), + } + endpoints := []*v1.Endpoints{ - { - ObjectMeta: v1.ObjectMeta{ - Name: "fully_working_service", - UID: "1", - Namespace: "testing", - }, - Subsets: []v1.EndpointSubset{ - { - Addresses: []v1.EndpointAddress{ - { - IP: "10.10.0.1", - }, - }, - Ports: []v1.EndpointPort{ - { - Port: 8080, - }, - }, - }, - }, - }, - { - ObjectMeta: v1.ObjectMeta{ - Name: "missing_endpoint_subsets_service", - UID: "4", - Namespace: "testing", - }, - Subsets: []v1.EndpointSubset{}, - }, + buildEndpoint( + eName("fully_working_service"), + eUID("1"), + eNamespace("testing"), + subset( + eAddresses(eAddress("10.10.0.1")), + ePorts(ePort(8080, ""))), + ), + buildEndpoint( + eName("missing_endpoint_subsets_service"), + eUID("4"), + eNamespace("testing"), + subset(), + ), } watchChan := make(chan interface{}) @@ -2102,146 +1087,87 @@ func TestMissingResources(t *testing.T) { } provider := Provider{} - actual, err := provider.loadIngresses(client) - if err != nil { - t.Fatalf("error %+v", err) - } - expected := &types.Configuration{ - Backends: map[string]*types.Backend{ - "fully_working": { - Servers: map[string]types.Server{ - "http://10.10.0.1:8080": { - URL: "http://10.10.0.1:8080", - Weight: 1, - }, - }, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - "missing_service": { - Servers: map[string]types.Server{}, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - "missing_endpoints": { - Servers: map[string]types.Server{}, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - "missing_endpoint_subsets": { - Servers: map[string]types.Server{}, - CircuitBreaker: nil, - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, - }, - }, - Frontends: map[string]*types.Frontend{ - "fully_working": { - Backend: "fully_working", - PassHostHeader: true, - Routes: map[string]types.Route{ - "fully_working": { - Rule: "Host:fully_working", - }, - }, - }, - "missing_endpoints": { - Backend: "missing_endpoints", - PassHostHeader: true, - Routes: map[string]types.Route{ - "missing_endpoints": { - Rule: "Host:missing_endpoints", - }, - }, - }, - "missing_endpoint_subsets": { - Backend: "missing_endpoint_subsets", - PassHostHeader: true, - Routes: map[string]types.Route{ - "missing_endpoint_subsets": { - Rule: "Host:missing_endpoint_subsets", - }, - }, - }, - }, - } + actual, err := provider.loadIngresses(client) + require.NoError(t, err, "error loading ingresses") + + expected := buildConfiguration( + backends( + backend("fully_working", + servers(server("http://10.10.0.1:8080", weight(1))), + lbMethod("wrr"), + ), + backend("missing_service", + servers(), + lbMethod("wrr"), + ), + backend("missing_endpoints", + servers(), + lbMethod("wrr"), + ), + backend("missing_endpoint_subsets", + servers(), + lbMethod("wrr"), + ), + ), + frontends( + frontend("fully_working", + passHostHeader(), + routes(route("fully_working", "Host:fully_working")), + ), + frontend("missing_endpoints", + passHostHeader(), + routes(route("missing_endpoints", "Host:missing_endpoints")), + ), + frontend("missing_endpoint_subsets", + passHostHeader(), + routes(route("missing_endpoint_subsets", "Host:missing_endpoint_subsets")), + ), + ), + ) assert.Equal(t, expected, actual) } func TestBasicAuthInTemplate(t *testing.T) { ingresses := []*v1beta1.Ingress{ - { - ObjectMeta: v1.ObjectMeta{ - Namespace: "testing", - Annotations: map[string]string{ - "ingress.kubernetes.io/auth-type": "basic", - "ingress.kubernetes.io/auth-secret": "mySecret", - }, - }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ - { - Host: "basic", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ - { - Path: "/auth", - Backend: v1beta1.IngressBackend{ - ServiceName: "service1", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - }, - }, - }, - } - services := []*v1.Service{ - { - ObjectMeta: v1.ObjectMeta{ - Name: "service1", - UID: "1", - Namespace: "testing", - }, - Spec: v1.ServiceSpec{ - ClusterIP: "10.0.0.1", - Type: "ExternalName", - ExternalName: "example.com", - Ports: []v1.ServicePort{ - { - Name: "http", - Port: 80, - }, - }, - }, - }, - } - secrets := []*v1.Secret{ - { - ObjectMeta: v1.ObjectMeta{ - Name: "mySecret", - UID: "1", - Namespace: "testing", - }, - Data: map[string][]byte{ - "auth": []byte("myUser:myEncodedPW"), - }, - }, + buildIngress( + iNamespace("testing"), + iAnnotation(annotationKubernetesAuthType, "basic"), + iAnnotation(annotationKubernetesAuthSecret, "mySecret"), + iRules( + iRule( + iHost("basic"), + iPaths(onePath(iPath("/auth"), iBackend("service1", intstr.FromInt(80))))), + ), + ), } - endpoints := []*v1.Endpoints{} + services := []*v1.Service{ + buildService( + sName("service1"), + sNamespace("testing"), + sUID("1"), + sSpec( + clusterIP("10.0.0.1"), + sType("ExternalName"), + sExternalName("example.com"), + sPorts(sPort(80, "http"))), + ), + } + + secrets := []*v1.Secret{{ + ObjectMeta: v1.ObjectMeta{ + Name: "mySecret", + UID: "1", + Namespace: "testing", + }, + Data: map[string][]byte{ + "auth": []byte("myUser:myEncodedPW"), + }, + }} + + var endpoints []*v1.Endpoints watchChan := make(chan interface{}) client := clientMock{ ingresses: ingresses, @@ -2251,10 +1177,9 @@ func TestBasicAuthInTemplate(t *testing.T) { watchChan: watchChan, } provider := Provider{} + actual, err := provider.loadIngresses(client) - if err != nil { - t.Fatalf("error %+v", err) - } + require.NoError(t, err, "error loading ingresses") actual = provider.loadConfig(*actual) got := actual.Frontends["basic/auth"].BasicAuth