feat(consulcatalog): add rate limit and error pages tags.
This commit is contained in:
parent
170fc13e02
commit
e56551d047
3 changed files with 210 additions and 0 deletions
|
@ -49,6 +49,10 @@ func (p *CatalogProvider) buildConfiguration(catalog []catalogUpdate) *types.Con
|
||||||
"getPassTLSCert": p.getFuncBoolAttribute(label.SuffixFrontendPassTLSCert, label.DefaultPassTLSCert),
|
"getPassTLSCert": p.getFuncBoolAttribute(label.SuffixFrontendPassTLSCert, label.DefaultPassTLSCert),
|
||||||
"getWhitelistSourceRange": p.getFuncSliceAttribute(label.SuffixFrontendWhitelistSourceRange),
|
"getWhitelistSourceRange": p.getFuncSliceAttribute(label.SuffixFrontendWhitelistSourceRange),
|
||||||
"getRedirect": p.getRedirect,
|
"getRedirect": p.getRedirect,
|
||||||
|
"hasErrorPages": p.getFuncHasAttributePrefix(label.BaseFrontendErrorPage),
|
||||||
|
"getErrorPages": p.getErrorPages,
|
||||||
|
"hasRateLimit": p.getFuncHasAttributePrefix(label.BaseFrontendRateLimit),
|
||||||
|
"getRateLimit": p.getRateLimit,
|
||||||
}
|
}
|
||||||
|
|
||||||
var allNodes []*api.ServiceEntry
|
var allNodes []*api.ServiceEntry
|
||||||
|
@ -302,8 +306,54 @@ func (p *CatalogProvider) getRedirect(tags []string) *types.Redirect {
|
||||||
return nil
|
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
|
// 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 {
|
func (p *CatalogProvider) getFuncStringAttribute(name string, defaultValue string) func(tags []string) string {
|
||||||
return func(tags []string) string {
|
return func(tags []string) string {
|
||||||
return p.getAttribute(name, tags, defaultValue)
|
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 {
|
func (p *CatalogProvider) getInt64Attribute(name string, tags []string, defaultValue int64) int64 {
|
||||||
rawValue := getTag(p.getPrefixedName(name), tags, "")
|
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)
|
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 {
|
func (p *CatalogProvider) getAttribute(name string, tags []string, defaultValue string) string {
|
||||||
return getTag(p.getPrefixedName(name), tags, defaultValue)
|
return getTag(p.getPrefixedName(name), tags, defaultValue)
|
||||||
}
|
}
|
||||||
|
@ -411,6 +471,19 @@ func hasTag(name string, tags []string) bool {
|
||||||
return false
|
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 {
|
func getTag(name string, tags []string, defaultValue string) string {
|
||||||
lowerName := strings.ToLower(name)
|
lowerName := strings.ToLower(name)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,9 @@ package consul
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containous/flaeg"
|
||||||
"github.com/containous/traefik/provider/label"
|
"github.com/containous/traefik/provider/label"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/hashicorp/consul/api"
|
"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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -75,6 +75,33 @@
|
||||||
replacement = "{{ $redirect.Replacement }}"
|
replacement = "{{ $redirect.Replacement }}"
|
||||||
{{end}}
|
{{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}}"]
|
[frontends."frontend-{{$service.ServiceName}}".routes."route-host-{{$service.ServiceName}}"]
|
||||||
rule = "{{ getFrontendRule $service }}"
|
rule = "{{ getFrontendRule $service }}"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue