refactor: Consul Catalog labels.
This commit is contained in:
parent
46db91ce73
commit
ff61cc971e
14 changed files with 1715 additions and 2018 deletions
|
@ -246,6 +246,15 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
|
|||
}
|
||||
}
|
||||
|
||||
if gc.ConsulCatalog != nil {
|
||||
if len(gc.ConsulCatalog.Filename) != 0 && gc.ConsulCatalog.TemplateVersion != 2 {
|
||||
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
|
||||
gc.ConsulCatalog.TemplateVersion = 1
|
||||
} else {
|
||||
gc.ConsulCatalog.TemplateVersion = 2
|
||||
}
|
||||
}
|
||||
|
||||
if gc.Rancher != nil {
|
||||
if len(gc.Rancher.Filename) != 0 && gc.Rancher.TemplateVersion != 2 {
|
||||
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/containous/traefik/integration/try"
|
||||
"github.com/containous/traefik/provider/label"
|
||||
"github.com/go-check/check"
|
||||
"github.com/hashicorp/consul/api"
|
||||
checker "github.com/vdemeester/shakers"
|
||||
|
@ -160,7 +161,6 @@ func (s *ConsulCatalogSuite) TestSingleService(c *check.C) {
|
|||
s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
||||
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody())
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) TestExposedByDefaultFalseSingleService(c *check.C) {
|
||||
|
@ -202,13 +202,12 @@ func (s *ConsulCatalogSuite) TestExposedByDefaultFalseSimpleServiceMultipleNode(
|
|||
defer cmd.Process.Kill()
|
||||
|
||||
whoami := s.composeProject.Container(c, "whoami1")
|
||||
whoami2 := s.composeProject.Container(c, "whoami2")
|
||||
|
||||
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
||||
|
||||
err = s.registerService("test", whoami2.NetworkSettings.IPAddress, 80, []string{"traefik.enable=true"})
|
||||
whoami2 := s.composeProject.Container(c, "whoami2")
|
||||
err = s.registerService("test", whoami2.NetworkSettings.IPAddress, 80, []string{label.TraefikEnable + "=true"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", whoami2.NetworkSettings.IPAddress)
|
||||
|
||||
|
@ -326,7 +325,7 @@ func (s *ConsulCatalogSuite) TestBasicAuthSimpleService(c *check.C) {
|
|||
whoami := s.composeProject.Container(c, "whoami1")
|
||||
|
||||
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{
|
||||
"traefik.frontend.auth.basic=test:$2a$06$O5NksJPAcgrC9MuANkSoE.Xe9DSg7KcLLFYNr1Lj6hPcMmvgwxhme,test2:$2y$10$xP1SZ70QbZ4K2bTGKJOhpujkpcLxQcB3kEPF6XAV19IdcqsZTyDEe",
|
||||
label.TraefikFrontendAuthBasic + "=test:$2a$06$O5NksJPAcgrC9MuANkSoE.Xe9DSg7KcLLFYNr1Lj6hPcMmvgwxhme,test2:$2y$10$xP1SZ70QbZ4K2bTGKJOhpujkpcLxQcB3kEPF6XAV19IdcqsZTyDEe",
|
||||
})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
||||
|
@ -362,7 +361,8 @@ func (s *ConsulCatalogSuite) TestRefreshConfigTagChange(c *check.C) {
|
|||
|
||||
whoami := s.composeProject.Container(c, "whoami1")
|
||||
|
||||
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1", "traefik.enable=false", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
|
||||
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80,
|
||||
[]string{"name=whoami1", label.TraefikEnable + "=false", label.TraefikBackendCircuitBreakerExpression + "=NetworkErrorRatio() > 0.5"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
||||
|
||||
|
@ -370,7 +370,8 @@ func (s *ConsulCatalogSuite) TestRefreshConfigTagChange(c *check.C) {
|
|||
try.BodyContains(whoami.NetworkSettings.IPAddress))
|
||||
c.Assert(err, checker.NotNil)
|
||||
|
||||
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1", "traefik.enable=true", "traefik.backend.circuitbreaker=ResponseCodeRatio(500, 600, 0, 600) > 0.5"})
|
||||
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80,
|
||||
[]string{"name=whoami1", label.TraefikEnable + "=true", label.TraefikBackendCircuitBreakerExpression + "=ResponseCodeRatio(500, 600, 0, 600) > 0.5"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||
|
@ -403,16 +404,20 @@ func (s *ConsulCatalogSuite) TestCircuitBreaker(c *check.C) {
|
|||
defer cmd.Process.Kill()
|
||||
|
||||
whoami := s.composeProject.Container(c, "whoami1")
|
||||
whoami2 := s.composeProject.Container(c, "whoami2")
|
||||
whoami3 := s.composeProject.Container(c, "whoami3")
|
||||
|
||||
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1", "traefik.enable=true", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
|
||||
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80,
|
||||
[]string{"name=whoami1", label.TraefikEnable + "=true", label.TraefikBackendCircuitBreakerExpression + "=NetworkErrorRatio() > 0.5"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
||||
err = s.registerService("test", whoami2.NetworkSettings.IPAddress, 42, []string{"name=whoami2", "traefik.enable=true", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
|
||||
|
||||
whoami2 := s.composeProject.Container(c, "whoami2")
|
||||
err = s.registerService("test", whoami2.NetworkSettings.IPAddress, 42,
|
||||
[]string{"name=whoami2", label.TraefikEnable + "=true", label.TraefikBackendCircuitBreakerExpression + "=NetworkErrorRatio() > 0.5"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", whoami2.NetworkSettings.IPAddress)
|
||||
err = s.registerService("test", whoami3.NetworkSettings.IPAddress, 42, []string{"name=whoami3", "traefik.enable=true", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
|
||||
|
||||
whoami3 := s.composeProject.Container(c, "whoami3")
|
||||
err = s.registerService("test", whoami3.NetworkSettings.IPAddress, 42,
|
||||
[]string{"name=whoami3", label.TraefikEnable + "=true", label.TraefikBackendCircuitBreakerExpression + "=NetworkErrorRatio() > 0.5"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", whoami3.NetworkSettings.IPAddress)
|
||||
|
||||
|
@ -452,7 +457,7 @@ func (s *ConsulCatalogSuite) TestRefreshConfigPortChange(c *check.C) {
|
|||
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 5*time.Second, try.BodyContains(whoami.NetworkSettings.IPAddress))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1", "traefik.enable=true"})
|
||||
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1", label.TraefikEnable + "=true"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
|
||||
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
||||
|
|
273
provider/consulcatalog/config.go
Normal file
273
provider/consulcatalog/config.go
Normal file
|
@ -0,0 +1,273 @@
|
|||
package consulcatalog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/provider"
|
||||
"github.com/containous/traefik/provider/label"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/hashicorp/consul/api"
|
||||
)
|
||||
|
||||
func (p *Provider) buildConfigurationV2(catalog []catalogUpdate) *types.Configuration {
|
||||
var funcMap = template.FuncMap{
|
||||
"getAttribute": p.getAttribute,
|
||||
"getTag": getTag,
|
||||
"hasTag": hasTag,
|
||||
|
||||
// Backend functions
|
||||
"getNodeBackendName": getNodeBackendName,
|
||||
"getServiceBackendName": getServiceBackendName,
|
||||
"getBackendAddress": getBackendAddress,
|
||||
"getServerName": getServerName,
|
||||
"getCircuitBreaker": getCircuitBreaker,
|
||||
"getLoadBalancer": getLoadBalancer,
|
||||
"getMaxConn": label.GetMaxConn,
|
||||
"getHealthCheck": label.GetHealthCheck,
|
||||
"getBuffering": label.GetBuffering,
|
||||
"getServer": p.getServer,
|
||||
|
||||
// Frontend functions
|
||||
"getFrontendRule": p.getFrontendRule,
|
||||
"getBasicAuth": label.GetFuncSliceString(label.TraefikFrontendAuthBasic),
|
||||
"getFrontEndEntryPoints": label.GetFuncSliceString(label.TraefikFrontendEntryPoints),
|
||||
"getPriority": label.GetFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
|
||||
"getPassHostHeader": label.GetFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
|
||||
"getPassTLSCert": label.GetFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
|
||||
"getWhiteList": label.GetWhiteList,
|
||||
"getRedirect": label.GetRedirect,
|
||||
"getErrorPages": label.GetErrorPages,
|
||||
"getRateLimit": label.GetRateLimit,
|
||||
"getHeaders": label.GetHeaders,
|
||||
}
|
||||
|
||||
var allNodes []*api.ServiceEntry
|
||||
var services []*serviceUpdate
|
||||
for _, info := range catalog {
|
||||
if len(info.Nodes) > 0 {
|
||||
services = append(services, info.Service)
|
||||
allNodes = append(allNodes, info.Nodes...)
|
||||
}
|
||||
}
|
||||
// Ensure a stable ordering of nodes so that identical configurations may be detected
|
||||
sort.Sort(nodeSorter(allNodes))
|
||||
|
||||
templateObjects := struct {
|
||||
Services []*serviceUpdate
|
||||
Nodes []*api.ServiceEntry
|
||||
}{
|
||||
Services: services,
|
||||
Nodes: allNodes,
|
||||
}
|
||||
|
||||
configuration, err := p.GetConfiguration("templates/consul_catalog.tmpl", funcMap, templateObjects)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create config")
|
||||
}
|
||||
|
||||
return configuration
|
||||
}
|
||||
|
||||
// Specific functions
|
||||
|
||||
func (p *Provider) getFrontendRule(service serviceUpdate) string {
|
||||
customFrontendRule := label.GetStringValue(service.TraefikLabels, label.TraefikFrontendRule, "")
|
||||
if customFrontendRule == "" {
|
||||
customFrontendRule = p.FrontEndRule
|
||||
}
|
||||
|
||||
tmpl := p.frontEndRuleTemplate
|
||||
tmpl, err := tmpl.Parse(customFrontendRule)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to parse Consul Catalog custom frontend rule: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
templateObjects := struct {
|
||||
ServiceName string
|
||||
Domain string
|
||||
Attributes []string
|
||||
}{
|
||||
ServiceName: service.ServiceName,
|
||||
Domain: p.Domain,
|
||||
Attributes: service.Attributes,
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
err = tmpl.Execute(&buffer, templateObjects)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to execute Consul Catalog custom frontend rule template: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (p *Provider) getServer(node *api.ServiceEntry) types.Server {
|
||||
scheme := p.getAttribute(label.SuffixProtocol, node.Service.Tags, label.DefaultProtocol)
|
||||
address := getBackendAddress(node)
|
||||
|
||||
return types.Server{
|
||||
URL: fmt.Sprintf("%s://%s:%d", scheme, address, node.Service.Port),
|
||||
Weight: p.getWeight(node.Service.Tags),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) setupFrontEndRuleTemplate() {
|
||||
var FuncMap = template.FuncMap{
|
||||
"getAttribute": p.getAttribute,
|
||||
"getTag": getTag,
|
||||
"hasTag": hasTag,
|
||||
}
|
||||
p.frontEndRuleTemplate = template.New("consul catalog frontend rule").Funcs(FuncMap)
|
||||
}
|
||||
|
||||
// Specific functions
|
||||
|
||||
// Only for compatibility
|
||||
// Deprecated
|
||||
func getLoadBalancer(labels map[string]string) *types.LoadBalancer {
|
||||
if v, ok := labels[label.TraefikBackendLoadBalancer]; ok {
|
||||
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancer, label.TraefikBackendLoadBalancerMethod)
|
||||
if !label.Has(labels, label.TraefikBackendLoadBalancerMethod) {
|
||||
labels[label.TraefikBackendLoadBalancerMethod] = v
|
||||
}
|
||||
}
|
||||
|
||||
return label.GetLoadBalancer(labels)
|
||||
}
|
||||
|
||||
// Only for compatibility
|
||||
// Deprecated
|
||||
func getCircuitBreaker(labels map[string]string) *types.CircuitBreaker {
|
||||
if v, ok := labels[label.TraefikBackendCircuitBreaker]; ok {
|
||||
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendCircuitBreaker, label.TraefikBackendCircuitBreakerExpression)
|
||||
if !label.Has(labels, label.TraefikBackendCircuitBreakerExpression) {
|
||||
labels[label.TraefikBackendCircuitBreakerExpression] = v
|
||||
}
|
||||
}
|
||||
|
||||
return label.GetCircuitBreaker(labels)
|
||||
}
|
||||
|
||||
func getServiceBackendName(service *serviceUpdate) string {
|
||||
return strings.ToLower(service.ServiceName)
|
||||
}
|
||||
|
||||
func getNodeBackendName(node *api.ServiceEntry) string {
|
||||
return strings.ToLower(node.Service.Service)
|
||||
}
|
||||
|
||||
func getBackendAddress(node *api.ServiceEntry) string {
|
||||
if node.Service.Address != "" {
|
||||
return node.Service.Address
|
||||
}
|
||||
return node.Node.Address
|
||||
}
|
||||
|
||||
func getServerName(node *api.ServiceEntry, index int) string {
|
||||
serviceName := node.Service.Service + node.Service.Address + strconv.Itoa(node.Service.Port)
|
||||
// TODO sort tags ?
|
||||
serviceName += strings.Join(node.Service.Tags, "")
|
||||
|
||||
hash := sha1.New()
|
||||
_, err := hash.Write([]byte(serviceName))
|
||||
if err != nil {
|
||||
// Impossible case
|
||||
log.Error(err)
|
||||
} else {
|
||||
serviceName = base64.URLEncoding.EncodeToString(hash.Sum(nil))
|
||||
}
|
||||
|
||||
// unique int at the end
|
||||
return provider.Normalize(node.Service.Service + "-" + strconv.Itoa(index) + "-" + serviceName)
|
||||
}
|
||||
|
||||
func (p *Provider) getWeight(tags []string) int {
|
||||
weight := p.getIntAttribute(label.SuffixWeight, tags, label.DefaultWeightInt)
|
||||
|
||||
// Deprecated
|
||||
deprecatedWeightTag := "backend." + label.SuffixWeight
|
||||
if p.hasAttribute(deprecatedWeightTag, tags) {
|
||||
log.Warnf("Deprecated configuration found: %s. Please use %s.",
|
||||
p.getPrefixedName(deprecatedWeightTag), p.getPrefixedName(label.SuffixWeight))
|
||||
|
||||
weight = p.getIntAttribute(deprecatedWeightTag, tags, label.DefaultWeightInt)
|
||||
}
|
||||
|
||||
return weight
|
||||
}
|
||||
|
||||
// Base functions
|
||||
|
||||
func (p *Provider) hasAttribute(name string, tags []string) bool {
|
||||
return hasTag(p.getPrefixedName(name), tags)
|
||||
}
|
||||
|
||||
func (p *Provider) getAttribute(name string, tags []string, defaultValue string) string {
|
||||
return getTag(p.getPrefixedName(name), tags, defaultValue)
|
||||
}
|
||||
|
||||
func (p *Provider) getPrefixedName(name string) string {
|
||||
if len(p.Prefix) > 0 && len(name) > 0 {
|
||||
return p.Prefix + "." + name
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func hasTag(name string, tags []string) bool {
|
||||
lowerName := strings.ToLower(name)
|
||||
|
||||
for _, tag := range tags {
|
||||
lowerTag := strings.ToLower(tag)
|
||||
|
||||
// Given the nature of Consul tags, which could be either singular markers, or key=value pairs
|
||||
if strings.HasPrefix(lowerTag, lowerName+"=") || lowerTag == lowerName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
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)
|
||||
|
||||
for _, tag := range tags {
|
||||
lowerTag := strings.ToLower(tag)
|
||||
|
||||
// Given the nature of Consul tags, which could be either singular markers, or key=value pairs
|
||||
if strings.HasPrefix(lowerTag, lowerName+"=") || lowerTag == lowerName {
|
||||
// In case, where a tag might be a key=value, try to split it by the first '='
|
||||
kv := strings.SplitN(tag, "=", 2)
|
||||
|
||||
// If the returned result is a key=value pair, return the 'value' component
|
||||
if len(kv) == 2 {
|
||||
return kv[1]
|
||||
}
|
||||
// If the returned result is a singular marker, return the 'key' component
|
||||
return kv[0]
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
10
provider/consulcatalog/config_root.go
Normal file
10
provider/consulcatalog/config_root.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package consulcatalog
|
||||
|
||||
import "github.com/containous/traefik/types"
|
||||
|
||||
func (p *Provider) buildConfiguration(catalog []catalogUpdate) *types.Configuration {
|
||||
if p.TemplateVersion == 1 {
|
||||
return p.buildConfigurationV1(catalog)
|
||||
}
|
||||
return p.buildConfigurationV2(catalog)
|
||||
}
|
518
provider/consulcatalog/config_test.go
Normal file
518
provider/consulcatalog/config_test.go
Normal file
|
@ -0,0 +1,518 @@
|
|||
package consulcatalog
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
"github.com/containous/traefik/provider/label"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestProviderBuildConfiguration(t *testing.T) {
|
||||
p := &Provider{
|
||||
Domain: "localhost",
|
||||
Prefix: "traefik",
|
||||
ExposedByDefault: false,
|
||||
FrontEndRule: "Host:{{.ServiceName}}.{{.Domain}}",
|
||||
frontEndRuleTemplate: template.New("consul catalog frontend rule"),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
nodes []catalogUpdate
|
||||
expectedFrontends map[string]*types.Frontend
|
||||
expectedBackends map[string]*types.Backend
|
||||
}{
|
||||
{
|
||||
desc: "Should build config of nothing",
|
||||
nodes: []catalogUpdate{},
|
||||
expectedFrontends: map[string]*types.Frontend{},
|
||||
expectedBackends: map[string]*types.Backend{},
|
||||
},
|
||||
{
|
||||
desc: "Should build config with no frontend and backend",
|
||||
nodes: []catalogUpdate{
|
||||
{
|
||||
Service: &serviceUpdate{
|
||||
ServiceName: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{},
|
||||
expectedBackends: map[string]*types.Backend{},
|
||||
},
|
||||
{
|
||||
desc: "Should build config who contains one frontend and one backend",
|
||||
nodes: []catalogUpdate{
|
||||
{
|
||||
Service: &serviceUpdate{
|
||||
ServiceName: "test",
|
||||
Attributes: []string{
|
||||
"random.foo=bar",
|
||||
label.TraefikBackendLoadBalancerMethod + "=drr",
|
||||
label.TraefikBackendCircuitBreakerExpression + "=NetworkErrorRatio() > 0.5",
|
||||
label.TraefikBackendMaxConnAmount + "=1000",
|
||||
label.TraefikBackendMaxConnExtractorFunc + "=client.ip",
|
||||
label.TraefikFrontendAuthBasic + "=test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
},
|
||||
},
|
||||
Nodes: []*api.ServiceEntry{
|
||||
{
|
||||
Service: &api.AgentService{
|
||||
Service: "test",
|
||||
Address: "127.0.0.1",
|
||||
Port: 80,
|
||||
Tags: []string{
|
||||
"random.foo=bar",
|
||||
label.Prefix + "backend.weight=42", // Deprecated label
|
||||
label.TraefikFrontendPassHostHeader + "=true",
|
||||
label.TraefikProtocol + "=https",
|
||||
},
|
||||
},
|
||||
Node: &api.Node{
|
||||
Node: "localhost",
|
||||
Address: "127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{
|
||||
"frontend-test": {
|
||||
Backend: "backend-test",
|
||||
PassHostHeader: true,
|
||||
Routes: map[string]types.Route{
|
||||
"route-host-test": {
|
||||
Rule: "Host:test.localhost",
|
||||
},
|
||||
},
|
||||
EntryPoints: []string{},
|
||||
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
|
||||
},
|
||||
},
|
||||
expectedBackends: map[string]*types.Backend{
|
||||
"backend-test": {
|
||||
Servers: map[string]types.Server{
|
||||
"test-0-us4-27hAOu2ARV7nNrmv6GoKlcA": {
|
||||
URL: "https://127.0.0.1:80",
|
||||
Weight: 42,
|
||||
},
|
||||
},
|
||||
LoadBalancer: &types.LoadBalancer{
|
||||
Method: "drr",
|
||||
},
|
||||
CircuitBreaker: &types.CircuitBreaker{
|
||||
Expression: "NetworkErrorRatio() > 0.5",
|
||||
},
|
||||
MaxConn: &types.MaxConn{
|
||||
Amount: 1000,
|
||||
ExtractorFunc: "client.ip",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
nodes := fakeLoadTraefikLabelsSlice(test.nodes, p.Prefix)
|
||||
|
||||
actualConfig := p.buildConfigurationV2(nodes)
|
||||
assert.NotNil(t, actualConfig)
|
||||
assert.Equal(t, test.expectedBackends, actualConfig.Backends)
|
||||
assert.Equal(t, test.expectedFrontends, actualConfig.Frontends)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTag(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
tags []string
|
||||
key string
|
||||
defaultValue string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "Should return value of foo.bar key",
|
||||
tags: []string{
|
||||
"foo.bar=random",
|
||||
"traefik.backend.weight=42",
|
||||
"management",
|
||||
},
|
||||
key: "foo.bar",
|
||||
defaultValue: "0",
|
||||
expected: "random",
|
||||
},
|
||||
{
|
||||
desc: "Should return default value when nonexistent key",
|
||||
tags: []string{
|
||||
"foo.bar.foo.bar=random",
|
||||
"traefik.backend.weight=42",
|
||||
"management",
|
||||
},
|
||||
key: "foo.bar",
|
||||
defaultValue: "0",
|
||||
expected: "0",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := getTag(test.key, test.tags, test.defaultValue)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasTag(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
name string
|
||||
tags []string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
desc: "tag without value",
|
||||
name: "foo",
|
||||
tags: []string{"foo"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
desc: "tag with value",
|
||||
name: "foo",
|
||||
tags: []string{"foo=true"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
desc: "missing tag",
|
||||
name: "foo",
|
||||
tags: []string{"foobar=true"},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := hasTag(test.name, test.tags)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderGetPrefixedName(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
name string
|
||||
prefix string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "empty name with prefix",
|
||||
name: "",
|
||||
prefix: "foo",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
desc: "empty name without prefix",
|
||||
name: "",
|
||||
prefix: "",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
desc: "with prefix",
|
||||
name: "bar",
|
||||
prefix: "foo",
|
||||
expected: "foo.bar",
|
||||
},
|
||||
{
|
||||
desc: "without prefix",
|
||||
name: "bar",
|
||||
prefix: "",
|
||||
expected: "bar",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := &Provider{Prefix: test.prefix}
|
||||
|
||||
actual := p.getPrefixedName(test.name)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestProviderGetAttribute(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
tags []string
|
||||
key string
|
||||
defaultValue string
|
||||
prefix string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "Should return tag value 42",
|
||||
prefix: "traefik",
|
||||
tags: []string{
|
||||
"foo.bar=ramdom",
|
||||
"traefik.backend.weight=42",
|
||||
},
|
||||
key: "backend.weight",
|
||||
defaultValue: "0",
|
||||
expected: "42",
|
||||
},
|
||||
{
|
||||
desc: "Should return tag default value 0",
|
||||
prefix: "traefik",
|
||||
tags: []string{
|
||||
"foo.bar=ramdom",
|
||||
"traefik.backend.wei=42",
|
||||
},
|
||||
key: "backend.weight",
|
||||
defaultValue: "0",
|
||||
expected: "0",
|
||||
},
|
||||
{
|
||||
desc: "Should return tag value 42 when empty prefix",
|
||||
tags: []string{
|
||||
"foo.bar=ramdom",
|
||||
"backend.weight=42",
|
||||
},
|
||||
key: "backend.weight",
|
||||
defaultValue: "0",
|
||||
expected: "42",
|
||||
},
|
||||
{
|
||||
desc: "Should return default value 0 when empty prefix",
|
||||
tags: []string{
|
||||
"foo.bar=ramdom",
|
||||
"backend.wei=42",
|
||||
},
|
||||
key: "backend.weight",
|
||||
defaultValue: "0",
|
||||
expected: "0",
|
||||
},
|
||||
{
|
||||
desc: "Should return for.bar key value random when empty prefix",
|
||||
tags: []string{
|
||||
"foo.bar=ramdom",
|
||||
"backend.wei=42",
|
||||
},
|
||||
key: "foo.bar",
|
||||
defaultValue: "random",
|
||||
expected: "ramdom",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := &Provider{
|
||||
Domain: "localhost",
|
||||
Prefix: test.prefix,
|
||||
}
|
||||
|
||||
actual := p.getAttribute(test.key, test.tags, test.defaultValue)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderGetFrontendRule(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
service serviceUpdate
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "Should return default host foo.localhost",
|
||||
service: serviceUpdate{
|
||||
ServiceName: "foo",
|
||||
Attributes: []string{},
|
||||
},
|
||||
expected: "Host:foo.localhost",
|
||||
},
|
||||
{
|
||||
desc: "Should return host *.example.com",
|
||||
service: serviceUpdate{
|
||||
ServiceName: "foo",
|
||||
Attributes: []string{
|
||||
"traefik.frontend.rule=Host:*.example.com",
|
||||
},
|
||||
},
|
||||
expected: "Host:*.example.com",
|
||||
},
|
||||
{
|
||||
desc: "Should return host foo.example.com",
|
||||
service: serviceUpdate{
|
||||
ServiceName: "foo",
|
||||
Attributes: []string{
|
||||
"traefik.frontend.rule=Host:{{.ServiceName}}.example.com",
|
||||
},
|
||||
},
|
||||
expected: "Host:foo.example.com",
|
||||
},
|
||||
{
|
||||
desc: "Should return path prefix /bar",
|
||||
service: serviceUpdate{
|
||||
ServiceName: "foo",
|
||||
Attributes: []string{
|
||||
"traefik.frontend.rule=PathPrefix:{{getTag \"contextPath\" .Attributes \"/\"}}",
|
||||
"contextPath=/bar",
|
||||
},
|
||||
},
|
||||
expected: "PathPrefix:/bar",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := &Provider{
|
||||
Domain: "localhost",
|
||||
Prefix: "traefik",
|
||||
FrontEndRule: "Host:{{.ServiceName}}.{{.Domain}}",
|
||||
frontEndRuleTemplate: template.New("consul catalog frontend rule"),
|
||||
}
|
||||
p.setupFrontEndRuleTemplate()
|
||||
|
||||
labels := tagsToNeutralLabels(test.service.Attributes, p.Prefix)
|
||||
test.service.TraefikLabels = labels
|
||||
|
||||
actual := p.getFrontendRule(test.service)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBackendAddress(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
node *api.ServiceEntry
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "Should return the address of the service",
|
||||
node: &api.ServiceEntry{
|
||||
Node: &api.Node{
|
||||
Address: "10.1.0.1",
|
||||
},
|
||||
Service: &api.AgentService{
|
||||
Address: "10.2.0.1",
|
||||
},
|
||||
},
|
||||
expected: "10.2.0.1",
|
||||
},
|
||||
{
|
||||
desc: "Should return the address of the node",
|
||||
node: &api.ServiceEntry{
|
||||
Node: &api.Node{
|
||||
Address: "10.1.0.1",
|
||||
},
|
||||
Service: &api.AgentService{
|
||||
Address: "",
|
||||
},
|
||||
},
|
||||
expected: "10.1.0.1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := getBackendAddress(test.node)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetServerName(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
node *api.ServiceEntry
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "Should create backend name without tags",
|
||||
node: &api.ServiceEntry{
|
||||
Service: &api.AgentService{
|
||||
Service: "api",
|
||||
Address: "10.0.0.1",
|
||||
Port: 80,
|
||||
Tags: []string{},
|
||||
},
|
||||
},
|
||||
expected: "api-0-eUSiqD6uNvvh6zxsY-OeRi8ZbaE",
|
||||
},
|
||||
{
|
||||
desc: "Should create backend name with multiple tags",
|
||||
node: &api.ServiceEntry{
|
||||
Service: &api.AgentService{
|
||||
Service: "api",
|
||||
Address: "10.0.0.1",
|
||||
Port: 80,
|
||||
Tags: []string{"traefik.weight=42", "traefik.enable=true"},
|
||||
},
|
||||
},
|
||||
expected: "api-1-eJ8MR2JxjXyZgs1bhurVa0-9OI8",
|
||||
},
|
||||
{
|
||||
desc: "Should create backend name with one tag",
|
||||
node: &api.ServiceEntry{
|
||||
Service: &api.AgentService{
|
||||
Service: "api",
|
||||
Address: "10.0.0.1",
|
||||
Port: 80,
|
||||
Tags: []string{"a funny looking tag"},
|
||||
},
|
||||
},
|
||||
expected: "api-2-lMCDCsG7sh0SCXOHo4oBOQB-9D4",
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range testCases {
|
||||
test := test
|
||||
i := i
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := getServerName(test.node, i)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func fakeLoadTraefikLabelsSlice(nodes []catalogUpdate, prefix string) []catalogUpdate {
|
||||
var result []catalogUpdate
|
||||
|
||||
for _, node := range nodes {
|
||||
labels := tagsToNeutralLabels(node.Service.Attributes, prefix)
|
||||
node.Service.TraefikLabels = labels
|
||||
result = append(result, node)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
|
@ -2,6 +2,7 @@ package consulcatalog
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
|
@ -50,6 +51,7 @@ type Service struct {
|
|||
type serviceUpdate struct {
|
||||
ServiceName string
|
||||
Attributes []string
|
||||
TraefikLabels map[string]string
|
||||
}
|
||||
|
||||
type catalogUpdate struct {
|
||||
|
@ -446,10 +448,13 @@ func (p *Provider) healthyNodes(service string) (catalogUpdate, error) {
|
|||
).(map[string]bool)).([]string)
|
||||
}, []string{}, nodes).([]string)
|
||||
|
||||
labels := tagsToNeutralLabels(tags, p.Prefix)
|
||||
|
||||
return catalogUpdate{
|
||||
Service: &serviceUpdate{
|
||||
ServiceName: service,
|
||||
Attributes: tags,
|
||||
TraefikLabels: labels,
|
||||
},
|
||||
Nodes: nodes,
|
||||
}, nil
|
||||
|
@ -473,7 +478,18 @@ func (p *Provider) nodeFilter(service string, node *api.ServiceEntry) bool {
|
|||
}
|
||||
|
||||
func (p *Provider) isServiceEnabled(node *api.ServiceEntry) bool {
|
||||
return p.getBoolAttribute(label.SuffixEnable, node.Service.Tags, p.ExposedByDefault)
|
||||
rawValue := getTag(p.getPrefixedName(label.SuffixEnable), node.Service.Tags, "")
|
||||
|
||||
if len(rawValue) == 0 {
|
||||
return p.ExposedByDefault
|
||||
}
|
||||
|
||||
value, err := strconv.ParseBool(rawValue)
|
||||
if err != nil {
|
||||
log.Errorf("Invalid value for %s: %s", label.SuffixEnable, rawValue)
|
||||
return p.ExposedByDefault
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (p *Provider) getConstraintTags(tags []string) []string {
|
||||
|
|
|
@ -1,589 +0,0 @@
|
|||
package consulcatalog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/provider"
|
||||
"github.com/containous/traefik/provider/label"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/hashicorp/consul/api"
|
||||
)
|
||||
|
||||
func (p *Provider) buildConfiguration(catalog []catalogUpdate) *types.Configuration {
|
||||
var FuncMap = template.FuncMap{
|
||||
"getAttribute": p.getAttribute,
|
||||
"getTag": getTag,
|
||||
"hasTag": hasTag,
|
||||
|
||||
// Backend functions
|
||||
"getBackend": getNodeBackendName, // TODO Deprecated [breaking] getBackend -> getNodeBackendName
|
||||
"getNodeBackendName": getNodeBackendName,
|
||||
"getServiceBackendName": getServiceBackendName,
|
||||
"getBackendAddress": getBackendAddress,
|
||||
"getBackendName": getServerName, // TODO Deprecated [breaking] getBackendName -> getServerName
|
||||
"getServerName": getServerName,
|
||||
"hasMaxconnAttributes": p.hasMaxConnAttributes, // TODO Deprecated [breaking]
|
||||
"getSticky": p.getSticky, // TODO Deprecated [breaking]
|
||||
"hasStickinessLabel": p.hasStickinessLabel, // TODO Deprecated [breaking]
|
||||
"getStickinessCookieName": p.getStickinessCookieName, // TODO Deprecated [breaking]
|
||||
"getWeight": p.getWeight, // TODO Deprecated [breaking] Must replaced by a simple: "getWeight": p.getFuncIntAttribute(label.SuffixWeight, 0)
|
||||
"getProtocol": p.getFuncStringAttribute(label.SuffixProtocol, label.DefaultProtocol),
|
||||
"getCircuitBreaker": p.getCircuitBreaker,
|
||||
"getLoadBalancer": p.getLoadBalancer,
|
||||
"getMaxConn": p.getMaxConn,
|
||||
"getHealthCheck": p.getHealthCheck,
|
||||
"getBuffering": p.getBuffering,
|
||||
|
||||
// Frontend functions
|
||||
"getFrontendRule": p.getFrontendRule,
|
||||
"getBasicAuth": p.getFuncSliceAttribute(label.SuffixFrontendAuthBasic),
|
||||
"getEntryPoints": getEntryPoints, // TODO Deprecated [breaking]
|
||||
"getFrontEndEntryPoints": p.getFuncSliceAttribute(label.SuffixFrontendEntryPoints), // TODO [breaking] rename to getEntryPoints when getEntryPoints will be removed
|
||||
"getPriority": p.getFuncIntAttribute(label.SuffixFrontendPriority, label.DefaultFrontendPriorityInt),
|
||||
"getPassHostHeader": p.getFuncBoolAttribute(label.SuffixFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
|
||||
"getPassTLSCert": p.getFuncBoolAttribute(label.SuffixFrontendPassTLSCert, label.DefaultPassTLSCert),
|
||||
"getWhiteList": p.getWhiteList,
|
||||
"getRedirect": p.getRedirect,
|
||||
"hasErrorPages": p.getFuncHasAttributePrefix(label.BaseFrontendErrorPage),
|
||||
"getErrorPages": p.getErrorPages,
|
||||
"hasRateLimit": p.getFuncHasAttributePrefix(label.BaseFrontendRateLimit),
|
||||
"getRateLimit": p.getRateLimit,
|
||||
"getHeaders": p.getHeaders,
|
||||
}
|
||||
|
||||
var allNodes []*api.ServiceEntry
|
||||
var services []*serviceUpdate
|
||||
for _, info := range catalog {
|
||||
if len(info.Nodes) > 0 {
|
||||
services = append(services, info.Service)
|
||||
allNodes = append(allNodes, info.Nodes...)
|
||||
}
|
||||
}
|
||||
// Ensure a stable ordering of nodes so that identical configurations may be detected
|
||||
sort.Sort(nodeSorter(allNodes))
|
||||
|
||||
templateObjects := struct {
|
||||
Services []*serviceUpdate
|
||||
Nodes []*api.ServiceEntry
|
||||
}{
|
||||
Services: services,
|
||||
Nodes: allNodes,
|
||||
}
|
||||
|
||||
configuration, err := p.GetConfiguration("templates/consul_catalog.tmpl", FuncMap, templateObjects)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create config")
|
||||
}
|
||||
|
||||
return configuration
|
||||
}
|
||||
|
||||
func (p *Provider) setupFrontEndRuleTemplate() {
|
||||
var FuncMap = template.FuncMap{
|
||||
"getAttribute": p.getAttribute,
|
||||
"getTag": getTag,
|
||||
"hasTag": hasTag,
|
||||
}
|
||||
tmpl := template.New("consul catalog frontend rule").Funcs(FuncMap)
|
||||
p.frontEndRuleTemplate = tmpl
|
||||
}
|
||||
|
||||
// Specific functions
|
||||
|
||||
func (p *Provider) getFrontendRule(service serviceUpdate) string {
|
||||
customFrontendRule := p.getAttribute(label.SuffixFrontendRule, service.Attributes, "")
|
||||
if customFrontendRule == "" {
|
||||
customFrontendRule = p.FrontEndRule
|
||||
}
|
||||
|
||||
tmpl := p.frontEndRuleTemplate
|
||||
tmpl, err := tmpl.Parse(customFrontendRule)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to parse Consul Catalog custom frontend rule: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
templateObjects := struct {
|
||||
ServiceName string
|
||||
Domain string
|
||||
Attributes []string
|
||||
}{
|
||||
ServiceName: service.ServiceName,
|
||||
Domain: p.Domain,
|
||||
Attributes: service.Attributes,
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
err = tmpl.Execute(&buffer, templateObjects)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to execute Consul Catalog custom frontend rule template: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) hasMaxConnAttributes(attributes []string) bool {
|
||||
amount := p.getAttribute(label.SuffixBackendMaxConnAmount, attributes, "")
|
||||
extractorFunc := p.getAttribute(label.SuffixBackendMaxConnExtractorFunc, attributes, "")
|
||||
return amount != "" && extractorFunc != ""
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func getEntryPoints(list string) []string {
|
||||
return strings.Split(list, ",")
|
||||
}
|
||||
|
||||
func getNodeBackendName(node *api.ServiceEntry) string {
|
||||
return strings.ToLower(node.Service.Service)
|
||||
}
|
||||
|
||||
func getServiceBackendName(service *serviceUpdate) string {
|
||||
return strings.ToLower(service.ServiceName)
|
||||
}
|
||||
|
||||
func getBackendAddress(node *api.ServiceEntry) string {
|
||||
if node.Service.Address != "" {
|
||||
return node.Service.Address
|
||||
}
|
||||
return node.Node.Address
|
||||
}
|
||||
|
||||
func getServerName(node *api.ServiceEntry, index int) string {
|
||||
serviceName := node.Service.Service + node.Service.Address + strconv.Itoa(node.Service.Port)
|
||||
// TODO sort tags ?
|
||||
serviceName += strings.Join(node.Service.Tags, "")
|
||||
|
||||
hash := sha1.New()
|
||||
_, err := hash.Write([]byte(serviceName))
|
||||
if err != nil {
|
||||
// Impossible case
|
||||
log.Error(err)
|
||||
} else {
|
||||
serviceName = base64.URLEncoding.EncodeToString(hash.Sum(nil))
|
||||
}
|
||||
|
||||
// unique int at the end
|
||||
return provider.Normalize(node.Service.Service + "-" + strconv.Itoa(index) + "-" + serviceName)
|
||||
}
|
||||
|
||||
// TODO: Deprecated
|
||||
// replaced by Stickiness
|
||||
// Deprecated
|
||||
func (p *Provider) getSticky(tags []string) string {
|
||||
stickyTag := p.getAttribute(label.SuffixBackendLoadBalancerSticky, tags, "")
|
||||
if len(stickyTag) > 0 {
|
||||
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
|
||||
} else {
|
||||
stickyTag = "false"
|
||||
}
|
||||
return stickyTag
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) hasStickinessLabel(tags []string) bool {
|
||||
stickinessTag := p.getAttribute(label.SuffixBackendLoadBalancerStickiness, tags, "")
|
||||
return len(stickinessTag) > 0 && strings.EqualFold(strings.TrimSpace(stickinessTag), "true")
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) getStickinessCookieName(tags []string) string {
|
||||
return p.getAttribute(label.SuffixBackendLoadBalancerStickinessCookieName, tags, "")
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) getWeight(tags []string) int {
|
||||
weight := p.getIntAttribute(label.SuffixWeight, tags, label.DefaultWeightInt)
|
||||
|
||||
// Deprecated
|
||||
deprecatedWeightTag := "backend." + label.SuffixWeight
|
||||
if p.hasAttribute(deprecatedWeightTag, tags) {
|
||||
log.Warnf("Deprecated configuration found: %s. Please use %s.",
|
||||
p.getPrefixedName(deprecatedWeightTag), p.getPrefixedName(label.SuffixWeight))
|
||||
|
||||
weight = p.getIntAttribute(deprecatedWeightTag, tags, label.DefaultWeightInt)
|
||||
}
|
||||
|
||||
return weight
|
||||
}
|
||||
|
||||
func (p *Provider) getCircuitBreaker(tags []string) *types.CircuitBreaker {
|
||||
circuitBreaker := p.getAttribute(label.SuffixBackendCircuitBreakerExpression, tags, "")
|
||||
|
||||
if p.hasAttribute(label.SuffixBackendCircuitBreaker, tags) {
|
||||
log.Warnf("Deprecated configuration found: %s. Please use %s.",
|
||||
p.getPrefixedName(label.SuffixBackendCircuitBreaker), p.getPrefixedName(label.SuffixBackendCircuitBreakerExpression))
|
||||
|
||||
circuitBreaker = p.getAttribute(label.SuffixBackendCircuitBreaker, tags, "")
|
||||
}
|
||||
|
||||
if len(circuitBreaker) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &types.CircuitBreaker{Expression: circuitBreaker}
|
||||
}
|
||||
|
||||
func (p *Provider) getLoadBalancer(tags []string) *types.LoadBalancer {
|
||||
rawSticky := p.getSticky(tags)
|
||||
sticky, err := strconv.ParseBool(rawSticky)
|
||||
if err != nil {
|
||||
log.Debugf("Invalid sticky value: %s", rawSticky)
|
||||
sticky = false
|
||||
}
|
||||
|
||||
method := p.getAttribute(label.SuffixBackendLoadBalancerMethod, tags, label.DefaultBackendLoadBalancerMethod)
|
||||
|
||||
// Deprecated
|
||||
deprecatedMethodTag := "backend.loadbalancer"
|
||||
if p.hasAttribute(deprecatedMethodTag, tags) {
|
||||
log.Warnf("Deprecated configuration found: %s. Please use %s.",
|
||||
p.getPrefixedName(deprecatedMethodTag), p.getPrefixedName(label.SuffixWeight))
|
||||
|
||||
method = p.getAttribute(deprecatedMethodTag, tags, label.SuffixBackendLoadBalancerMethod)
|
||||
}
|
||||
|
||||
lb := &types.LoadBalancer{
|
||||
Method: method,
|
||||
Sticky: sticky,
|
||||
}
|
||||
|
||||
if p.getBoolAttribute(label.SuffixBackendLoadBalancerStickiness, tags, false) {
|
||||
lb.Stickiness = &types.Stickiness{
|
||||
CookieName: p.getAttribute(label.SuffixBackendLoadBalancerStickinessCookieName, tags, ""),
|
||||
}
|
||||
}
|
||||
|
||||
return lb
|
||||
}
|
||||
|
||||
func (p *Provider) getMaxConn(tags []string) *types.MaxConn {
|
||||
amount := p.getInt64Attribute(label.SuffixBackendMaxConnAmount, tags, math.MinInt64)
|
||||
extractorFunc := p.getAttribute(label.SuffixBackendMaxConnExtractorFunc, tags, label.DefaultBackendMaxconnExtractorFunc)
|
||||
|
||||
if amount == math.MinInt64 || len(extractorFunc) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &types.MaxConn{
|
||||
Amount: amount,
|
||||
ExtractorFunc: extractorFunc,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) getHealthCheck(tags []string) *types.HealthCheck {
|
||||
path := p.getAttribute(label.SuffixBackendHealthCheckPath, tags, "")
|
||||
|
||||
if len(path) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
port := p.getIntAttribute(label.SuffixBackendHealthCheckPort, tags, label.DefaultBackendHealthCheckPort)
|
||||
interval := p.getAttribute(label.SuffixBackendHealthCheckInterval, tags, "")
|
||||
|
||||
return &types.HealthCheck{
|
||||
Path: path,
|
||||
Port: port,
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) getBuffering(tags []string) *types.Buffering {
|
||||
if !p.hasAttributePrefix(label.SuffixBackendBuffering, tags) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &types.Buffering{
|
||||
MaxRequestBodyBytes: p.getInt64Attribute(label.SuffixBackendBufferingMaxRequestBodyBytes, tags, 0),
|
||||
MaxResponseBodyBytes: p.getInt64Attribute(label.SuffixBackendBufferingMaxResponseBodyBytes, tags, 0),
|
||||
MemRequestBodyBytes: p.getInt64Attribute(label.SuffixBackendBufferingMemRequestBodyBytes, tags, 0),
|
||||
MemResponseBodyBytes: p.getInt64Attribute(label.SuffixBackendBufferingMemResponseBodyBytes, tags, 0),
|
||||
RetryExpression: p.getAttribute(label.SuffixBackendBufferingRetryExpression, tags, ""),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) getWhiteList(tags []string) *types.WhiteList {
|
||||
ranges := p.getSliceAttribute(label.SuffixFrontendWhiteListSourceRange, tags)
|
||||
|
||||
if len(ranges) > 0 {
|
||||
return &types.WhiteList{
|
||||
SourceRange: ranges,
|
||||
UseXForwardedFor: p.getBoolAttribute(label.SuffixFrontendWhiteListUseXForwardedFor, tags, false),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) getRedirect(tags []string) *types.Redirect {
|
||||
permanent := p.getBoolAttribute(label.SuffixFrontendRedirectPermanent, tags, false)
|
||||
|
||||
if p.hasAttribute(label.SuffixFrontendRedirectEntryPoint, tags) {
|
||||
return &types.Redirect{
|
||||
EntryPoint: p.getAttribute(label.SuffixFrontendRedirectEntryPoint, tags, ""),
|
||||
Permanent: permanent,
|
||||
}
|
||||
}
|
||||
|
||||
if p.hasAttribute(label.SuffixFrontendRedirectRegex, tags) && p.hasAttribute(label.SuffixFrontendRedirectReplacement, tags) {
|
||||
return &types.Redirect{
|
||||
Regex: p.getAttribute(label.SuffixFrontendRedirectRegex, tags, ""),
|
||||
Replacement: p.getAttribute(label.SuffixFrontendRedirectReplacement, tags, ""),
|
||||
Permanent: permanent,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) 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 *Provider) 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,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) getHeaders(tags []string) *types.Headers {
|
||||
headers := &types.Headers{
|
||||
CustomRequestHeaders: p.getMapAttribute(label.SuffixFrontendRequestHeaders, tags),
|
||||
CustomResponseHeaders: p.getMapAttribute(label.SuffixFrontendResponseHeaders, tags),
|
||||
SSLProxyHeaders: p.getMapAttribute(label.SuffixFrontendHeadersSSLProxyHeaders, tags),
|
||||
AllowedHosts: p.getSliceAttribute(label.SuffixFrontendHeadersAllowedHosts, tags),
|
||||
HostsProxyHeaders: p.getSliceAttribute(label.SuffixFrontendHeadersHostsProxyHeaders, tags),
|
||||
SSLHost: p.getAttribute(label.SuffixFrontendHeadersSSLHost, tags, ""),
|
||||
CustomFrameOptionsValue: p.getAttribute(label.SuffixFrontendHeadersCustomFrameOptionsValue, tags, ""),
|
||||
ContentSecurityPolicy: p.getAttribute(label.SuffixFrontendHeadersContentSecurityPolicy, tags, ""),
|
||||
PublicKey: p.getAttribute(label.SuffixFrontendHeadersPublicKey, tags, ""),
|
||||
ReferrerPolicy: p.getAttribute(label.SuffixFrontendHeadersReferrerPolicy, tags, ""),
|
||||
CustomBrowserXSSValue: p.getAttribute(label.SuffixFrontendHeadersCustomBrowserXSSValue, tags, ""),
|
||||
STSSeconds: p.getInt64Attribute(label.SuffixFrontendHeadersSTSSeconds, tags, 0),
|
||||
SSLRedirect: p.getBoolAttribute(label.SuffixFrontendHeadersSSLRedirect, tags, false),
|
||||
SSLTemporaryRedirect: p.getBoolAttribute(label.SuffixFrontendHeadersSSLTemporaryRedirect, tags, false),
|
||||
STSIncludeSubdomains: p.getBoolAttribute(label.SuffixFrontendHeadersSTSIncludeSubdomains, tags, false),
|
||||
STSPreload: p.getBoolAttribute(label.SuffixFrontendHeadersSTSPreload, tags, false),
|
||||
ForceSTSHeader: p.getBoolAttribute(label.SuffixFrontendHeadersForceSTSHeader, tags, false),
|
||||
FrameDeny: p.getBoolAttribute(label.SuffixFrontendHeadersFrameDeny, tags, false),
|
||||
ContentTypeNosniff: p.getBoolAttribute(label.SuffixFrontendHeadersContentTypeNosniff, tags, false),
|
||||
BrowserXSSFilter: p.getBoolAttribute(label.SuffixFrontendHeadersBrowserXSSFilter, tags, false),
|
||||
IsDevelopment: p.getBoolAttribute(label.SuffixFrontendHeadersIsDevelopment, tags, false),
|
||||
}
|
||||
|
||||
if !headers.HasSecureHeadersDefined() && !headers.HasCustomHeadersDefined() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
// Base functions
|
||||
|
||||
func (p *Provider) 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 *Provider) getFuncStringAttribute(name string, defaultValue string) func(tags []string) string {
|
||||
return func(tags []string) string {
|
||||
return p.getAttribute(name, tags, defaultValue)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) getFuncSliceAttribute(name string) func(tags []string) []string {
|
||||
return func(tags []string) []string {
|
||||
return p.getSliceAttribute(name, tags)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) getMapAttribute(name string, tags []string) map[string]string {
|
||||
rawValue := getTag(p.getPrefixedName(name), tags, "")
|
||||
|
||||
if len(rawValue) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return label.ParseMapValue(p.getPrefixedName(name), rawValue)
|
||||
}
|
||||
|
||||
func (p *Provider) getFuncIntAttribute(name string, defaultValue int) func(tags []string) int {
|
||||
return func(tags []string) int {
|
||||
return p.getIntAttribute(name, tags, defaultValue)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) getFuncBoolAttribute(name string, defaultValue bool) func(tags []string) bool {
|
||||
return func(tags []string) bool {
|
||||
return p.getBoolAttribute(name, tags, defaultValue)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) getFuncHasAttributePrefix(name string) func(tags []string) bool {
|
||||
return func(tags []string) bool {
|
||||
return p.hasAttributePrefix(name, tags)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) getInt64Attribute(name string, tags []string, defaultValue int64) int64 {
|
||||
rawValue := getTag(p.getPrefixedName(name), tags, "")
|
||||
|
||||
if len(rawValue) == 0 {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
value, err := strconv.ParseInt(rawValue, 10, 64)
|
||||
if err != nil {
|
||||
log.Errorf("Invalid value for %s: %s", name, rawValue)
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (p *Provider) getIntAttribute(name string, tags []string, defaultValue int) int {
|
||||
rawValue := getTag(p.getPrefixedName(name), tags, "")
|
||||
|
||||
if len(rawValue) == 0 {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
value, err := strconv.Atoi(rawValue)
|
||||
if err != nil {
|
||||
log.Errorf("Invalid value for %s: %s", name, rawValue)
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (p *Provider) getSliceAttribute(name string, tags []string) []string {
|
||||
rawValue := getTag(p.getPrefixedName(name), tags, "")
|
||||
|
||||
if len(rawValue) == 0 {
|
||||
return nil
|
||||
}
|
||||
return label.SplitAndTrimString(rawValue, ",")
|
||||
}
|
||||
|
||||
func (p *Provider) getBoolAttribute(name string, tags []string, defaultValue bool) bool {
|
||||
rawValue := getTag(p.getPrefixedName(name), tags, "")
|
||||
|
||||
if len(rawValue) == 0 {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
value, err := strconv.ParseBool(rawValue)
|
||||
if err != nil {
|
||||
log.Errorf("Invalid value for %s: %s", name, rawValue)
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (p *Provider) hasAttribute(name string, tags []string) bool {
|
||||
return hasTag(p.getPrefixedName(name), tags)
|
||||
}
|
||||
|
||||
func (p *Provider) hasAttributePrefix(name string, tags []string) bool {
|
||||
return hasTagPrefix(p.getPrefixedName(name), tags)
|
||||
}
|
||||
|
||||
func (p *Provider) getAttribute(name string, tags []string, defaultValue string) string {
|
||||
return getTag(p.getPrefixedName(name), tags, defaultValue)
|
||||
}
|
||||
|
||||
func (p *Provider) getPrefixedName(name string) string {
|
||||
if len(p.Prefix) > 0 && len(name) > 0 {
|
||||
return p.Prefix + "." + name
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func hasTag(name string, tags []string) bool {
|
||||
lowerName := strings.ToLower(name)
|
||||
|
||||
for _, tag := range tags {
|
||||
lowerTag := strings.ToLower(tag)
|
||||
|
||||
// Given the nature of Consul tags, which could be either singular markers, or key=value pairs
|
||||
if strings.HasPrefix(lowerTag, lowerName+"=") || lowerTag == lowerName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
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)
|
||||
|
||||
for _, tag := range tags {
|
||||
lowerTag := strings.ToLower(tag)
|
||||
|
||||
// Given the nature of Consul tags, which could be either singular markers, or key=value pairs
|
||||
if strings.HasPrefix(lowerTag, lowerName+"=") || lowerTag == lowerName {
|
||||
// In case, where a tag might be a key=value, try to split it by the first '='
|
||||
kv := strings.SplitN(tag, "=", 2)
|
||||
|
||||
// If the returned result is a key=value pair, return the 'value' component
|
||||
if len(kv) == 2 {
|
||||
return kv[1]
|
||||
}
|
||||
// If the returned result is a singular marker, return the 'key' component
|
||||
return kv[0]
|
||||
}
|
||||
|
||||
}
|
||||
return defaultValue
|
||||
}
|
File diff suppressed because it is too large
Load diff
29
provider/consulcatalog/convert_types.go
Normal file
29
provider/consulcatalog/convert_types.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package consulcatalog
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/provider/label"
|
||||
)
|
||||
|
||||
func tagsToNeutralLabels(tags []string, prefix string) map[string]string {
|
||||
var labels map[string]string
|
||||
|
||||
for _, tag := range tags {
|
||||
if strings.HasPrefix(tag, 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], prefix+".")
|
||||
labels[key] = parts[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return labels
|
||||
}
|
64
provider/consulcatalog/convert_types_test.go
Normal file
64
provider/consulcatalog/convert_types_test.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package consulcatalog
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTagsToNeutralLabels(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
tags []string
|
||||
prefix string
|
||||
expected map[string]string
|
||||
}{
|
||||
{
|
||||
desc: "without tags",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
desc: "with a prefix",
|
||||
prefix: "test",
|
||||
tags: []string{
|
||||
"test.aaa=01",
|
||||
"test.bbb=02",
|
||||
"ccc=03",
|
||||
"test.ddd=04=to",
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.aaa": "01",
|
||||
"traefik.bbb": "02",
|
||||
"traefik.ddd": "04=to",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
desc: "with an empty prefix",
|
||||
prefix: "",
|
||||
tags: []string{
|
||||
"test.aaa=01",
|
||||
"test.bbb=02",
|
||||
"ccc=03",
|
||||
"test.ddd=04=to",
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.test.aaa": "01",
|
||||
"traefik.test.bbb": "02",
|
||||
"traefik.ccc": "03",
|
||||
"traefik.test.ddd": "04=to",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
labels := tagsToNeutralLabels(test.tags, test.prefix)
|
||||
|
||||
assert.Equal(t, test.expected, labels)
|
||||
})
|
||||
}
|
||||
}
|
250
provider/consulcatalog/deprecated_config.go
Normal file
250
provider/consulcatalog/deprecated_config.go
Normal file
|
@ -0,0 +1,250 @@
|
|||
package consulcatalog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/provider/label"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/hashicorp/consul/api"
|
||||
)
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) buildConfigurationV1(catalog []catalogUpdate) *types.Configuration {
|
||||
var FuncMap = template.FuncMap{
|
||||
"getAttribute": p.getAttribute,
|
||||
"getTag": getTag,
|
||||
"hasTag": hasTag,
|
||||
|
||||
// Backend functions
|
||||
"getBackend": getNodeBackendName,
|
||||
"getServiceBackendName": getServiceBackendName,
|
||||
"getBackendAddress": getBackendAddress,
|
||||
"getBackendName": getServerName,
|
||||
"hasMaxconnAttributes": p.hasMaxConnAttributesV1,
|
||||
"getSticky": p.getStickyV1,
|
||||
"hasStickinessLabel": p.hasStickinessLabelV1,
|
||||
"getStickinessCookieName": p.getStickinessCookieNameV1,
|
||||
"getWeight": p.getWeight,
|
||||
"getProtocol": p.getFuncStringAttribute(label.SuffixProtocol, label.DefaultProtocol),
|
||||
|
||||
// Frontend functions
|
||||
"getFrontendRule": p.getFrontendRuleV1,
|
||||
"getBasicAuth": p.getFuncSliceAttribute(label.SuffixFrontendAuthBasic),
|
||||
"getEntryPoints": getEntryPointsV1,
|
||||
"getPriority": p.getFuncIntAttribute(label.SuffixFrontendPriority, label.DefaultFrontendPriorityInt),
|
||||
"getPassHostHeader": p.getFuncBoolAttribute(label.SuffixFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
|
||||
"getPassTLSCert": p.getFuncBoolAttribute(label.SuffixFrontendPassTLSCert, label.DefaultPassTLSCert),
|
||||
}
|
||||
|
||||
var allNodes []*api.ServiceEntry
|
||||
var services []*serviceUpdate
|
||||
for _, info := range catalog {
|
||||
if len(info.Nodes) > 0 {
|
||||
services = append(services, info.Service)
|
||||
allNodes = append(allNodes, info.Nodes...)
|
||||
}
|
||||
}
|
||||
// Ensure a stable ordering of nodes so that identical configurations may be detected
|
||||
sort.Sort(nodeSorter(allNodes))
|
||||
|
||||
templateObjects := struct {
|
||||
Services []*serviceUpdate
|
||||
Nodes []*api.ServiceEntry
|
||||
}{
|
||||
Services: services,
|
||||
Nodes: allNodes,
|
||||
}
|
||||
|
||||
configuration, err := p.GetConfiguration("templates/consul_catalog-v1.tmpl", FuncMap, templateObjects)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create config")
|
||||
}
|
||||
|
||||
return configuration
|
||||
}
|
||||
|
||||
// Specific functions
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) getFrontendRuleV1(service serviceUpdate) string {
|
||||
customFrontendRule := p.getAttribute(label.SuffixFrontendRule, service.Attributes, "")
|
||||
if customFrontendRule == "" {
|
||||
customFrontendRule = p.FrontEndRule
|
||||
}
|
||||
|
||||
tmpl := p.frontEndRuleTemplate
|
||||
tmpl, err := tmpl.Parse(customFrontendRule)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to parse Consul Catalog custom frontend rule: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
templateObjects := struct {
|
||||
ServiceName string
|
||||
Domain string
|
||||
Attributes []string
|
||||
}{
|
||||
ServiceName: service.ServiceName,
|
||||
Domain: p.Domain,
|
||||
Attributes: service.Attributes,
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
err = tmpl.Execute(&buffer, templateObjects)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to execute Consul Catalog custom frontend rule template: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) hasMaxConnAttributesV1(attributes []string) bool {
|
||||
amount := p.getAttribute(label.SuffixBackendMaxConnAmount, attributes, "")
|
||||
extractorFunc := p.getAttribute(label.SuffixBackendMaxConnExtractorFunc, attributes, "")
|
||||
return amount != "" && extractorFunc != ""
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func getEntryPointsV1(list string) []string {
|
||||
return strings.Split(list, ",")
|
||||
}
|
||||
|
||||
// TODO: Deprecated
|
||||
// replaced by Stickiness
|
||||
// Deprecated
|
||||
func (p *Provider) getStickyV1(tags []string) string {
|
||||
stickyTag := p.getAttribute(label.SuffixBackendLoadBalancerSticky, tags, "")
|
||||
if len(stickyTag) > 0 {
|
||||
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
|
||||
} else {
|
||||
stickyTag = "false"
|
||||
}
|
||||
return stickyTag
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) hasStickinessLabelV1(tags []string) bool {
|
||||
stickinessTag := p.getAttribute(label.SuffixBackendLoadBalancerStickiness, tags, "")
|
||||
return len(stickinessTag) > 0 && strings.EqualFold(strings.TrimSpace(stickinessTag), "true")
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) getStickinessCookieNameV1(tags []string) string {
|
||||
return p.getAttribute(label.SuffixBackendLoadBalancerStickinessCookieName, tags, "")
|
||||
}
|
||||
|
||||
// Base functions
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) getFuncStringAttribute(name string, defaultValue string) func(tags []string) string {
|
||||
return func(tags []string) string {
|
||||
return p.getAttribute(name, tags, defaultValue)
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) getFuncSliceAttribute(name string) func(tags []string) []string {
|
||||
return func(tags []string) []string {
|
||||
return p.getSliceAttribute(name, tags)
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) getMapAttribute(name string, tags []string) map[string]string {
|
||||
rawValue := getTag(p.getPrefixedName(name), tags, "")
|
||||
|
||||
if len(rawValue) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return label.ParseMapValue(p.getPrefixedName(name), rawValue)
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) getFuncIntAttribute(name string, defaultValue int) func(tags []string) int {
|
||||
return func(tags []string) int {
|
||||
return p.getIntAttribute(name, tags, defaultValue)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) getFuncBoolAttribute(name string, defaultValue bool) func(tags []string) bool {
|
||||
return func(tags []string) bool {
|
||||
return p.getBoolAttribute(name, tags, defaultValue)
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) getFuncHasAttributePrefix(name string) func(tags []string) bool {
|
||||
return func(tags []string) bool {
|
||||
return p.hasAttributePrefix(name, tags)
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) getInt64Attribute(name string, tags []string, defaultValue int64) int64 {
|
||||
rawValue := getTag(p.getPrefixedName(name), tags, "")
|
||||
|
||||
if len(rawValue) == 0 {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
value, err := strconv.ParseInt(rawValue, 10, 64)
|
||||
if err != nil {
|
||||
log.Errorf("Invalid value for %s: %s", name, rawValue)
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) getIntAttribute(name string, tags []string, defaultValue int) int {
|
||||
rawValue := getTag(p.getPrefixedName(name), tags, "")
|
||||
|
||||
if len(rawValue) == 0 {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
value, err := strconv.Atoi(rawValue)
|
||||
if err != nil {
|
||||
log.Errorf("Invalid value for %s: %s", name, rawValue)
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) getSliceAttribute(name string, tags []string) []string {
|
||||
rawValue := getTag(p.getPrefixedName(name), tags, "")
|
||||
|
||||
if len(rawValue) == 0 {
|
||||
return nil
|
||||
}
|
||||
return label.SplitAndTrimString(rawValue, ",")
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) getBoolAttribute(name string, tags []string, defaultValue bool) bool {
|
||||
rawValue := getTag(p.getPrefixedName(name), tags, "")
|
||||
|
||||
if len(rawValue) == 0 {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
value, err := strconv.ParseBool(rawValue)
|
||||
if err != nil {
|
||||
log.Errorf("Invalid value for %s: %s", name, rawValue)
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (p *Provider) hasAttributePrefix(name string, tags []string) bool {
|
||||
return hasTagPrefix(p.getPrefixedName(name), tags)
|
||||
}
|
444
provider/consulcatalog/deprecated_config_test.go
Normal file
444
provider/consulcatalog/deprecated_config_test.go
Normal file
|
@ -0,0 +1,444 @@
|
|||
package consulcatalog
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
"github.com/containous/traefik/provider/label"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestProviderBuildConfigurationV1(t *testing.T) {
|
||||
p := &Provider{
|
||||
Domain: "localhost",
|
||||
Prefix: "traefik",
|
||||
ExposedByDefault: false,
|
||||
FrontEndRule: "Host:{{.ServiceName}}.{{.Domain}}",
|
||||
frontEndRuleTemplate: template.New("consul catalog frontend rule"),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
nodes []catalogUpdate
|
||||
expectedFrontends map[string]*types.Frontend
|
||||
expectedBackends map[string]*types.Backend
|
||||
}{
|
||||
{
|
||||
desc: "Should build config of nothing",
|
||||
nodes: []catalogUpdate{},
|
||||
expectedFrontends: map[string]*types.Frontend{},
|
||||
expectedBackends: map[string]*types.Backend{},
|
||||
},
|
||||
{
|
||||
desc: "Should build config with no frontend and backend",
|
||||
nodes: []catalogUpdate{
|
||||
{
|
||||
Service: &serviceUpdate{
|
||||
ServiceName: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{},
|
||||
expectedBackends: map[string]*types.Backend{},
|
||||
},
|
||||
{
|
||||
desc: "Should build config who contains one frontend and one backend",
|
||||
nodes: []catalogUpdate{
|
||||
{
|
||||
Service: &serviceUpdate{
|
||||
ServiceName: "test",
|
||||
Attributes: []string{
|
||||
"random.foo=bar",
|
||||
label.TraefikBackendLoadBalancer + "=drr",
|
||||
label.TraefikBackendCircuitBreaker + "=NetworkErrorRatio() > 0.5",
|
||||
label.TraefikBackendMaxConnAmount + "=1000",
|
||||
label.TraefikBackendMaxConnExtractorFunc + "=client.ip",
|
||||
label.TraefikFrontendAuthBasic + "=test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
},
|
||||
},
|
||||
Nodes: []*api.ServiceEntry{
|
||||
{
|
||||
Service: &api.AgentService{
|
||||
Service: "test",
|
||||
Address: "127.0.0.1",
|
||||
Port: 80,
|
||||
Tags: []string{
|
||||
"random.foo=bar",
|
||||
label.Prefix + "backend.weight=42",
|
||||
label.TraefikFrontendPassHostHeader + "=true",
|
||||
label.TraefikProtocol + "=https",
|
||||
},
|
||||
},
|
||||
Node: &api.Node{
|
||||
Node: "localhost",
|
||||
Address: "127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{
|
||||
"frontend-test": {
|
||||
Backend: "backend-test",
|
||||
PassHostHeader: true,
|
||||
Routes: map[string]types.Route{
|
||||
"route-host-test": {
|
||||
Rule: "Host:test.localhost",
|
||||
},
|
||||
},
|
||||
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
|
||||
},
|
||||
},
|
||||
expectedBackends: map[string]*types.Backend{
|
||||
"backend-test": {
|
||||
Servers: map[string]types.Server{
|
||||
"test-0-us4-27hAOu2ARV7nNrmv6GoKlcA": {
|
||||
URL: "https://127.0.0.1:80",
|
||||
Weight: 42,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: &types.CircuitBreaker{
|
||||
Expression: "NetworkErrorRatio() > 0.5",
|
||||
},
|
||||
LoadBalancer: &types.LoadBalancer{
|
||||
Method: "drr",
|
||||
},
|
||||
MaxConn: &types.MaxConn{
|
||||
Amount: 1000,
|
||||
ExtractorFunc: "client.ip",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actualConfig := p.buildConfigurationV1(test.nodes)
|
||||
assert.NotNil(t, actualConfig)
|
||||
assert.Equal(t, test.expectedBackends, actualConfig.Backends)
|
||||
assert.Equal(t, test.expectedFrontends, actualConfig.Frontends)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderGetIntAttributeV1(t *testing.T) {
|
||||
p := &Provider{
|
||||
Prefix: "traefik",
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
name string
|
||||
tags []string
|
||||
defaultValue int
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
desc: "should return default value when empty name",
|
||||
name: "",
|
||||
tags: []string{"traefik.foo=10"},
|
||||
defaultValue: 666,
|
||||
expected: 666,
|
||||
},
|
||||
{
|
||||
desc: "should return default value when empty tags",
|
||||
name: "traefik.foo",
|
||||
tags: nil,
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
desc: "should return default value when value is not a int",
|
||||
name: "foo",
|
||||
tags: []string{"traefik.foo=bar"},
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
desc: "should return a value when tag exist",
|
||||
name: "foo",
|
||||
tags: []string{"traefik.foo=10"},
|
||||
expected: 10,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
result := p.getIntAttribute(test.name, test.tags, test.defaultValue)
|
||||
|
||||
assert.Equal(t, test.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderGetInt64AttributeV1(t *testing.T) {
|
||||
p := &Provider{
|
||||
Prefix: "traefik",
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
name string
|
||||
tags []string
|
||||
defaultValue int64
|
||||
expected int64
|
||||
}{
|
||||
{
|
||||
desc: "should return default value when empty name",
|
||||
name: "",
|
||||
tags: []string{"traefik.foo=10"},
|
||||
defaultValue: 666,
|
||||
expected: 666,
|
||||
},
|
||||
{
|
||||
desc: "should return default value when empty tags",
|
||||
name: "traefik.foo",
|
||||
tags: nil,
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
desc: "should return default value when value is not a int",
|
||||
name: "foo",
|
||||
tags: []string{"traefik.foo=bar"},
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
desc: "should return a value when tag exist",
|
||||
name: "foo",
|
||||
tags: []string{"traefik.foo=10"},
|
||||
expected: 10,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
result := p.getInt64Attribute(test.name, test.tags, test.defaultValue)
|
||||
|
||||
assert.Equal(t, test.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderGetBoolAttributeV1(t *testing.T) {
|
||||
p := &Provider{
|
||||
Prefix: "traefik",
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
name string
|
||||
tags []string
|
||||
defaultValue bool
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
desc: "should return default value when empty name",
|
||||
name: "",
|
||||
tags: []string{"traefik.foo=true"},
|
||||
defaultValue: true,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
desc: "should return default value when empty tags",
|
||||
name: "traefik.foo",
|
||||
tags: nil,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
desc: "should return default value when value is not a bool",
|
||||
name: "foo",
|
||||
tags: []string{"traefik.foo=bar"},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
desc: "should return a value when tag exist",
|
||||
name: "foo",
|
||||
tags: []string{"traefik.foo=true"},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
result := p.getBoolAttribute(test.name, test.tags, test.defaultValue)
|
||||
|
||||
assert.Equal(t, test.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderGetSliceAttributeV1(t *testing.T) {
|
||||
p := &Provider{
|
||||
Prefix: "traefik",
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
name string
|
||||
tags []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
desc: "should return nil when empty name",
|
||||
name: "",
|
||||
tags: []string{"traefik.foo=bar,bor,bir"},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
desc: "should return nil when empty tags",
|
||||
name: "foo",
|
||||
tags: nil,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
desc: "should return nil when tag doesn't have value",
|
||||
name: "",
|
||||
tags: []string{"traefik.foo="},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
desc: "should return a slice when tag contains comma separated values",
|
||||
name: "foo",
|
||||
tags: []string{"traefik.foo=bar,bor,bir"},
|
||||
expected: []string{"bar", "bor", "bir"},
|
||||
},
|
||||
{
|
||||
desc: "should return a slice when tag contains one value",
|
||||
name: "foo",
|
||||
tags: []string{"traefik.foo=bar"},
|
||||
expected: []string{"bar"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
result := p.getSliceAttribute(test.name, test.tags)
|
||||
|
||||
assert.Equal(t, test.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderGetFrontendRuleV1(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
service serviceUpdate
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "Should return default host foo.localhost",
|
||||
service: serviceUpdate{
|
||||
ServiceName: "foo",
|
||||
Attributes: []string{},
|
||||
},
|
||||
expected: "Host:foo.localhost",
|
||||
},
|
||||
{
|
||||
desc: "Should return host *.example.com",
|
||||
service: serviceUpdate{
|
||||
ServiceName: "foo",
|
||||
Attributes: []string{
|
||||
"traefik.frontend.rule=Host:*.example.com",
|
||||
},
|
||||
},
|
||||
expected: "Host:*.example.com",
|
||||
},
|
||||
{
|
||||
desc: "Should return host foo.example.com",
|
||||
service: serviceUpdate{
|
||||
ServiceName: "foo",
|
||||
Attributes: []string{
|
||||
"traefik.frontend.rule=Host:{{.ServiceName}}.example.com",
|
||||
},
|
||||
},
|
||||
expected: "Host:foo.example.com",
|
||||
},
|
||||
{
|
||||
desc: "Should return path prefix /bar",
|
||||
service: serviceUpdate{
|
||||
ServiceName: "foo",
|
||||
Attributes: []string{
|
||||
"traefik.frontend.rule=PathPrefix:{{getTag \"contextPath\" .Attributes \"/\"}}",
|
||||
"contextPath=/bar",
|
||||
},
|
||||
},
|
||||
expected: "PathPrefix:/bar",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := &Provider{
|
||||
Domain: "localhost",
|
||||
Prefix: "traefik",
|
||||
FrontEndRule: "Host:{{.ServiceName}}.{{.Domain}}",
|
||||
frontEndRuleTemplate: template.New("consul catalog frontend rule"),
|
||||
}
|
||||
p.setupFrontEndRuleTemplate()
|
||||
|
||||
actual := p.getFrontendRuleV1(test.service)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasStickinessLabelV1(t *testing.T) {
|
||||
p := &Provider{
|
||||
Prefix: "traefik",
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
tags []string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
desc: "label missing",
|
||||
tags: []string{},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
desc: "stickiness=true",
|
||||
tags: []string{
|
||||
label.TraefikBackendLoadBalancerStickiness + "=true",
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
desc: "stickiness=false",
|
||||
tags: []string{
|
||||
label.TraefikBackendLoadBalancerStickiness + "=false",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := p.hasStickinessLabelV1(test.tags)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
56
templates/consul_catalog-v1.tmpl
Normal file
56
templates/consul_catalog-v1.tmpl
Normal file
|
@ -0,0 +1,56 @@
|
|||
[backends]
|
||||
{{range $index, $node := .Nodes }}
|
||||
[backends."backend-{{ getBackend $node }}".servers."{{ getBackendName $node $index }}"]
|
||||
url = "{{ getAttribute "protocol" $node.Service.Tags "http" }}://{{ getBackendAddress $node }}:{{ $node.Service.Port }}"
|
||||
{{ $weight := getAttribute "backend.weight" $node.Service.Tags "0" }}
|
||||
{{with $weight }}
|
||||
weight = {{ $weight }}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{range .Services }}
|
||||
{{ $service := .ServiceName }}
|
||||
|
||||
{{ $circuitBreaker := getAttribute "backend.circuitbreaker" .Attributes "" }}
|
||||
{{with $circuitBreaker }}
|
||||
[backends."backend-{{ $service }}".circuitbreaker]
|
||||
expression = "{{ $circuitBreaker }}"
|
||||
{{end}}
|
||||
|
||||
[backends."backend-{{ $service }}".loadbalancer]
|
||||
method = "{{ getAttribute "backend.loadbalancer" .Attributes "wrr" }}"
|
||||
sticky = {{ getSticky .Attributes }}
|
||||
{{if hasStickinessLabel .Attributes }}
|
||||
[backends."backend-{{ $service }}".loadbalancer.stickiness]
|
||||
cookieName = "{{ getStickinessCookieName .Attributes }}"
|
||||
{{end}}
|
||||
|
||||
{{if hasMaxconnAttributes .Attributes }}
|
||||
[backends."backend-{{ $service }}".maxconn]
|
||||
amount = {{ getAttribute "backend.maxconn.amount" .Attributes "" }}
|
||||
extractorfunc = "{{ getAttribute "backend.maxconn.extractorfunc" .Attributes "" }}"
|
||||
{{end}}
|
||||
|
||||
{{end}}
|
||||
|
||||
[frontends]
|
||||
{{range .Services }}
|
||||
[frontends."frontend-{{ .ServiceName }}"]
|
||||
backend = "backend-{{ .ServiceName }}"
|
||||
passHostHeader = {{ getAttribute "frontend.passHostHeader" .Attributes "true" }}
|
||||
priority = {{ getAttribute "frontend.priority" .Attributes "0" }}
|
||||
|
||||
{{ $entryPoints := getAttribute "frontend.entrypoints" .Attributes "" }}
|
||||
{{with $entryPoints }}
|
||||
entrypoints = [{{range getEntryPoints $entryPoints }}
|
||||
"{{ . }}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
|
||||
basicAuth = [{{range getBasicAuth .Attributes }}
|
||||
"{{ . }}",
|
||||
{{end}}]
|
||||
|
||||
[frontends."frontend-{{ .ServiceName }}".routes."route-host-{{ .ServiceName }}"]
|
||||
rule = "{{ getFrontendRule . }}"
|
||||
{{end}}
|
|
@ -2,13 +2,13 @@
|
|||
{{range $service := .Services}}
|
||||
{{ $backendName := getServiceBackendName $service }}
|
||||
|
||||
{{ $circuitBreaker := getCircuitBreaker $service.Attributes }}
|
||||
{{ $circuitBreaker := getCircuitBreaker $service.TraefikLabels }}
|
||||
{{if $circuitBreaker }}
|
||||
[backends."backend-{{ $backendName }}".circuitBreaker]
|
||||
expression = "{{ $circuitBreaker.Expression }}"
|
||||
{{end}}
|
||||
|
||||
{{ $loadBalancer := getLoadBalancer $service.Attributes }}
|
||||
{{ $loadBalancer := getLoadBalancer $service.TraefikLabels }}
|
||||
{{if $loadBalancer }}
|
||||
[backends."backend-{{ $backendName }}".loadBalancer]
|
||||
method = "{{ $loadBalancer.Method }}"
|
||||
|
@ -19,14 +19,14 @@
|
|||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ $maxConn := getMaxConn $service.Attributes }}
|
||||
{{ $maxConn := getMaxConn $service.TraefikLabels }}
|
||||
{{if $maxConn }}
|
||||
[backends."backend-{{ $backendName }}".maxConn]
|
||||
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
|
||||
amount = {{ $maxConn.Amount }}
|
||||
{{end}}
|
||||
|
||||
{{ $healthCheck := getHealthCheck $service.Attributes }}
|
||||
{{ $healthCheck := getHealthCheck $service.TraefikLabels }}
|
||||
{{if $healthCheck }}
|
||||
[backends."backend-{{ $backendName }}".healthCheck]
|
||||
path = "{{ $healthCheck.Path }}"
|
||||
|
@ -34,7 +34,7 @@
|
|||
interval = "{{ $healthCheck.Interval }}"
|
||||
{{end}}
|
||||
|
||||
{{ $buffering := getBuffering $service.Attributes }}
|
||||
{{ $buffering := getBuffering $service.TraefikLabels }}
|
||||
{{if $buffering }}
|
||||
[backends."backend-{{ $backendName }}".buffering]
|
||||
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
|
||||
|
@ -46,10 +46,10 @@
|
|||
|
||||
{{end}}
|
||||
{{range $index, $node := .Nodes}}
|
||||
|
||||
{{ $server := getServer $node }}
|
||||
[backends."backend-{{ getNodeBackendName $node }}".servers."{{ getServerName $node $index }}"]
|
||||
url = "{{ getProtocol $node.Service.Tags }}://{{ getBackendAddress $node }}:{{ $node.Service.Port }}"
|
||||
weight = {{ getWeight $node.Service.Tags }}
|
||||
url = "{{ $server.URL }}"
|
||||
weight = {{ $server.Weight }}
|
||||
|
||||
{{end}}
|
||||
|
||||
|
@ -58,19 +58,19 @@
|
|||
|
||||
[frontends."frontend-{{ $service.ServiceName }}"]
|
||||
backend = "backend-{{ getServiceBackendName $service }}"
|
||||
priority = {{ getPriority $service.Attributes }}
|
||||
passHostHeader = {{ getPassHostHeader $service.Attributes }}
|
||||
passTLSCert = {{ getPassTLSCert $service.Attributes }}
|
||||
priority = {{ getPriority $service.TraefikLabels }}
|
||||
passHostHeader = {{ getPassHostHeader $service.TraefikLabels }}
|
||||
passTLSCert = {{ getPassTLSCert $service.TraefikLabels }}
|
||||
|
||||
entryPoints = [{{range getFrontEndEntryPoints $service.Attributes }}
|
||||
entryPoints = [{{range getFrontEndEntryPoints $service.TraefikLabels }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
|
||||
basicAuth = [{{range getBasicAuth $service.Attributes }}
|
||||
basicAuth = [{{range getBasicAuth $service.TraefikLabels }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
|
||||
{{ $whitelist := getWhiteList $service.Attributes }}
|
||||
{{ $whitelist := getWhiteList $service.TraefikLabels }}
|
||||
{{if $whitelist }}
|
||||
[frontends."frontend-{{ $service.ServiceName }}".whiteList]
|
||||
sourceRange = [{{range $whitelist.SourceRange }}
|
||||
|
@ -79,7 +79,7 @@
|
|||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||
{{end}}
|
||||
|
||||
{{ $redirect := getRedirect $service.Attributes }}
|
||||
{{ $redirect := getRedirect $service.TraefikLabels }}
|
||||
{{if $redirect }}
|
||||
[frontends."frontend-{{ $service.ServiceName }}".redirect]
|
||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||
|
@ -88,9 +88,10 @@
|
|||
permanent = {{ $redirect.Permanent }}
|
||||
{{end}}
|
||||
|
||||
{{if hasErrorPages $service.Attributes }}
|
||||
{{ $errorPages := getErrorPages $service.TraefikLabels }}
|
||||
{{if $errorPages }}
|
||||
[frontends."frontend-{{ $service.ServiceName }}".errors]
|
||||
{{range $pageName, $page := getErrorPages $service.Attributes }}
|
||||
{{range $pageName, $page := $errorPages }}
|
||||
[frontends."frontend-{{ $service.ServiceName }}".errors."{{ $pageName }}"]
|
||||
status = [{{range $page.Status }}
|
||||
"{{.}}",
|
||||
|
@ -100,22 +101,20 @@
|
|||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{if hasRateLimit $service.Attributes }}
|
||||
{{ $rateLimit := getRateLimit $service.Attributes }}
|
||||
{{ $rateLimit := getRateLimit $service.TraefikLabels }}
|
||||
{{if $rateLimit }}
|
||||
[frontends."frontend-{{ $service.ServiceName }}".rateLimit]
|
||||
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
|
||||
|
||||
[frontends."frontend-{{ $service.ServiceName }}".rateLimit.rateSet]
|
||||
{{range $limitName, $limit := $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}}
|
||||
|
||||
{{ $headers := getHeaders $service.Attributes }}
|
||||
{{ $headers := getHeaders $service.TraefikLabels }}
|
||||
{{if $headers }}
|
||||
[frontends."frontend-{{ $service.ServiceName }}".headers]
|
||||
SSLRedirect = {{ $headers.SSLRedirect }}
|
||||
|
|
Loading…
Reference in a new issue