diff --git a/docs/configuration/backends/marathon.md b/docs/configuration/backends/marathon.md index cc4f763cc..c4592e10c 100644 --- a/docs/configuration/backends/marathon.md +++ b/docs/configuration/backends/marathon.md @@ -68,6 +68,16 @@ domain = "marathon.localhost" # # marathonLBCompatibility = true +# Enable filtering using Marathon constraints.. +# If enabled, Traefik will read Marathon constraints, as defined in https://mesosphere.github.io/marathon/docs/constraints.html +# Each individual constraint will be treated as a verbatim compounded tag. +# i.e. "rack_id:CLUSTER:rack-1", with all constraint groups concatenated together using ":" +# +# Optional +# Default: false +# +# filterMarathonConstraints = true + # Enable Marathon basic authentication. # # Optional diff --git a/provider/marathon/builder_test.go b/provider/marathon/builder_test.go index e9a88eb96..bc9e2122d 100644 --- a/provider/marathon/builder_test.go +++ b/provider/marathon/builder_test.go @@ -44,6 +44,12 @@ func label(key, value string) func(*marathon.Application) { } } +func constraint(value string) func(*marathon.Application) { + return func(app *marathon.Application) { + app.AddConstraint(strings.Split(value, ":")...) + } +} + func labelWithService(key, value string, serviceName string) func(*marathon.Application) { if len(serviceName) == 0 { panic("serviceName can not be empty") diff --git a/provider/marathon/marathon.go b/provider/marathon/marathon.go index 5e6186fbf..d7267485d 100644 --- a/provider/marathon/marathon.go +++ b/provider/marathon/marathon.go @@ -53,20 +53,21 @@ var servicesPropertiesRegexp = regexp.MustCompile(`^traefik\.(?P.+ // Provider holds configuration of the provider. type Provider struct { provider.BaseProvider - Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon" export:"true"` - Domain string `description:"Default domain used" export:"true"` - ExposedByDefault bool `description:"Expose Marathon apps by default" export:"true"` - GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains" export:"true"` - DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header" export:"true"` - MarathonLBCompatibility bool `description:"Add compatibility with marathon-lb labels" export:"true"` - TLS *types.ClientTLS `description:"Enable Docker TLS support" export:"true"` - DialerTimeout flaeg.Duration `description:"Set a non-default connection timeout for Marathon" export:"true"` - KeepAlive flaeg.Duration `description:"Set a non-default TCP Keep Alive time in seconds" export:"true"` - ForceTaskHostname bool `description:"Force to use the task's hostname." export:"true"` - Basic *Basic `description:"Enable basic authentication" export:"true"` - RespectReadinessChecks bool `description:"Filter out tasks with non-successful readiness checks during deployments" export:"true"` - readyChecker *readinessChecker - marathonClient marathon.Marathon + Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon" export:"true"` + Domain string `description:"Default domain used" export:"true"` + ExposedByDefault bool `description:"Expose Marathon apps by default" export:"true"` + GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains" export:"true"` + DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header" export:"true"` + MarathonLBCompatibility bool `description:"Add compatibility with marathon-lb labels" export:"true"` + FilterMarathonConstraints bool `description:"Enable use of Marathon constraints in constraint filtering" export:"true"` + TLS *types.ClientTLS `description:"Enable Docker TLS support" export:"true"` + DialerTimeout flaeg.Duration `description:"Set a non-default connection timeout for Marathon" export:"true"` + KeepAlive flaeg.Duration `description:"Set a non-default TCP Keep Alive time in seconds" export:"true"` + ForceTaskHostname bool `description:"Force to use the task's hostname." export:"true"` + Basic *Basic `description:"Enable basic authentication" export:"true"` + RespectReadinessChecks bool `description:"Filter out tasks with non-successful readiness checks during deployments" export:"true"` + readyChecker *readinessChecker + marathonClient marathon.Marathon } // Basic holds basic authentication specific configurations @@ -247,6 +248,11 @@ func (p *Provider) applicationFilter(app marathon.Application) bool { constraintTags = append(constraintTags, label) } } + if p.FilterMarathonConstraints && app.Constraints != nil { + for _, constraintParts := range *app.Constraints { + constraintTags = append(constraintTags, strings.Join(constraintParts, ":")) + } + } if ok, failingConstraint := p.MatchConstraints(constraintTags); !ok { if failingConstraint != nil { log.Debugf("Filtering Marathon application %v pruned by '%v' constraint", app.ID, failingConstraint.String()) diff --git a/provider/marathon/marathon_test.go b/provider/marathon/marathon_test.go index f7ea244af..c2a531a48 100644 --- a/provider/marathon/marathon_test.go +++ b/provider/marathon/marathon_test.go @@ -519,10 +519,11 @@ func TestMarathonTaskFilter(t *testing.T) { func TestMarathonApplicationFilterConstraints(t *testing.T) { cases := []struct { - desc string - application marathon.Application - marathonLBCompatibility bool - expected bool + desc string + application marathon.Application + marathonLBCompatibility bool + filterMarathonConstraints bool + expected bool }{ { desc: "tags missing", @@ -536,6 +537,27 @@ func TestMarathonApplicationFilterConstraints(t *testing.T) { marathonLBCompatibility: false, expected: true, }, + { + desc: "constraint missing", + application: application(), + marathonLBCompatibility: false, + filterMarathonConstraints: true, + expected: false, + }, + { + desc: "constraint invalid", + application: application(constraint("service_cluster:CLUSTER:test")), + marathonLBCompatibility: false, + filterMarathonConstraints: true, + expected: false, + }, + { + desc: "constraint valid", + application: application(constraint("valid")), + marathonLBCompatibility: false, + filterMarathonConstraints: true, + expected: true, + }, { desc: "LB compatibility tag matching", application: application( @@ -552,8 +574,9 @@ func TestMarathonApplicationFilterConstraints(t *testing.T) { t.Run(c.desc, func(t *testing.T) { t.Parallel() provider := &Provider{ - ExposedByDefault: true, - MarathonLBCompatibility: c.marathonLBCompatibility, + ExposedByDefault: true, + MarathonLBCompatibility: c.marathonLBCompatibility, + FilterMarathonConstraints: c.filterMarathonConstraints, } constraint, err := types.NewConstraint("tag==valid") if err != nil {