diff --git a/provider/consul/consul_catalog_config.go b/provider/consul/consul_catalog_config.go index 1e8064199..11042fbf4 100644 --- a/provider/consul/consul_catalog_config.go +++ b/provider/consul/consul_catalog_config.go @@ -49,6 +49,10 @@ func (p *CatalogProvider) buildConfiguration(catalog []catalogUpdate) *types.Con "getPassTLSCert": p.getFuncBoolAttribute(label.SuffixFrontendPassTLSCert, label.DefaultPassTLSCert), "getWhitelistSourceRange": p.getFuncSliceAttribute(label.SuffixFrontendWhitelistSourceRange), "getRedirect": p.getRedirect, + "hasErrorPages": p.getFuncHasAttributePrefix(label.BaseFrontendErrorPage), + "getErrorPages": p.getErrorPages, + "hasRateLimit": p.getFuncHasAttributePrefix(label.BaseFrontendRateLimit), + "getRateLimit": p.getRateLimit, } var allNodes []*api.ServiceEntry @@ -302,8 +306,54 @@ func (p *CatalogProvider) getRedirect(tags []string) *types.Redirect { return nil } +func (p *CatalogProvider) getErrorPages(tags []string) map[string]*types.ErrorPage { + labels := p.parseTagsToNeutralLabels(tags) + + prefix := label.Prefix + label.BaseFrontendErrorPage + return label.ParseErrorPages(labels, prefix, label.RegexpFrontendErrorPage) +} + +func (p *CatalogProvider) getRateLimit(tags []string) *types.RateLimit { + extractorFunc := p.getAttribute(label.SuffixFrontendRateLimitExtractorFunc, tags, "") + if len(extractorFunc) == 0 { + return nil + } + + labels := p.parseTagsToNeutralLabels(tags) + + prefix := label.Prefix + label.BaseFrontendRateLimit + limits := label.ParseRateSets(labels, prefix, label.RegexpFrontendRateLimit) + + return &types.RateLimit{ + ExtractorFunc: extractorFunc, + RateSet: limits, + } +} + // Base functions +func (p *CatalogProvider) parseTagsToNeutralLabels(tags []string) map[string]string { + var labels map[string]string + + for _, tag := range tags { + if strings.HasPrefix(tag, p.Prefix) { + + parts := strings.SplitN(tag, "=", 2) + if len(parts) == 2 { + if labels == nil { + labels = make(map[string]string) + } + + // replace custom prefix by the generic prefix + key := label.Prefix + strings.TrimPrefix(parts[0], p.Prefix+".") + labels[key] = parts[1] + } + } + } + + return labels +} + func (p *CatalogProvider) getFuncStringAttribute(name string, defaultValue string) func(tags []string) string { return func(tags []string) string { return p.getAttribute(name, tags, defaultValue) @@ -328,6 +378,12 @@ func (p *CatalogProvider) getFuncBoolAttribute(name string, defaultValue bool) f } } +func (p *CatalogProvider) getFuncHasAttributePrefix(name string) func(tags []string) bool { + return func(tags []string) bool { + return p.hasAttributePrefix(name, tags) + } +} + func (p *CatalogProvider) getInt64Attribute(name string, tags []string, defaultValue int64) int64 { rawValue := getTag(p.getPrefixedName(name), tags, "") @@ -386,6 +442,10 @@ func (p *CatalogProvider) hasAttribute(name string, tags []string) bool { return hasTag(p.getPrefixedName(name), tags) } +func (p *CatalogProvider) hasAttributePrefix(name string, tags []string) bool { + return hasTagPrefix(p.getPrefixedName(name), tags) +} + func (p *CatalogProvider) getAttribute(name string, tags []string, defaultValue string) string { return getTag(p.getPrefixedName(name), tags, defaultValue) } @@ -411,6 +471,19 @@ func hasTag(name string, tags []string) bool { return false } +func hasTagPrefix(name string, tags []string) bool { + lowerName := strings.ToLower(name) + + for _, tag := range tags { + lowerTag := strings.ToLower(tag) + + if strings.HasPrefix(lowerTag, lowerName) { + return true + } + } + return false +} + func getTag(name string, tags []string, defaultValue string) string { lowerName := strings.ToLower(name) diff --git a/provider/consul/consul_catalog_config_test.go b/provider/consul/consul_catalog_config_test.go index 2e3f7c118..51242d676 100644 --- a/provider/consul/consul_catalog_config_test.go +++ b/provider/consul/consul_catalog_config_test.go @@ -3,7 +3,9 @@ package consul import ( "testing" "text/template" + "time" + "github.com/containous/flaeg" "github.com/containous/traefik/provider/label" "github.com/containous/traefik/types" "github.com/hashicorp/consul/api" @@ -1056,3 +1058,111 @@ func TestCatalogProviderGetRedirect(t *testing.T) { }) } } + +func TestCatalogProviderGetErrorPages(t *testing.T) { + p := &CatalogProvider{ + Prefix: "traefik", + } + + testCases := []struct { + desc string + tags []string + expected map[string]*types.ErrorPage + }{ + { + desc: "should return nil when no tags", + tags: []string{}, + expected: nil, + }, + { + desc: "should return a map when tags are present", + tags: []string{ + label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus + "=404", + label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend + "=foo_backend", + 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 + "=bar_backend", + label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageQuery + "=bar_query", + }, + expected: map[string]*types.ErrorPage{ + "foo": { + Status: []string{"404"}, + Query: "foo_query", + Backend: "foo_backend", + }, + "bar": { + Status: []string{"500", "600"}, + Query: "bar_query", + Backend: "bar_backend", + }, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + result := p.getErrorPages(test.tags) + + assert.Equal(t, test.expected, result) + }) + } +} + +func TestCatalogProviderGetRateLimit(t *testing.T) { + p := &CatalogProvider{ + Prefix: "traefik", + } + + testCases := []struct { + desc string + tags []string + expected *types.RateLimit + }{ + { + desc: "should return nil when no tags", + tags: []string{}, + expected: nil, + }, + { + desc: "should return a map when tags are present", + tags: []string{ + 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", + }, + expected: &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 { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + result := p.getRateLimit(test.tags) + + assert.Equal(t, test.expected, result) + }) + } +} diff --git a/templates/consul_catalog.tmpl b/templates/consul_catalog.tmpl index bb92340c2..441165018 100644 --- a/templates/consul_catalog.tmpl +++ b/templates/consul_catalog.tmpl @@ -75,6 +75,33 @@ replacement = "{{ $redirect.Replacement }}" {{end}} + {{ if hasErrorPages $service.Attributes }} + [frontends."frontend-{{ $service.ServiceName }}".errors] + {{ range $pageName, $page := getErrorPages $service.Attributes }} + [frontends."frontend-{{ $service.ServiceName }}".errors.{{ $pageName }}] + status = [{{range $page.Status }} + "{{.}}", + {{end}}] + backend = "{{ $page.Backend }}" + query = "{{ $page.Query }}" + {{end}} + {{end}} + + {{ if hasRateLimit $service.Attributes }} + {{ $rateLimit := getRateLimit $service.Attributes }} + [frontends."frontend-{{ $service.ServiceName }}".rateLimit] + extractorFunc = "{{ $rateLimit.ExtractorFunc }}" + + [frontends."frontend-{{ $service.ServiceName }}".rateLimit.rateSet] + {{ range $limitName, $limit := $rateLimit.RateSet }} + [frontends."frontend-{{ $service.ServiceName }}".rateLimit.rateSet.{{ $limitName }}] + period = "{{ $limit.Period }}" + average = {{ $limit.Average }} + burst = {{ $limit.Burst }} + {{end}} + + {{end}} + [frontends."frontend-{{$service.ServiceName}}".routes."route-host-{{$service.ServiceName}}"] rule = "{{ getFrontendRule $service }}"