1
0
Fork 0

Enhanced flexibility in Consul Catalog configuration

This commit is contained in:
Alex Antonov 2017-05-08 12:46:53 -05:00 committed by Ludovic Fernandez
parent 9c27a98821
commit 7d6c778211
6 changed files with 242 additions and 20 deletions

View file

@ -1,6 +1,7 @@
package consul
import (
"bytes"
"errors"
"sort"
"strconv"
@ -31,7 +32,9 @@ type CatalogProvider struct {
Endpoint string `description:"Consul server endpoint"`
Domain string `description:"Default domain used"`
Prefix string `description:"Prefix used for Consul catalog tags"`
FrontEndRule string `description:"Frontend rule used for Consul services"`
client *api.Client
frontEndRuleTemplate *template.Template
}
type serviceUpdate struct {
@ -137,9 +140,9 @@ func (p *CatalogProvider) healthyNodes(service string) (catalogUpdate, error) {
}
nodes := fun.Filter(func(node *api.ServiceEntry) bool {
constraintTags := p.getContraintTags(node.Service.Tags)
constraintTags := p.getConstraintTags(node.Service.Tags)
ok, failingConstraint := p.MatchConstraints(constraintTags)
if ok == false && failingConstraint != nil {
if !ok && failingConstraint != nil {
log.Debugf("Service %v pruned by '%v' constraint", service, failingConstraint.String())
}
return ok
@ -162,6 +165,13 @@ func (p *CatalogProvider) healthyNodes(service string) (catalogUpdate, error) {
}, nil
}
func (p *CatalogProvider) getPrefixedName(name string) string {
if len(p.Prefix) > 0 {
return p.Prefix + "." + name
}
return name
}
func (p *CatalogProvider) getEntryPoints(list string) []string {
return strings.Split(list, ",")
}
@ -172,10 +182,35 @@ func (p *CatalogProvider) getBackend(node *api.ServiceEntry) string {
func (p *CatalogProvider) getFrontendRule(service serviceUpdate) string {
customFrontendRule := p.getAttribute("frontend.rule", service.Attributes, "")
if customFrontendRule != "" {
return customFrontendRule
if customFrontendRule == "" {
customFrontendRule = p.FrontEndRule
}
return "Host:" + service.ServiceName + "." + p.Domain
t := p.frontEndRuleTemplate
t, err := t.Parse(customFrontendRule)
if err != nil {
log.Errorf("failed to parse Consul Catalog custom frontend rule: %s", err)
return ""
}
templateObjects := struct {
ServiceName string
Domain string
Attributes []string
}{
ServiceName: service.ServiceName,
Domain: p.Domain,
Attributes: service.Attributes,
}
var buffer bytes.Buffer
err = t.Execute(&buffer, templateObjects)
if err != nil {
log.Errorf("failed to execute Consul Catalog custom frontend rule template: %s", err)
return ""
}
return buffer.String()
}
func (p *CatalogProvider) getBackendAddress(node *api.ServiceEntry) string {
@ -201,22 +236,42 @@ func (p *CatalogProvider) getBackendName(node *api.ServiceEntry, index int) stri
}
func (p *CatalogProvider) getAttribute(name string, tags []string, defaultValue string) string {
return p.getTag(p.getPrefixedName(name), tags, defaultValue)
}
func (p *CatalogProvider) hasTag(name string, tags []string) bool {
// Very-very unlikely that a Consul tag would ever start with '=!='
tag := p.getTag(name, tags, "=!=")
return tag != "=!="
}
func (p *CatalogProvider) getTag(name string, tags []string, defaultValue string) string {
for _, tag := range tags {
if strings.Index(strings.ToLower(tag), p.Prefix+".") == 0 {
if kv := strings.SplitN(tag[len(p.Prefix+"."):], "=", 2); len(kv) == 2 && strings.ToLower(kv[0]) == strings.ToLower(name) {
return kv[1]
// Given the nature of Consul tags, which could be either singular markers, or key=value pairs, we check if the consul tag starts with 'name'
if strings.Index(strings.ToLower(tag), strings.ToLower(name)) == 0 {
// In case, where a tag might be a key=value, try to split it by the first '='
// - If the first element (which would always be there, even if the tag is a singular marker without '=' in it
if kv := strings.SplitN(tag, "=", 2); strings.ToLower(kv[0]) == strings.ToLower(name) {
// 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
}
func (p *CatalogProvider) getContraintTags(tags []string) []string {
func (p *CatalogProvider) getConstraintTags(tags []string) []string {
var list []string
for _, tag := range tags {
if strings.Index(strings.ToLower(tag), p.Prefix+".tags=") == 0 {
splitedTags := strings.Split(tag[len(p.Prefix+".tags="):], ",")
// If 'AllTagsConstraintFiltering' is disabled, we look for a Consul tag named 'traefik.tags' (unless different 'prefix' is configured)
if strings.Index(strings.ToLower(tag), p.getPrefixedName("tags=")) == 0 {
// If 'traefik.tags=' tag is found, take the tag value and split by ',' adding the result to the list to be returned
splitedTags := strings.Split(tag[len(p.getPrefixedName("tags=")):], ",")
list = append(list, splitedTags...)
}
}
@ -231,6 +286,8 @@ func (p *CatalogProvider) buildConfig(catalog []catalogUpdate) *types.Configurat
"getBackendName": p.getBackendName,
"getBackendAddress": p.getBackendAddress,
"getAttribute": p.getAttribute,
"getTag": p.getTag,
"hasTag": p.hasTag,
"getEntryPoints": p.getEntryPoints,
"hasMaxconnAttributes": p.hasMaxconnAttributes,
}
@ -326,6 +383,16 @@ func (p *CatalogProvider) watch(configurationChan chan<- types.ConfigMessage, st
}
}
func (p *CatalogProvider) setupFrontEndTemplate() {
var FuncMap = template.FuncMap{
"getAttribute": p.getAttribute,
"getTag": p.getTag,
"hasTag": p.hasTag,
}
t := template.New("consul catalog frontend rule").Funcs(FuncMap)
p.frontEndRuleTemplate = t
}
// Provide allows the consul catalog provider to provide configurations to traefik
// using the given configuration channel.
func (p *CatalogProvider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
@ -337,6 +404,7 @@ func (p *CatalogProvider) Provide(configurationChan chan<- types.ConfigMessage,
}
p.client = client
p.Constraints = append(p.Constraints, constraints...)
p.setupFrontEndTemplate()
pool.Go(func(stop chan bool) {
notify := func(err error, time time.Duration) {