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 gc.Rancher != nil {
|
||||||
if len(gc.Rancher.Filename) != 0 && gc.Rancher.TemplateVersion != 2 {
|
if len(gc.Rancher.Filename) != 0 && gc.Rancher.TemplateVersion != 2 {
|
||||||
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
|
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containous/traefik/integration/try"
|
"github.com/containous/traefik/integration/try"
|
||||||
|
"github.com/containous/traefik/provider/label"
|
||||||
"github.com/go-check/check"
|
"github.com/go-check/check"
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
checker "github.com/vdemeester/shakers"
|
checker "github.com/vdemeester/shakers"
|
||||||
|
@ -160,7 +161,6 @@ func (s *ConsulCatalogSuite) TestSingleService(c *check.C) {
|
||||||
s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
||||||
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody())
|
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody())
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConsulCatalogSuite) TestExposedByDefaultFalseSingleService(c *check.C) {
|
func (s *ConsulCatalogSuite) TestExposedByDefaultFalseSingleService(c *check.C) {
|
||||||
|
@ -202,13 +202,12 @@ func (s *ConsulCatalogSuite) TestExposedByDefaultFalseSimpleServiceMultipleNode(
|
||||||
defer cmd.Process.Kill()
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
whoami := s.composeProject.Container(c, "whoami1")
|
whoami := s.composeProject.Container(c, "whoami1")
|
||||||
whoami2 := s.composeProject.Container(c, "whoami2")
|
|
||||||
|
|
||||||
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{})
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{})
|
||||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
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"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
defer s.deregisterService("test", whoami2.NetworkSettings.IPAddress)
|
defer s.deregisterService("test", whoami2.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
@ -326,7 +325,7 @@ func (s *ConsulCatalogSuite) TestBasicAuthSimpleService(c *check.C) {
|
||||||
whoami := s.composeProject.Container(c, "whoami1")
|
whoami := s.composeProject.Container(c, "whoami1")
|
||||||
|
|
||||||
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{
|
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"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
||||||
|
@ -362,7 +361,8 @@ func (s *ConsulCatalogSuite) TestRefreshConfigTagChange(c *check.C) {
|
||||||
|
|
||||||
whoami := s.composeProject.Container(c, "whoami1")
|
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"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
@ -370,7 +370,8 @@ func (s *ConsulCatalogSuite) TestRefreshConfigTagChange(c *check.C) {
|
||||||
try.BodyContains(whoami.NetworkSettings.IPAddress))
|
try.BodyContains(whoami.NetworkSettings.IPAddress))
|
||||||
c.Assert(err, checker.NotNil)
|
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"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
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()
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
whoami := s.composeProject.Container(c, "whoami1")
|
whoami := s.composeProject.Container(c, "whoami1")
|
||||||
whoami2 := s.composeProject.Container(c, "whoami2")
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80,
|
||||||
whoami3 := s.composeProject.Container(c, "whoami3")
|
[]string{"name=whoami1", label.TraefikEnable + "=true", label.TraefikBackendCircuitBreakerExpression + "=NetworkErrorRatio() > 0.5"})
|
||||||
|
|
||||||
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1", "traefik.enable=true", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
|
|
||||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
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"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
defer s.deregisterService("test", whoami2.NetworkSettings.IPAddress)
|
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"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
defer s.deregisterService("test", whoami3.NetworkSettings.IPAddress)
|
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))
|
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)
|
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"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
|
||||||
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
@ -50,6 +51,7 @@ type Service struct {
|
||||||
type serviceUpdate struct {
|
type serviceUpdate struct {
|
||||||
ServiceName string
|
ServiceName string
|
||||||
Attributes []string
|
Attributes []string
|
||||||
|
TraefikLabels map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type catalogUpdate struct {
|
type catalogUpdate struct {
|
||||||
|
@ -446,10 +448,13 @@ func (p *Provider) healthyNodes(service string) (catalogUpdate, error) {
|
||||||
).(map[string]bool)).([]string)
|
).(map[string]bool)).([]string)
|
||||||
}, []string{}, nodes).([]string)
|
}, []string{}, nodes).([]string)
|
||||||
|
|
||||||
|
labels := tagsToNeutralLabels(tags, p.Prefix)
|
||||||
|
|
||||||
return catalogUpdate{
|
return catalogUpdate{
|
||||||
Service: &serviceUpdate{
|
Service: &serviceUpdate{
|
||||||
ServiceName: service,
|
ServiceName: service,
|
||||||
Attributes: tags,
|
Attributes: tags,
|
||||||
|
TraefikLabels: labels,
|
||||||
},
|
},
|
||||||
Nodes: nodes,
|
Nodes: nodes,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -473,7 +478,18 @@ func (p *Provider) nodeFilter(service string, node *api.ServiceEntry) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) isServiceEnabled(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 {
|
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}}
|
{{range $service := .Services}}
|
||||||
{{ $backendName := getServiceBackendName $service }}
|
{{ $backendName := getServiceBackendName $service }}
|
||||||
|
|
||||||
{{ $circuitBreaker := getCircuitBreaker $service.Attributes }}
|
{{ $circuitBreaker := getCircuitBreaker $service.TraefikLabels }}
|
||||||
{{if $circuitBreaker }}
|
{{if $circuitBreaker }}
|
||||||
[backends."backend-{{ $backendName }}".circuitBreaker]
|
[backends."backend-{{ $backendName }}".circuitBreaker]
|
||||||
expression = "{{ $circuitBreaker.Expression }}"
|
expression = "{{ $circuitBreaker.Expression }}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $loadBalancer := getLoadBalancer $service.Attributes }}
|
{{ $loadBalancer := getLoadBalancer $service.TraefikLabels }}
|
||||||
{{if $loadBalancer }}
|
{{if $loadBalancer }}
|
||||||
[backends."backend-{{ $backendName }}".loadBalancer]
|
[backends."backend-{{ $backendName }}".loadBalancer]
|
||||||
method = "{{ $loadBalancer.Method }}"
|
method = "{{ $loadBalancer.Method }}"
|
||||||
|
@ -19,14 +19,14 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $maxConn := getMaxConn $service.Attributes }}
|
{{ $maxConn := getMaxConn $service.TraefikLabels }}
|
||||||
{{if $maxConn }}
|
{{if $maxConn }}
|
||||||
[backends."backend-{{ $backendName }}".maxConn]
|
[backends."backend-{{ $backendName }}".maxConn]
|
||||||
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
|
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
|
||||||
amount = {{ $maxConn.Amount }}
|
amount = {{ $maxConn.Amount }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $healthCheck := getHealthCheck $service.Attributes }}
|
{{ $healthCheck := getHealthCheck $service.TraefikLabels }}
|
||||||
{{if $healthCheck }}
|
{{if $healthCheck }}
|
||||||
[backends."backend-{{ $backendName }}".healthCheck]
|
[backends."backend-{{ $backendName }}".healthCheck]
|
||||||
path = "{{ $healthCheck.Path }}"
|
path = "{{ $healthCheck.Path }}"
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
interval = "{{ $healthCheck.Interval }}"
|
interval = "{{ $healthCheck.Interval }}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $buffering := getBuffering $service.Attributes }}
|
{{ $buffering := getBuffering $service.TraefikLabels }}
|
||||||
{{if $buffering }}
|
{{if $buffering }}
|
||||||
[backends."backend-{{ $backendName }}".buffering]
|
[backends."backend-{{ $backendName }}".buffering]
|
||||||
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
|
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
|
||||||
|
@ -46,10 +46,10 @@
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
{{range $index, $node := .Nodes}}
|
{{range $index, $node := .Nodes}}
|
||||||
|
{{ $server := getServer $node }}
|
||||||
[backends."backend-{{ getNodeBackendName $node }}".servers."{{ getServerName $node $index }}"]
|
[backends."backend-{{ getNodeBackendName $node }}".servers."{{ getServerName $node $index }}"]
|
||||||
url = "{{ getProtocol $node.Service.Tags }}://{{ getBackendAddress $node }}:{{ $node.Service.Port }}"
|
url = "{{ $server.URL }}"
|
||||||
weight = {{ getWeight $node.Service.Tags }}
|
weight = {{ $server.Weight }}
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
@ -58,19 +58,19 @@
|
||||||
|
|
||||||
[frontends."frontend-{{ $service.ServiceName }}"]
|
[frontends."frontend-{{ $service.ServiceName }}"]
|
||||||
backend = "backend-{{ getServiceBackendName $service }}"
|
backend = "backend-{{ getServiceBackendName $service }}"
|
||||||
priority = {{ getPriority $service.Attributes }}
|
priority = {{ getPriority $service.TraefikLabels }}
|
||||||
passHostHeader = {{ getPassHostHeader $service.Attributes }}
|
passHostHeader = {{ getPassHostHeader $service.TraefikLabels }}
|
||||||
passTLSCert = {{ getPassTLSCert $service.Attributes }}
|
passTLSCert = {{ getPassTLSCert $service.TraefikLabels }}
|
||||||
|
|
||||||
entryPoints = [{{range getFrontEndEntryPoints $service.Attributes }}
|
entryPoints = [{{range getFrontEndEntryPoints $service.TraefikLabels }}
|
||||||
"{{.}}",
|
"{{.}}",
|
||||||
{{end}}]
|
{{end}}]
|
||||||
|
|
||||||
basicAuth = [{{range getBasicAuth $service.Attributes }}
|
basicAuth = [{{range getBasicAuth $service.TraefikLabels }}
|
||||||
"{{.}}",
|
"{{.}}",
|
||||||
{{end}}]
|
{{end}}]
|
||||||
|
|
||||||
{{ $whitelist := getWhiteList $service.Attributes }}
|
{{ $whitelist := getWhiteList $service.TraefikLabels }}
|
||||||
{{if $whitelist }}
|
{{if $whitelist }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".whiteList]
|
[frontends."frontend-{{ $service.ServiceName }}".whiteList]
|
||||||
sourceRange = [{{range $whitelist.SourceRange }}
|
sourceRange = [{{range $whitelist.SourceRange }}
|
||||||
|
@ -79,7 +79,7 @@
|
||||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $redirect := getRedirect $service.Attributes }}
|
{{ $redirect := getRedirect $service.TraefikLabels }}
|
||||||
{{if $redirect }}
|
{{if $redirect }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".redirect]
|
[frontends."frontend-{{ $service.ServiceName }}".redirect]
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
|
@ -88,9 +88,10 @@
|
||||||
permanent = {{ $redirect.Permanent }}
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if hasErrorPages $service.Attributes }}
|
{{ $errorPages := getErrorPages $service.TraefikLabels }}
|
||||||
|
{{if $errorPages }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".errors]
|
[frontends."frontend-{{ $service.ServiceName }}".errors]
|
||||||
{{range $pageName, $page := getErrorPages $service.Attributes }}
|
{{range $pageName, $page := $errorPages }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".errors."{{ $pageName }}"]
|
[frontends."frontend-{{ $service.ServiceName }}".errors."{{ $pageName }}"]
|
||||||
status = [{{range $page.Status }}
|
status = [{{range $page.Status }}
|
||||||
"{{.}}",
|
"{{.}}",
|
||||||
|
@ -100,22 +101,20 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if hasRateLimit $service.Attributes }}
|
{{ $rateLimit := getRateLimit $service.TraefikLabels }}
|
||||||
{{ $rateLimit := getRateLimit $service.Attributes }}
|
{{if $rateLimit }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".rateLimit]
|
[frontends."frontend-{{ $service.ServiceName }}".rateLimit]
|
||||||
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
|
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
|
||||||
|
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".rateLimit.rateSet]
|
[frontends."frontend-{{ $service.ServiceName }}".rateLimit.rateSet]
|
||||||
{{range $limitName, $limit := $rateLimit.RateSet }}
|
{{ range $limitName, $limit := $rateLimit.RateSet }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".rateLimit.rateSet."{{ $limitName }}"]
|
[frontends."frontend-{{ $service.ServiceName }}".rateLimit.rateSet."{{ $limitName }}"]
|
||||||
period = "{{ $limit.Period }}"
|
period = "{{ $limit.Period }}"
|
||||||
average = {{ $limit.Average }}
|
average = {{ $limit.Average }}
|
||||||
burst = {{ $limit.Burst }}
|
burst = {{ $limit.Burst }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $headers := getHeaders $service.Attributes }}
|
{{ $headers := getHeaders $service.TraefikLabels }}
|
||||||
{{if $headers }}
|
{{if $headers }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".headers]
|
[frontends."frontend-{{ $service.ServiceName }}".headers]
|
||||||
SSLRedirect = {{ $headers.SSLRedirect }}
|
SSLRedirect = {{ $headers.SSLRedirect }}
|
||||||
|
|
Loading…
Reference in a new issue