From b1ea36793b22e4e92dd77adb1658eb57f8e5d5d8 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 5 Jan 2018 17:31:24 +0100 Subject: [PATCH] refactor: simplify template and use typed function. --- autogen/gentemplates/gen.go | 153 ++++++------ provider/kv/kv_config.go | 197 ++++++++++++++- provider/kv/kv_config_test.go | 457 +++++++++++++++++++++++++++++++++- templates/kv.tmpl | 153 ++++++------ 4 files changed, 790 insertions(+), 170 deletions(-) diff --git a/autogen/gentemplates/gen.go b/autogen/gentemplates/gen.go index 7b5e00cd8..00e877b2e 100644 --- a/autogen/gentemplates/gen.go +++ b/autogen/gentemplates/gen.go @@ -890,91 +890,88 @@ func templatesKubernetesTmpl() (*asset, error) { } var _templatesKvTmpl = []byte(`[backends] -{{range $backend := List .Prefix "/backends/"}} - {{$backendName := Last $backend}} +{{range $backend := List .Prefix "/backends/" }} + {{ $backendName := Last $backend }} - {{$circuitBreaker := Get "" $backend "/circuitbreaker/expression"}} - {{with $circuitBreaker}} - [backends."{{$backendName}}".circuitBreaker] - expression = "{{$circuitBreaker}}" + {{ $circuitBreaker := getCircuitBreaker $backend }} + {{if $circuitBreaker }} + [backends."{{ $backendName }}".circuitBreaker] + expression = "{{ $circuitBreaker.Expression }}" {{end}} - {{$loadBalancer := Get "" $backend "/loadbalancer/method"}} - {{with $loadBalancer}} - [backends."{{$backendName}}".loadBalancer] - method = "{{$loadBalancer}}" - sticky = {{ getSticky $backend }} - {{if hasStickinessLabel $backend}} - [backends."{{$backendName}}".loadBalancer.stickiness] - cookieName = "{{getStickinessCookieName $backend}}" - {{end}} + {{ $loadBalancer := getLoadBalancer $backend }} + {{if $loadBalancer }} + [backends."{{ $backendName }}".loadBalancer] + method = "{{ $loadBalancer.Method }}" + sticky = {{ $loadBalancer.Sticky }} + {{if $loadBalancer.Stickiness }} + [backends."{{ $backendName }}".loadBalancer.stickiness] + cookieName = "{{ $loadBalancer.Stickiness.CookieName }}" + {{end}} {{end}} - {{$maxConnAmt := Get "" $backend "/maxconn/amount"}} - {{$maxConnExtractorFunc := Get "" $backend "/maxconn/extractorfunc"}} - {{with $maxConnAmt}} - {{with $maxConnExtractorFunc}} - [backends."{{$backendName}}".maxConn] - amount = {{$maxConnAmt}} - extractorFunc = "{{$maxConnExtractorFunc}}" - {{end}} + {{ $maxConn := getMaxConn $backend }} + {{if $maxConn }} + [backends."{{ $backendName }}".maxConn] + extractorFunc = "{{ $maxConn.ExtractorFunc }}" + amount = {{ $maxConn.Amount }} {{end}} - {{$healthCheck := Get "" $backend "/healthcheck/path"}} - {{with $healthCheck}} - [backends."{{$backendName}}".healthCheck] - path = "{{$healthCheck}}" - port = {{ Get "0" $backend "/healthcheck/port" }} - interval = "{{ Get "30s" $backend "/healthcheck/interval" }}" + {{ $healthCheck := getHealthCheck $backend }} + {{if $healthCheck }} + [backends.{{ $backendName }}.healthCheck] + path = "{{ $healthCheck.Path }}" + port = {{ $healthCheck.Port }} + interval = "{{ $healthCheck.Interval }}" {{end}} - {{range $server := ListServers $backend}} - [backends."{{$backendName}}".servers."{{Last $server}}"] - url = "{{Get "" $server "/url"}}" - weight = {{Get "0" $server "/weight"}} + {{range $serverName, $server := getServers $backend}} + [backends."{{ $backendName }}".servers."{{ $serverName }}"] + url = "{{ $server.URL }}" + weight = {{ $server.Weight }} {{end}} {{end}} [frontends] {{range $frontend := List .Prefix "/frontends/" }} - {{$frontendName := Last $frontend}} + {{ $frontendName := Last $frontend }} - [frontends."{{$frontendName}}"] - backend = "{{Get "" $frontend "/backend"}}" - priority = {{Get "0" $frontend "/priority"}} - passHostHeader = {{Get "true" $frontend "/passHostHeader"}} - passTLSCert = {{Get "false" $frontend "/passtlscert"}} + [frontends."{{ $frontendName }}"] + backend = "{{ getBackendName $frontend }}" + priority = {{ getPriority $frontend }} + passHostHeader = {{ getPassHostHeader $frontend }} + passTLSCert = {{ getPassTLSCert $frontend }} - {{$entryPoints := SplitGet $frontend "/entrypoints"}} - entryPoints = [{{range $entryPoints}} + entryPoints = [{{range getEntryPoints $frontend }} "{{.}}", {{end}}] - {{$whitelistSourceRange := SplitGet $frontend "/whitelistsourcerange"}} - whitelistSourceRange = [{{range $whitelistSourceRange}} + {{ $whitelistSourceRange := getWhitelistSourceRange $frontend }} + {{if $whitelistSourceRange }} + whitelistSourceRange = [{{range $whitelistSourceRange }} + "{{.}}", + {{end}}] + {{end}} + + basicAuth = [{{range getBasicAuth $frontend }} "{{.}}", {{end}}] - {{$basicAuth := SplitGet $frontend "/basicauth"}} - basicAuth = [{{range $basicAuth}} - "{{.}}", - {{end}}] - - {{$redirect := getRedirect $frontend }} - {{ if $redirect }} - [frontends."{{$frontendName}}".redirect] + {{ $redirect := getRedirect $frontend }} + {{if $redirect }} + [frontends."{{ $frontendName }}".redirect] entryPoint = "{{ $redirect.EntryPoint }}" regex = "{{ $redirect.Regex }}" replacement = "{{ $redirect.Replacement }}" {{end}} {{ $errorPages := getErrorPages $frontend }} - {{ if $errorPages }} - [frontends."{{$frontendName}}".errors] - {{ range $pageName, $page := $errorPages }} + {{if $errorPages }} + [frontends."{{ $frontendName }}".errors] + {{range $pageName, $page := $errorPages }} [frontends."{{$frontendName}}".errors.{{ $pageName }}] - status = [{{range $page.Status}} + status = [{{range $page.Status }} "{{.}}", {{end}}] backend = "{{$page.Backend}}" @@ -983,12 +980,12 @@ var _templatesKvTmpl = []byte(`[backends] {{end}} {{ $rateLimit := getRateLimit $frontend }} - {{ if $rateLimit }} - [frontends."{{$frontendName}}".rateLimit] + {{if $rateLimit }} + [frontends."{{ $frontendName }}".rateLimit] extractorFunc = "{{ $rateLimit.ExtractorFunc }}" - [frontends."{{$frontendName}}".rateLimit.rateSet] - {{ range $limitName, $rateLimit := $rateLimit.RateSet }} - [frontends."{{$frontendName}}".rateLimit.rateSet.{{ $limitName }}] + [frontends."{{ $frontendName }}".rateLimit.rateSet] + {{range $limitName, $rateLimit := $rateLimit.RateSet }} + [frontends."{{ $frontendName }}".rateLimit.rateSet.{{ $limitName }}] period = "{{ $rateLimit.Period }}" average = {{ $rateLimit.Average }} burst = {{ $rateLimit.Burst }} @@ -996,7 +993,7 @@ var _templatesKvTmpl = []byte(`[backends] {{end}} {{ $headers := getHeaders $frontend }} - {{ if $headers }} + {{if $headers }} [frontends."{{ $frontendName }}".headers] SSLRedirect = {{ $headers.SSLRedirect }} SSLTemporaryRedirect = {{ $headers.SSLTemporaryRedirect }} @@ -1014,33 +1011,33 @@ var _templatesKvTmpl = []byte(`[backends] ReferrerPolicy = "{{ $headers.ReferrerPolicy }}" IsDevelopment = {{ $headers.IsDevelopment }} - {{ if $headers.AllowedHosts }} - AllowedHosts = [{{ range $headers.AllowedHosts }} + {{if $headers.AllowedHosts }} + AllowedHosts = [{{range $headers.AllowedHosts }} "{{.}}", {{end}}] {{end}} - {{ if $headers.HostsProxyHeaders }} - HostsProxyHeaders = [{{ range $headers.HostsProxyHeaders }} + {{if $headers.HostsProxyHeaders }} + HostsProxyHeaders = [{{range $headers.HostsProxyHeaders }} "{{.}}", {{end}}] {{end}} - {{ if $headers.CustomRequestHeaders }} + {{if $headers.CustomRequestHeaders }} [frontends."{{ $frontendName }}".headers.customRequestHeaders] - {{ range $k, $v := $headers.CustomRequestHeaders }} + {{range $k, $v := $headers.CustomRequestHeaders }} {{$k}} = "{{$v}}" {{end}} {{end}} - {{ if $headers.CustomResponseHeaders }} + {{if $headers.CustomResponseHeaders }} [frontends."{{ $frontendName }}".headers.customResponseHeaders] - {{ range $k, $v := $headers.CustomResponseHeaders }} + {{range $k, $v := $headers.CustomResponseHeaders }} {{$k}} = "{{$v}}" {{end}} {{end}} - {{ if $headers.SSLProxyHeaders }} + {{if $headers.SSLProxyHeaders }} [frontends."{{ $frontendName }}".headers.SSLProxyHeaders] {{range $k, $v := $headers.SSLProxyHeaders}} {{$k}} = "{{$v}}" @@ -1048,25 +1045,23 @@ var _templatesKvTmpl = []byte(`[backends] {{end}} {{end}} - {{range $route := List $frontend "/routes/"}} - [frontends."{{$frontendName}}".routes."{{Last $route}}"] - rule = "{{Get "" $route "/rule"}}" + {{range $routeName, $route := getRoutes $frontend }} + [frontends."{{ $frontendName }}".routes."{{ $routeName }}"] + rule = "{{ $route.Rule }}" {{end}} {{end}} -{{range $tlsConfiguration := List .Prefix "/tlsconfiguration/"}} - +{{range $tlsConfiguration := getTLSConfigurations .Prefix }} [[tlsConfiguration]] - {{$entryPoints := SplitGet $tlsConfiguration "/entrypoints"}} - entryPoints = [{{range $entryPoints}} + entryPoints = [{{range $tlsConfiguration.EntryPoints }} "{{.}}", {{end}}] [tlsConfiguration.certificate] - certFile = """{{Get "" $tlsConfiguration "/certificate/certfile"}}""" - keyFile = """{{Get "" $tlsConfiguration "/certificate/keyfile"}}""" + certFile = """{{ $tlsConfiguration.Certificate.CertFile }}""" + keyFile = """{{ $tlsConfiguration.Certificate.KeyFile }}""" {{end}} `) diff --git a/provider/kv/kv_config.go b/provider/kv/kv_config.go index a45885c59..3aa3ee811 100644 --- a/provider/kv/kv_config.go +++ b/provider/kv/kv_config.go @@ -2,6 +2,7 @@ package kv import ( "fmt" + "math" "net/http" "sort" "strconv" @@ -12,6 +13,7 @@ import ( "github.com/containous/flaeg" "github.com/containous/traefik/log" "github.com/containous/traefik/provider/label" + "github.com/containous/traefik/tls" "github.com/containous/traefik/types" "github.com/docker/libkv/store" ) @@ -35,16 +37,31 @@ func (p *Provider) buildConfiguration() *types.Configuration { "Last": p.last, "Has": p.has, + "getTLSConfigurations": p.getTLSConfigurations, + // Frontend functions - "getRedirect": p.getRedirect, - "getErrorPages": p.getErrorPages, - "getRateLimit": p.getRateLimit, - "getHeaders": p.getHeaders, + "getBackendName": p.getFuncString(pathFrontendBackend, ""), + "getPriority": p.getFuncInt(pathFrontendPriority, 0), + "getPassHostHeader": p.getFuncBool(pathFrontendPassHostHeader, true), + "getPassTLSCert": p.getFuncBool(pathFrontendPassTLSCert, label.DefaultPassTLSCert), + "getEntryPoints": p.getFuncSlice(pathFrontendEntryPoints), + "getWhitelistSourceRange": p.getFuncSlice(pathFrontendWhiteListSourceRange), + "getBasicAuth": p.getFuncSlice(pathFrontendBasicAuth), + "getRoutes": p.getRoutes, + "getRedirect": p.getRedirect, + "getErrorPages": p.getErrorPages, + "getRateLimit": p.getRateLimit, + "getHeaders": p.getHeaders, // Backend functions - "getSticky": p.getSticky, - "hasStickinessLabel": p.hasStickinessLabel, - "getStickinessCookieName": p.getStickinessCookieName, + "getServers": p.getServers, + "getCircuitBreaker": p.getCircuitBreaker, + "getLoadBalancer": p.getLoadBalancer, + "getMaxConn": p.getMaxConn, + "getHealthCheck": p.getHealthCheck, + "getSticky": p.getSticky, // Deprecated [breaking] + "hasStickinessLabel": p.hasStickinessLabel, // Deprecated [breaking] + "getStickinessCookieName": p.getStickinessCookieName, // Deprecated [breaking] } configuration, err := p.GetConfiguration("templates/kv.tmpl", KvFuncMap, templateObjects) @@ -61,6 +78,7 @@ func (p *Provider) buildConfiguration() *types.Configuration { return configuration } +// Deprecated func (p *Provider) getSticky(rootPath string) bool { stickyValue := p.get("", rootPath, pathBackendLoadBalancerSticky) if len(stickyValue) > 0 { @@ -77,10 +95,12 @@ func (p *Provider) getSticky(rootPath string) bool { return sticky } +// Deprecated func (p *Provider) hasStickinessLabel(rootPath string) bool { return p.getBool(false, rootPath, pathBackendLoadBalancerStickiness) } +// Deprecated func (p *Provider) getStickinessCookieName(rootPath string) string { return p.get("", rootPath, pathBackendLoadBalancerStickinessCookieName) } @@ -193,6 +213,145 @@ func (p *Provider) getHeaders(rootPath string) *types.Headers { return headers } +func (p *Provider) getLoadBalancer(rootPath string) *types.LoadBalancer { + lb := &types.LoadBalancer{ + Method: p.get(label.DefaultBackendLoadBalancerMethod, rootPath, pathBackendLoadBalancerMethod), + Sticky: p.getSticky(rootPath), + } + + if p.getBool(false, rootPath, pathBackendLoadBalancerStickiness) { + lb.Stickiness = &types.Stickiness{ + CookieName: p.get("", rootPath, pathBackendLoadBalancerStickinessCookieName), + } + } + + return lb +} + +func (p *Provider) getCircuitBreaker(rootPath string) *types.CircuitBreaker { + if !p.has(rootPath, pathBackendCircuitBreakerExpression) { + return nil + } + + circuitBreaker := p.get(label.DefaultCircuitBreakerExpression, rootPath, pathBackendCircuitBreakerExpression) + if len(circuitBreaker) == 0 { + return nil + } + + return &types.CircuitBreaker{Expression: circuitBreaker} +} + +func (p *Provider) getMaxConn(rootPath string) *types.MaxConn { + amount := p.getInt64(math.MinInt64, rootPath, pathBackendMaxConnAmount) + extractorFunc := p.get(label.DefaultBackendMaxconnExtractorFunc, rootPath, pathBackendMaxConnExtractorFunc) + + if amount == math.MinInt64 || len(extractorFunc) == 0 { + return nil + } + + return &types.MaxConn{ + Amount: amount, + ExtractorFunc: extractorFunc, + } +} + +func (p *Provider) getHealthCheck(rootPath string) *types.HealthCheck { + path := p.get("", rootPath, pathBackendHealthCheckPath) + + if len(path) == 0 { + return nil + } + + port := p.getInt(label.DefaultBackendHealthCheckPort, rootPath, pathBackendHealthCheckPort) + interval := p.get("30s", rootPath, pathBackendHealthCheckInterval) + + return &types.HealthCheck{ + Path: path, + Port: port, + Interval: interval, + } +} + +func (p *Provider) getTLSConfigurations(prefix string) []*tls.Configuration { + var tlsConfiguration []*tls.Configuration + + for _, tlsConfPath := range p.list(prefix, pathTLSConfiguration) { + certFile := p.get("", tlsConfPath, pathTLSConfigurationCertFile) + keyFile := p.get("", tlsConfPath, pathTLSConfigurationKeyFile) + + if len(certFile) == 0 && len(keyFile) == 0 { + log.Warnf("Invalid TLS configuration (no cert and no key): %s", tlsConfPath) + continue + } + + entryPoints := p.splitGet(tlsConfPath, pathTLSConfigurationEntryPoints) + if len(entryPoints) == 0 { + log.Warnf("Invalid TLS configuration (no entry points): %s", tlsConfPath) + continue + } + + tlsConf := &tls.Configuration{ + EntryPoints: entryPoints, + Certificate: &tls.Certificate{ + CertFile: tls.FileOrContent(certFile), + KeyFile: tls.FileOrContent(keyFile), + }, + } + + tlsConfiguration = append(tlsConfiguration, tlsConf) + } + + return tlsConfiguration +} + +func (p *Provider) getRoutes(rootPath string) map[string]types.Route { + var routes map[string]types.Route + + rts := p.list(rootPath, pathFrontendRoutes) + for _, rt := range rts { + + rule := p.get("", rt, pathFrontendRule) + if len(rule) == 0 { + continue + } + + if routes == nil { + routes = make(map[string]types.Route) + } + + routeName := p.last(rt) + routes[routeName] = types.Route{ + Rule: rule, + } + } + + return routes +} + +func (p *Provider) getServers(rootPath string) map[string]types.Server { + var servers map[string]types.Server + + serverKeys := p.listServers(rootPath) + for _, serverKey := range serverKeys { + serverURL := p.get("", serverKey, pathBackendServerURL) + if len(serverURL) == 0 { + continue + } + + if servers == nil { + servers = make(map[string]types.Server) + } + + serverName := p.last(serverKey) + servers[serverName] = types.Server{ + URL: serverURL, + Weight: p.getInt(0, serverKey, pathBackendServerWeight), + } + } + + return servers +} + func (p *Provider) listServers(backend string) []string { serverNames := p.list(backend, pathBackendServers) return fun.Filter(p.serverFilter, serverNames).([]string) @@ -229,6 +388,30 @@ func (p *Provider) checkConstraints(keys ...string) bool { return true } +func (p *Provider) getFuncString(key string, defaultValue string) func(rootPath string) string { + return func(rootPath string) string { + return p.get(defaultValue, rootPath, key) + } +} + +func (p *Provider) getFuncBool(key string, defaultValue bool) func(rootPath string) bool { + return func(rootPath string) bool { + return p.getBool(defaultValue, rootPath, key) + } +} + +func (p *Provider) getFuncInt(key string, defaultValue int) func(rootPath string) int { + return func(rootPath string) int { + return p.getInt(defaultValue, rootPath, key) + } +} + +func (p *Provider) getFuncSlice(key string) func(rootPath string) []string { + return func(rootPath string) []string { + return p.splitGet(rootPath, key) + } +} + func (p *Provider) get(defaultValue string, keyParts ...string) string { key := strings.Join(keyParts, "") diff --git a/provider/kv/kv_config_test.go b/provider/kv/kv_config_test.go index 75dc922d4..a6ca992aa 100644 --- a/provider/kv/kv_config_test.go +++ b/provider/kv/kv_config_test.go @@ -38,6 +38,7 @@ func TestProviderBuildConfiguration(t *testing.T) { expected: &types.Configuration{ Backends: map[string]*types.Backend{ "backend.with.dot.too": { + LoadBalancer: &types.LoadBalancer{Method: label.DefaultBackendLoadBalancerMethod}, Servers: map[string]types.Server{ "server.with.dot": { URL: "http://172.17.0.2:80", @@ -48,11 +49,10 @@ func TestProviderBuildConfiguration(t *testing.T) { }, Frontends: map[string]*types.Frontend{ "frontend.with.dot": { - Backend: "backend.with.dot.too", - PassHostHeader: true, - EntryPoints: []string{}, - WhitelistSourceRange: []string{}, - BasicAuth: []string{}, + Backend: "backend.with.dot.too", + PassHostHeader: true, + EntryPoints: []string{}, + BasicAuth: []string{}, Routes: map[string]types.Route{ "route.with.dot": { Rule: "Host:test.localhost", @@ -1292,3 +1292,450 @@ func TestProviderGetHeaders(t *testing.T) { }) } } + +func TestProviderGetLoadBalancer(t *testing.T) { + testCases := []struct { + desc string + rootPath string + kvPairs []*store.KVPair + expected *types.LoadBalancer + }{ + { + desc: "when all keys", + rootPath: "traefik/backends/foo", + kvPairs: filler("traefik", + backend("foo", + withPair(pathBackendLoadBalancerMethod, "drr"), + withPair(pathBackendLoadBalancerSticky, "true"), + withPair(pathBackendLoadBalancerStickiness, "true"), + withPair(pathBackendLoadBalancerStickinessCookieName, "aubergine"))), + expected: &types.LoadBalancer{ + Method: "drr", + Sticky: true, + Stickiness: &types.Stickiness{ + CookieName: "aubergine", + }, + }, + }, + { + desc: "when no specific configuration", + rootPath: "traefik/backends/foo", + kvPairs: filler("traefik", backend("foo")), + expected: &types.LoadBalancer{ + Method: "wrr", + }, + }, + { + desc: "when method is set", + rootPath: "traefik/backends/foo", + kvPairs: filler("traefik", + backend("foo", + withPair(pathBackendLoadBalancerMethod, "drr"))), + expected: &types.LoadBalancer{ + Method: "drr", + }, + }, + { + desc: "when sticky is set", + rootPath: "traefik/backends/foo", + kvPairs: filler("traefik", + backend("foo", + withPair(pathBackendLoadBalancerSticky, "true"))), + expected: &types.LoadBalancer{ + Method: "wrr", + Sticky: true, + }, + }, + { + desc: "when stickiness is set", + rootPath: "traefik/backends/foo", + kvPairs: filler("traefik", + backend("foo", + withPair(pathBackendLoadBalancerStickiness, "true"))), + expected: &types.LoadBalancer{ + Method: "wrr", + Stickiness: &types.Stickiness{}, + }, + }, + { + desc: "when stickiness cookie name is set", + rootPath: "traefik/backends/foo", + kvPairs: filler("traefik", + backend("foo", + withPair(pathBackendLoadBalancerStickiness, "true"), + withPair(pathBackendLoadBalancerStickinessCookieName, "aubergine"))), + expected: &types.LoadBalancer{ + Method: "wrr", + Stickiness: &types.Stickiness{ + CookieName: "aubergine", + }, + }, + }, + { + desc: "when stickiness cookie name is set but not stickiness", + rootPath: "traefik/backends/foo", + kvPairs: filler("traefik", + backend("foo", + withPair(pathBackendLoadBalancerStickinessCookieName, "aubergine"))), + expected: &types.LoadBalancer{ + Method: "wrr", + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + p := newProviderMock(test.kvPairs) + + result := p.getLoadBalancer(test.rootPath) + + assert.Equal(t, test.expected, result) + }) + } +} + +func TestProviderGetCircuitBreaker(t *testing.T) { + testCases := []struct { + desc string + rootPath string + kvPairs []*store.KVPair + expected *types.CircuitBreaker + }{ + { + desc: "when cb expression defined", + rootPath: "traefik/backends/foo", + kvPairs: filler("traefik", + backend("foo", + withPair(pathBackendCircuitBreakerExpression, label.DefaultCircuitBreakerExpression))), + expected: &types.CircuitBreaker{ + Expression: label.DefaultCircuitBreakerExpression, + }, + }, + { + desc: "when no cb expression", + rootPath: "traefik/backends/foo", + kvPairs: filler("traefik", backend("foo")), + expected: nil, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + p := newProviderMock(test.kvPairs) + + result := p.getCircuitBreaker(test.rootPath) + + assert.Equal(t, test.expected, result) + }) + } +} + +func TestProviderGetMaxConn(t *testing.T) { + testCases := []struct { + desc string + rootPath string + kvPairs []*store.KVPair + expected *types.MaxConn + }{ + { + desc: "when max conn keys are defined", + rootPath: "traefik/backends/foo", + kvPairs: filler("traefik", + backend("foo", + withPair(pathBackendMaxConnAmount, "5"), + withPair(pathBackendMaxConnExtractorFunc, "client.ip"))), + expected: &types.MaxConn{ + Amount: 5, + ExtractorFunc: "client.ip", + }, + }, + { + desc: "should return nil when only extractor func is defined", + rootPath: "traefik/backends/foo", + kvPairs: filler("traefik", + backend("foo", + withPair(pathBackendMaxConnExtractorFunc, "client.ip"))), + expected: nil, + }, + { + desc: "when only amount is defined", + rootPath: "traefik/backends/foo", + kvPairs: filler("traefik", + backend("foo", + withPair(pathBackendMaxConnAmount, "5"))), + expected: &types.MaxConn{ + Amount: 5, + ExtractorFunc: "request.host", + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + p := newProviderMock(test.kvPairs) + + result := p.getMaxConn(test.rootPath) + + assert.Equal(t, test.expected, result) + }) + } +} + +func TestProviderGetHealthCheck(t *testing.T) { + testCases := []struct { + desc string + rootPath string + kvPairs []*store.KVPair + expected *types.HealthCheck + }{ + { + desc: "when all configuration keys defined", + rootPath: "traefik/backends/foo", + kvPairs: filler("traefik", + backend("foo", + withPair(pathBackendHealthCheckPath, "/health"), + withPair(pathBackendHealthCheckPort, "80"), + withPair(pathBackendHealthCheckInterval, "10s"))), + expected: &types.HealthCheck{ + Interval: "10s", + Path: "/health", + Port: 80, + }, + }, + { + desc: "when only path defined", + rootPath: "traefik/backends/foo", + kvPairs: filler("traefik", + backend("foo", + withPair(pathBackendHealthCheckPath, "/health"))), + expected: &types.HealthCheck{ + Interval: "30s", + Path: "/health", + Port: 0, + }, + }, + { + desc: "should return nil when no path", + rootPath: "traefik/backends/foo", + kvPairs: filler("traefik", + backend("foo", + withPair(pathBackendHealthCheckPort, "80"), + withPair(pathBackendHealthCheckInterval, "30s"))), + expected: nil, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + p := newProviderMock(test.kvPairs) + + result := p.getHealthCheck(test.rootPath) + + assert.Equal(t, test.expected, result) + }) + } +} + +func TestProviderGetTLSConfigurations(t *testing.T) { + testCases := []struct { + desc string + kvPairs []*store.KVPair + expected []*tls.Configuration + }{ + { + desc: "when several TLS configuration defined", + kvPairs: filler("traefik", + entry("tlsconfiguration/foo", + withPair("entrypoints", "http,https"), + withPair("certificate/certfile", "certfile1"), + withPair("certificate/keyfile", "keyfile1")), + entry("tlsconfiguration/bar", + withPair("entrypoints", "http,https"), + withPair("certificate/certfile", "certfile2"), + withPair("certificate/keyfile", "keyfile2"))), + expected: []*tls.Configuration{ + { + EntryPoints: []string{"http", "https"}, + Certificate: &tls.Certificate{ + CertFile: "certfile2", + KeyFile: "keyfile2", + }, + }, + { + EntryPoints: []string{"http", "https"}, + Certificate: &tls.Certificate{ + CertFile: "certfile1", + KeyFile: "keyfile1", + }, + }, + }, + }, + { + desc: "should return nil when no TLS configuration", + kvPairs: filler("traefik", entry("tlsconfiguration/foo")), + expected: nil, + }, + { + desc: "should return nil when no entry points", + kvPairs: filler("traefik", + entry("tlsconfiguration/foo", + withPair("certificate/certfile", "certfile2"), + withPair("certificate/keyfile", "keyfile2"))), + expected: nil, + }, + { + desc: "should return nil when no cert file and no key file", + kvPairs: filler("traefik", + entry("tlsconfiguration/foo", + withPair("entrypoints", "http,https"))), + expected: nil, + }, + } + prefix := "traefik" + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + p := newProviderMock(test.kvPairs) + + result := p.getTLSConfigurations(prefix) + + assert.Equal(t, test.expected, result) + }) + } +} + +func TestProviderGetRoutes(t *testing.T) { + testCases := []struct { + desc string + rootPath string + kvPairs []*store.KVPair + expected map[string]types.Route + }{ + { + desc: "should return nil when no data", + expected: nil, + }, + { + desc: "should return nil when route key exists but without rule key", + rootPath: "traefik/frontends/foo", + kvPairs: filler("traefik", + frontend("foo", + withPair(pathFrontendRoutes+"bar", "test1"), + withPair(pathFrontendRoutes+"bir", "test2"))), + expected: nil, + }, + { + desc: "should return a map when configuration keys are defined", + rootPath: "traefik/frontends/foo", + kvPairs: filler("traefik", + frontend("foo", + withPair(pathFrontendRoutes+"bar"+pathFrontendRule, "test1"), + withPair(pathFrontendRoutes+"bir"+pathFrontendRule, "test2"))), + expected: map[string]types.Route{ + "bar": { + Rule: "test1", + }, + "bir": { + Rule: "test2", + }, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + p := newProviderMock(test.kvPairs) + + result := p.getRoutes(test.rootPath) + + assert.Equal(t, test.expected, result) + }) + } +} + +func TestProviderGetServers(t *testing.T) { + testCases := []struct { + desc string + rootPath string + kvPairs []*store.KVPair + expected map[string]types.Server + }{ + { + desc: "should return nil when no data", + expected: nil, + }, + { + desc: "should return nil when server has no URL", + rootPath: "traefik/backends/foo", + kvPairs: filler("traefik", + backend("foo", + withPair(pathBackendServers+"server1/weight", "7"), + withPair(pathBackendServers+"server2/weight", "6"))), + expected: nil, + }, + { + desc: "should use default weight when invalid weight value", + rootPath: "traefik/backends/foo", + kvPairs: filler("traefik", + backend("foo", + withPair(pathBackendServers+"server1/url", "http://172.17.0.2:80"), + withPair(pathBackendServers+"server1/weight", "kls"))), + expected: map[string]types.Server{ + "server1": { + URL: "http://172.17.0.2:80", + Weight: 0, + }, + }, + }, + { + desc: "should return a map when configuration keys are defined", + rootPath: "traefik/backends/foo", + kvPairs: filler("traefik", + backend("foo", + withPair(pathBackendServers+"server1/url", "http://172.17.0.2:80"), + withPair(pathBackendServers+"server2/url", "http://172.17.0.3:80"), + withPair(pathBackendServers+"server2/weight", "6"))), + expected: map[string]types.Server{ + "server1": { + URL: "http://172.17.0.2:80", + Weight: 0, + }, + "server2": { + URL: "http://172.17.0.3:80", + Weight: 6, + }, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + p := newProviderMock(test.kvPairs) + + result := p.getServers(test.rootPath) + + assert.Equal(t, test.expected, result) + }) + } +} diff --git a/templates/kv.tmpl b/templates/kv.tmpl index dbc38f6d4..efa9c359d 100644 --- a/templates/kv.tmpl +++ b/templates/kv.tmpl @@ -1,89 +1,86 @@ [backends] -{{range $backend := List .Prefix "/backends/"}} - {{$backendName := Last $backend}} +{{range $backend := List .Prefix "/backends/" }} + {{ $backendName := Last $backend }} - {{$circuitBreaker := Get "" $backend "/circuitbreaker/expression"}} - {{with $circuitBreaker}} - [backends."{{$backendName}}".circuitBreaker] - expression = "{{$circuitBreaker}}" + {{ $circuitBreaker := getCircuitBreaker $backend }} + {{if $circuitBreaker }} + [backends."{{ $backendName }}".circuitBreaker] + expression = "{{ $circuitBreaker.Expression }}" {{end}} - {{$loadBalancer := Get "" $backend "/loadbalancer/method"}} - {{with $loadBalancer}} - [backends."{{$backendName}}".loadBalancer] - method = "{{$loadBalancer}}" - sticky = {{ getSticky $backend }} - {{if hasStickinessLabel $backend}} - [backends."{{$backendName}}".loadBalancer.stickiness] - cookieName = "{{getStickinessCookieName $backend}}" - {{end}} + {{ $loadBalancer := getLoadBalancer $backend }} + {{if $loadBalancer }} + [backends."{{ $backendName }}".loadBalancer] + method = "{{ $loadBalancer.Method }}" + sticky = {{ $loadBalancer.Sticky }} + {{if $loadBalancer.Stickiness }} + [backends."{{ $backendName }}".loadBalancer.stickiness] + cookieName = "{{ $loadBalancer.Stickiness.CookieName }}" + {{end}} {{end}} - {{$maxConnAmt := Get "" $backend "/maxconn/amount"}} - {{$maxConnExtractorFunc := Get "" $backend "/maxconn/extractorfunc"}} - {{with $maxConnAmt}} - {{with $maxConnExtractorFunc}} - [backends."{{$backendName}}".maxConn] - amount = {{$maxConnAmt}} - extractorFunc = "{{$maxConnExtractorFunc}}" - {{end}} + {{ $maxConn := getMaxConn $backend }} + {{if $maxConn }} + [backends."{{ $backendName }}".maxConn] + extractorFunc = "{{ $maxConn.ExtractorFunc }}" + amount = {{ $maxConn.Amount }} {{end}} - {{$healthCheck := Get "" $backend "/healthcheck/path"}} - {{with $healthCheck}} - [backends."{{$backendName}}".healthCheck] - path = "{{$healthCheck}}" - port = {{ Get "0" $backend "/healthcheck/port" }} - interval = "{{ Get "30s" $backend "/healthcheck/interval" }}" + {{ $healthCheck := getHealthCheck $backend }} + {{if $healthCheck }} + [backends.{{ $backendName }}.healthCheck] + path = "{{ $healthCheck.Path }}" + port = {{ $healthCheck.Port }} + interval = "{{ $healthCheck.Interval }}" {{end}} - {{range $server := ListServers $backend}} - [backends."{{$backendName}}".servers."{{Last $server}}"] - url = "{{Get "" $server "/url"}}" - weight = {{Get "0" $server "/weight"}} + {{range $serverName, $server := getServers $backend}} + [backends."{{ $backendName }}".servers."{{ $serverName }}"] + url = "{{ $server.URL }}" + weight = {{ $server.Weight }} {{end}} {{end}} [frontends] {{range $frontend := List .Prefix "/frontends/" }} - {{$frontendName := Last $frontend}} + {{ $frontendName := Last $frontend }} - [frontends."{{$frontendName}}"] - backend = "{{Get "" $frontend "/backend"}}" - priority = {{Get "0" $frontend "/priority"}} - passHostHeader = {{Get "true" $frontend "/passHostHeader"}} - passTLSCert = {{Get "false" $frontend "/passtlscert"}} + [frontends."{{ $frontendName }}"] + backend = "{{ getBackendName $frontend }}" + priority = {{ getPriority $frontend }} + passHostHeader = {{ getPassHostHeader $frontend }} + passTLSCert = {{ getPassTLSCert $frontend }} - {{$entryPoints := SplitGet $frontend "/entrypoints"}} - entryPoints = [{{range $entryPoints}} + entryPoints = [{{range getEntryPoints $frontend }} "{{.}}", {{end}}] - {{$whitelistSourceRange := SplitGet $frontend "/whitelistsourcerange"}} - whitelistSourceRange = [{{range $whitelistSourceRange}} + {{ $whitelistSourceRange := getWhitelistSourceRange $frontend }} + {{if $whitelistSourceRange }} + whitelistSourceRange = [{{range $whitelistSourceRange }} + "{{.}}", + {{end}}] + {{end}} + + basicAuth = [{{range getBasicAuth $frontend }} "{{.}}", {{end}}] - {{$basicAuth := SplitGet $frontend "/basicauth"}} - basicAuth = [{{range $basicAuth}} - "{{.}}", - {{end}}] - - {{$redirect := getRedirect $frontend }} - {{ if $redirect }} - [frontends."{{$frontendName}}".redirect] + {{ $redirect := getRedirect $frontend }} + {{if $redirect }} + [frontends."{{ $frontendName }}".redirect] entryPoint = "{{ $redirect.EntryPoint }}" regex = "{{ $redirect.Regex }}" replacement = "{{ $redirect.Replacement }}" {{end}} {{ $errorPages := getErrorPages $frontend }} - {{ if $errorPages }} - [frontends."{{$frontendName}}".errors] - {{ range $pageName, $page := $errorPages }} + {{if $errorPages }} + [frontends."{{ $frontendName }}".errors] + {{range $pageName, $page := $errorPages }} [frontends."{{$frontendName}}".errors.{{ $pageName }}] - status = [{{range $page.Status}} + status = [{{range $page.Status }} "{{.}}", {{end}}] backend = "{{$page.Backend}}" @@ -92,12 +89,12 @@ {{end}} {{ $rateLimit := getRateLimit $frontend }} - {{ if $rateLimit }} - [frontends."{{$frontendName}}".rateLimit] + {{if $rateLimit }} + [frontends."{{ $frontendName }}".rateLimit] extractorFunc = "{{ $rateLimit.ExtractorFunc }}" - [frontends."{{$frontendName}}".rateLimit.rateSet] - {{ range $limitName, $rateLimit := $rateLimit.RateSet }} - [frontends."{{$frontendName}}".rateLimit.rateSet.{{ $limitName }}] + [frontends."{{ $frontendName }}".rateLimit.rateSet] + {{range $limitName, $rateLimit := $rateLimit.RateSet }} + [frontends."{{ $frontendName }}".rateLimit.rateSet.{{ $limitName }}] period = "{{ $rateLimit.Period }}" average = {{ $rateLimit.Average }} burst = {{ $rateLimit.Burst }} @@ -105,7 +102,7 @@ {{end}} {{ $headers := getHeaders $frontend }} - {{ if $headers }} + {{if $headers }} [frontends."{{ $frontendName }}".headers] SSLRedirect = {{ $headers.SSLRedirect }} SSLTemporaryRedirect = {{ $headers.SSLTemporaryRedirect }} @@ -123,33 +120,33 @@ ReferrerPolicy = "{{ $headers.ReferrerPolicy }}" IsDevelopment = {{ $headers.IsDevelopment }} - {{ if $headers.AllowedHosts }} - AllowedHosts = [{{ range $headers.AllowedHosts }} + {{if $headers.AllowedHosts }} + AllowedHosts = [{{range $headers.AllowedHosts }} "{{.}}", {{end}}] {{end}} - {{ if $headers.HostsProxyHeaders }} - HostsProxyHeaders = [{{ range $headers.HostsProxyHeaders }} + {{if $headers.HostsProxyHeaders }} + HostsProxyHeaders = [{{range $headers.HostsProxyHeaders }} "{{.}}", {{end}}] {{end}} - {{ if $headers.CustomRequestHeaders }} + {{if $headers.CustomRequestHeaders }} [frontends."{{ $frontendName }}".headers.customRequestHeaders] - {{ range $k, $v := $headers.CustomRequestHeaders }} + {{range $k, $v := $headers.CustomRequestHeaders }} {{$k}} = "{{$v}}" {{end}} {{end}} - {{ if $headers.CustomResponseHeaders }} + {{if $headers.CustomResponseHeaders }} [frontends."{{ $frontendName }}".headers.customResponseHeaders] - {{ range $k, $v := $headers.CustomResponseHeaders }} + {{range $k, $v := $headers.CustomResponseHeaders }} {{$k}} = "{{$v}}" {{end}} {{end}} - {{ if $headers.SSLProxyHeaders }} + {{if $headers.SSLProxyHeaders }} [frontends."{{ $frontendName }}".headers.SSLProxyHeaders] {{range $k, $v := $headers.SSLProxyHeaders}} {{$k}} = "{{$v}}" @@ -157,24 +154,22 @@ {{end}} {{end}} - {{range $route := List $frontend "/routes/"}} - [frontends."{{$frontendName}}".routes."{{Last $route}}"] - rule = "{{Get "" $route "/rule"}}" + {{range $routeName, $route := getRoutes $frontend }} + [frontends."{{ $frontendName }}".routes."{{ $routeName }}"] + rule = "{{ $route.Rule }}" {{end}} {{end}} -{{range $tlsConfiguration := List .Prefix "/tlsconfiguration/"}} - +{{range $tlsConfiguration := getTLSConfigurations .Prefix }} [[tlsConfiguration]] - {{$entryPoints := SplitGet $tlsConfiguration "/entrypoints"}} - entryPoints = [{{range $entryPoints}} + entryPoints = [{{range $tlsConfiguration.EntryPoints }} "{{.}}", {{end}}] [tlsConfiguration.certificate] - certFile = """{{Get "" $tlsConfiguration "/certificate/certfile"}}""" - keyFile = """{{Get "" $tlsConfiguration "/certificate/keyfile"}}""" + certFile = """{{ $tlsConfiguration.Certificate.CertFile }}""" + keyFile = """{{ $tlsConfiguration.Certificate.KeyFile }}""" {{end}}