From 22405a1259f60eafc8f35721957a7218f388cf23 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 20 Dec 2017 16:33:26 +0100 Subject: [PATCH] feat(rancher): add rate limit and error pages labels. --- provider/rancher/config.go | 62 ++++++++++++----- provider/rancher/config_test.go | 114 ++++++++++++++++++++++++++++++++ templates/rancher.tmpl | 28 +++++++- 3 files changed, 186 insertions(+), 18 deletions(-) diff --git a/provider/rancher/config.go b/provider/rancher/config.go index 65c0a4806..784de13f4 100644 --- a/provider/rancher/config.go +++ b/provider/rancher/config.go @@ -15,17 +15,12 @@ import ( func (p *Provider) buildConfiguration(services []rancherData) *types.Configuration { var RancherFuncMap = template.FuncMap{ + "getDomain": getFuncString(label.TraefikDomain, p.Domain), // FIXME dead ? + + // Backend functions "getPort": getFuncString(label.TraefikPort, ""), - "getBackend": getBackend, - "getWeight": getFuncString(label.TraefikWeight, label.DefaultWeight), - "getDomain": getFuncString(label.TraefikDomain, p.Domain), "getProtocol": getFuncString(label.TraefikProtocol, label.DefaultProtocol), - "getPassHostHeader": getFuncString(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader), - "getPassTLSCert": getFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert), - "getPriority": getFuncString(label.TraefikFrontendPriority, label.DefaultFrontendPriority), - "getEntryPoints": getFuncSliceString(label.TraefikFrontendEntryPoints), - "getBasicAuth": getFuncSliceString(label.TraefikFrontendAuthBasic), - "getFrontendRule": p.getFrontendRule, + "getWeight": getFuncString(label.TraefikWeight, label.DefaultWeight), "hasCircuitBreakerLabel": hasFunc(label.TraefikBackendCircuitBreakerExpression), "getCircuitBreakerExpression": getFuncString(label.TraefikBackendCircuitBreakerExpression, label.DefaultCircuitBreakerExpression), "hasLoadBalancerLabel": hasLoadBalancerLabel, @@ -33,19 +28,33 @@ func (p *Provider) buildConfiguration(services []rancherData) *types.Configurati "hasMaxConnLabels": hasMaxConnLabels, "getMaxConnAmount": getFuncInt64(label.TraefikBackendMaxConnAmount, math.MaxInt64), "getMaxConnExtractorFunc": getFuncString(label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc), - "getSticky": getSticky, // deprecated + "getSticky": getSticky, "hasStickinessLabel": hasFunc(label.TraefikBackendLoadBalancerStickiness), "getStickinessCookieName": getFuncString(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName), - "hasRedirect": hasRedirect, - "getRedirectEntryPoint": getFuncString(label.TraefikFrontendRedirectEntryPoint, label.DefaultFrontendRedirectEntryPoint), - "getRedirectRegex": getFuncString(label.TraefikFrontendRedirectRegex, ""), - "getRedirectReplacement": getFuncString(label.TraefikFrontendRedirectReplacement, ""), - "getWhitelistSourceRange": getFuncSliceString(label.TraefikFrontendWhitelistSourceRange), "hasHealthCheckLabels": hasFunc(label.TraefikBackendHealthCheckPath), "getHealthCheckPath": getFuncString(label.TraefikBackendHealthCheckPath, ""), "getHealthCheckPort": getFuncInt(label.TraefikBackendHealthCheckPort, label.DefaultBackendHealthCheckPort), "getHealthCheckInterval": getFuncString(label.TraefikBackendHealthCheckInterval, ""), + // Frontend functions + "getBackend": getBackend, + "getPriority": getFuncString(label.TraefikFrontendPriority, label.DefaultFrontendPriority), + "getPassHostHeader": getFuncString(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader), + "getPassTLSCert": getFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert), + "getEntryPoints": getFuncSliceString(label.TraefikFrontendEntryPoints), + "getBasicAuth": getFuncSliceString(label.TraefikFrontendAuthBasic), + "getWhitelistSourceRange": getFuncSliceString(label.TraefikFrontendWhitelistSourceRange), + "getFrontendRule": p.getFrontendRule, + "hasRedirect": hasRedirect, + "getRedirectEntryPoint": getFuncString(label.TraefikFrontendRedirectEntryPoint, label.DefaultFrontendRedirectEntryPoint), + "getRedirectRegex": getFuncString(label.TraefikFrontendRedirectRegex, ""), + "getRedirectReplacement": getFuncString(label.TraefikFrontendRedirectReplacement, ""), + "hasErrorPages": hasPrefixFunc(label.Prefix + label.BaseFrontendErrorPage), + "getErrorPages": getErrorPages, + "hasRateLimits": hasFunc(label.TraefikFrontendRateLimitExtractorFunc), + "getRateLimitsExtractorFunc": getFuncString(label.TraefikFrontendRateLimitExtractorFunc, ""), + "getRateLimits": getRateLimits, + // Headers "hasRequestHeaders": hasFunc(label.TraefikFrontendRequestHeaders), "getRequestHeaders": getFuncMap(label.TraefikFrontendRequestHeaders), "hasResponseHeaders": hasFunc(label.TraefikFrontendResponseHeaders), @@ -195,8 +204,21 @@ func getBackend(service rancherData) string { } func hasRedirect(service rancherData) bool { - return label.Has(service.Labels, label.TraefikFrontendRedirectEntryPoint) || - label.Has(service.Labels, label.TraefikFrontendRedirectRegex) && label.Has(service.Labels, label.TraefikFrontendRedirectReplacement) + frep := label.Has(service.Labels, label.TraefikFrontendRedirectEntryPoint) + frrg := label.Has(service.Labels, label.TraefikFrontendRedirectRegex) + frrp := label.Has(service.Labels, label.TraefikFrontendRedirectReplacement) + + return frep || frrg && frrp +} + +func getErrorPages(service rancherData) map[string]*types.ErrorPage { + prefix := label.Prefix + label.BaseFrontendErrorPage + return label.ParseErrorPages(service.Labels, prefix, label.RegexpFrontendErrorPage) +} + +func getRateLimits(service rancherData) map[string]*types.Rate { + prefix := label.Prefix + label.BaseFrontendRateLimit + return label.ParseRateSets(service.Labels, prefix, label.RegexpFrontendRateLimit) } // Label functions @@ -242,3 +264,9 @@ func hasFunc(labelName string) func(service rancherData) bool { return label.Has(service.Labels, labelName) } } + +func hasPrefixFunc(prefix string) func(service rancherData) bool { + return func(service rancherData) bool { + return label.HasPrefix(service.Labels, prefix) + } +} diff --git a/provider/rancher/config_test.go b/provider/rancher/config_test.go index 058a510f6..a4ac04e15 100644 --- a/provider/rancher/config_test.go +++ b/provider/rancher/config_test.go @@ -2,7 +2,9 @@ package rancher import ( "testing" + "time" + "github.com/containous/flaeg" "github.com/containous/traefik/provider/label" "github.com/containous/traefik/types" "github.com/stretchr/testify/assert" @@ -70,6 +72,118 @@ func TestProviderBuildConfiguration(t *testing.T) { }, }, }, + { + desc: "with Error Pages", + services: []rancherData{ + { + Name: "test/service", + Labels: map[string]string{ + label.TraefikPort: "80", + label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: "404", + label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend: "foobar", + label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageQuery: "foo_query", + label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageStatus: "500,600", + label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageBackend: "foobar", + label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageQuery: "bar_query", + }, + Health: "healthy", + Containers: []string{"127.0.0.1"}, + }, + }, + expectedBackends: map[string]*types.Backend{ + "backend-test-service": { + Servers: map[string]types.Server{ + "server-0": { + URL: "http://127.0.0.1:80", + Weight: 0, + }, + }, + }, + }, + expectedFrontends: map[string]*types.Frontend{ + "frontend-Host-test-service-rancher-localhost": { + EntryPoints: []string{}, + BasicAuth: []string{}, + Backend: "backend-test-service", + Routes: map[string]types.Route{ + "route-frontend-Host-test-service-rancher-localhost": { + Rule: "Host:test.service.rancher.localhost", + }, + }, + PassHostHeader: true, + Errors: map[string]*types.ErrorPage{ + "foo": { + Status: []string{"404"}, + Query: "foo_query", + Backend: "foobar", + }, + "bar": { + Status: []string{"500", "600"}, + Query: "bar_query", + Backend: "foobar", + }, + }, + }, + }, + }, + { + desc: "with rate Limits", + services: []rancherData{ + { + Name: "test/service", + Labels: map[string]string{ + label.TraefikPort: "80", + label.TraefikFrontendRateLimitExtractorFunc: "client.ip", + label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: "6", + label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: "12", + label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: "18", + label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: "3", + label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: "6", + label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst: "9", + }, + Health: "healthy", + Containers: []string{"127.0.0.1"}, + }, + }, + expectedBackends: map[string]*types.Backend{ + "backend-test-service": { + Servers: map[string]types.Server{ + "server-0": { + URL: "http://127.0.0.1:80", + Weight: 0, + }, + }, + }, + }, + expectedFrontends: map[string]*types.Frontend{ + "frontend-Host-test-service-rancher-localhost": { + EntryPoints: []string{}, + BasicAuth: []string{}, + Backend: "backend-test-service", + Routes: map[string]types.Route{ + "route-frontend-Host-test-service-rancher-localhost": { + Rule: "Host:test.service.rancher.localhost", + }, + }, + PassHostHeader: true, + RateLimit: &types.RateLimit{ + ExtractorFunc: "client.ip", + RateSet: map[string]*types.Rate{ + "foo": { + Period: flaeg.Duration(6 * time.Second), + Average: 12, + Burst: 18, + }, + "bar": { + Period: flaeg.Duration(3 * time.Second), + Average: 6, + Burst: 9, + }, + }, + }, + }, + }, + }, } for _, test := range testCases { diff --git a/templates/rancher.tmpl b/templates/rancher.tmpl index 67ba00395..00609cd97 100644 --- a/templates/rancher.tmpl +++ b/templates/rancher.tmpl @@ -2,6 +2,8 @@ [backends] {{range $backendName, $backend := .Backends}} + [backends.backend-{{$backendName}}] + {{if hasCircuitBreakerLabel $backend}} [backends.backend-{{$backendName}}.circuitBreaker] expression = "{{getCircuitBreakerExpression $backend}}" @@ -68,7 +70,31 @@ replacement = "{{getRedirectReplacement $service}}" {{end}} - [frontends."frontend-{{$frontendName}}".headers] + {{ if hasErrorPages $service }} + [frontends."frontend-{{$frontendName}}".errors] + {{ range $pageName, $page := getErrorPages $service }} + [frontends."frontend-{{$frontendName}}".errors.{{ $pageName }}] + status = [{{range $page.Status}} + "{{.}}", + {{end}}] + backend = "{{$page.Backend}}" + query = "{{$page.Query}}" + {{end}} + {{end}} + + {{ if hasRateLimits $service }} + [frontends."frontend-{{$frontendName}}".rateLimit] + extractorFunc = "{{ getRateLimitsExtractorFunc $service }}" + [frontends."frontend-{{$frontendName}}".rateLimit.rateSet] + {{ range $limitName, $rateLimit := getRateLimits $service }} + [frontends."frontend-{{$frontendName}}".rateLimit.rateSet.{{ $limitName }}] + period = "{{ $rateLimit.Period }}" + average = {{ $rateLimit.Average }} + burst = {{ $rateLimit.Burst }} + {{end}} + {{end}} + + [frontends."frontend-{{$frontendName}}".headers] {{if hasSSLRedirectHeaders $service}} SSLRedirect = {{getSSLRedirectHeaders $service}} {{end}}