From d501c0786f9639a325f2a3ca43454777d2bb8373 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 13 Feb 2020 10:26:04 +0100 Subject: [PATCH] Early filter of the catalog services. --- docs/content/providers/consul-catalog.md | 2 +- integration/consul_catalog_test.go | 13 ++++- pkg/provider/consulcatalog/consul_catalog.go | 53 ++++++++++++++++++-- 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/docs/content/providers/consul-catalog.md b/docs/content/providers/consul-catalog.md index 0b549ae47..d4e69f4cb 100644 --- a/docs/content/providers/consul-catalog.md +++ b/docs/content/providers/consul-catalog.md @@ -565,7 +565,7 @@ Constraints is an expression that Traefik matches against the service's tags to That is to say, if none of the service's tags match the expression, no route for that service is created. If the expression is empty, all detected services are included. -The expression syntax is based on the `Tag("tag")`, and `TagRegex("tag")` functions, +The expression syntax is based on the ```Tag(`tag`)```, and ```TagRegex(`tag`)``` functions, as well as the usual boolean logic, as shown in examples below. ??? example "Constraints Expression Examples" diff --git a/integration/consul_catalog_test.go b/integration/consul_catalog_test.go index 06f0287a1..632516682 100644 --- a/integration/consul_catalog_test.go +++ b/integration/consul_catalog_test.go @@ -128,7 +128,18 @@ func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings(c *c c.Assert(err, checker.IsNil) req.Host = "whoami" - err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2", "Hostname: whoami3")) + err = try.Request(req, 2*time.Second, + try.StatusCodeIs(200), + try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2", "Hostname: whoami3")) + c.Assert(err, checker.IsNil) + + err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, + try.StatusCodeIs(200), + try.BodyContains( + fmt.Sprintf(`"http://%s:80":"UP"`, reg1.Address), + fmt.Sprintf(`"http://%s:80":"UP"`, reg2.Address), + fmt.Sprintf(`"http://%s:80":"UP"`, reg3.Address), + )) c.Assert(err, checker.IsNil) err = s.deregisterService("whoami1", false) diff --git a/pkg/provider/consulcatalog/consul_catalog.go b/pkg/provider/consulcatalog/consul_catalog.go index f3c5c5a03..c788b4a70 100644 --- a/pkg/provider/consulcatalog/consul_catalog.go +++ b/pkg/provider/consulcatalog/consul_catalog.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strconv" + "strings" "text/template" "time" @@ -12,6 +13,7 @@ import ( "github.com/containous/traefik/v2/pkg/job" "github.com/containous/traefik/v2/pkg/log" "github.com/containous/traefik/v2/pkg/provider" + "github.com/containous/traefik/v2/pkg/provider/constraints" "github.com/containous/traefik/v2/pkg/safe" "github.com/containous/traefik/v2/pkg/types" "github.com/hashicorp/consul/api" @@ -151,7 +153,7 @@ func (p *Provider) getConsulServicesData(ctx context.Context) ([]itemData, error } var data []itemData - for name := range consulServiceNames { + for _, name := range consulServiceNames { consulServices, healthServices, err := p.fetchService(ctx, name) if err != nil { return nil, err @@ -204,10 +206,55 @@ func (p *Provider) fetchService(ctx context.Context, name string) ([]*api.Catalo return consulServices, healthServices, err } -func (p *Provider) fetchServices(ctx context.Context) (map[string][]string, error) { +func (p *Provider) fetchServices(ctx context.Context) ([]string, error) { + // The query option "Filter" is not supported by /catalog/services. + // https://www.consul.io/api/catalog.html#list-services opts := &api.QueryOptions{AllowStale: p.Stale, RequireConsistent: p.RequireConsistent, UseCache: p.Cache} serviceNames, _, err := p.client.Catalog().Services(opts) - return serviceNames, err + if err != nil { + return nil, err + } + + // The keys are the service names, and the array values provide all known tags for a given service. + // https://www.consul.io/api/catalog.html#list-services + var filtered []string + for svcName, tags := range serviceNames { + logger := log.FromContext(log.With(ctx, log.Str("serviceName", svcName))) + + if !p.ExposedByDefault && !contains(tags, p.Prefix+".enable=true") { + logger.Debug("Filtering disabled item") + continue + } + + if contains(tags, p.Prefix+".enable=false") { + logger.Debug("Filtering disabled item") + continue + } + + matches, err := constraints.MatchTags(tags, p.Constraints) + if err != nil { + logger.Errorf("Error matching constraints expression: %v", err) + continue + } + + if !matches { + logger.Debugf("Container pruned by constraint expression: %q", p.Constraints) + continue + } + + filtered = append(filtered, svcName) + } + + return filtered, err +} + +func contains(values []string, val string) bool { + for _, value := range values { + if strings.EqualFold(value, val) { + return true + } + } + return false } func createClient(cfg *EndpointConfig) (*api.Client, error) {