diff --git a/docs/toml.md b/docs/toml.md index ad2fe3ccd..943e3f0de 100644 --- a/docs/toml.md +++ b/docs/toml.md @@ -783,6 +783,13 @@ domain = "marathon.localhost" # # groupsAsSubDomains = true +# Enable compatibility with marathon-lb labels +# +# Optional +# Default: false +# +# marathonLBCompatibility = true + # Enable Marathon basic authentication # # Optional diff --git a/provider/marathon.go b/provider/marathon.go index 39934ec3e..f2ec40918 100644 --- a/provider/marathon.go +++ b/provider/marathon.go @@ -26,14 +26,15 @@ var _ Provider = (*Marathon)(nil) // Marathon holds configuration of the Marathon provider. type Marathon struct { BaseProvider - Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"` - Domain string `description:"Default domain used"` - ExposedByDefault bool `description:"Expose Marathon apps by default"` - GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"` - DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header"` - TLS *ClientTLS `description:"Enable Docker TLS support"` - Basic *MarathonBasic - marathonClient marathon.Marathon + Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"` + Domain string `description:"Default domain used"` + ExposedByDefault bool `description:"Expose Marathon apps by default"` + GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"` + DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header"` + MarathonLBCompatibility bool `description:"Add compatibility with marathon-lb labels"` + TLS *ClientTLS `description:"Enable Docker TLS support"` + Basic *MarathonBasic + marathonClient marathon.Marathon } // MarathonBasic holds basic authentication specific configurations @@ -194,6 +195,11 @@ func (provider *Marathon) taskFilter(task marathon.Task, applications *marathon. } label, _ := provider.getLabel(application, "traefik.tags") constraintTags := strings.Split(label, ",") + if provider.MarathonLBCompatibility { + if label, err := provider.getLabel(application, "HAPROXY_GROUP"); err == nil { + constraintTags = append(constraintTags, label) + } + } if ok, failingConstraint := provider.MatchConstraints(constraintTags); !ok { if failingConstraint != nil { log.Debugf("Application %v pruned by '%v' constraint", application.ID, failingConstraint.String()) @@ -263,6 +269,11 @@ func (provider *Marathon) taskFilter(task marathon.Task, applications *marathon. func (provider *Marathon) applicationFilter(app marathon.Application, filteredTasks []marathon.Task) bool { label, _ := provider.getLabel(app, "traefik.tags") constraintTags := strings.Split(label, ",") + if provider.MarathonLBCompatibility { + if label, err := provider.getLabel(app, "HAPROXY_GROUP"); err == nil { + constraintTags = append(constraintTags, label) + } + } if ok, failingConstraint := provider.MatchConstraints(constraintTags); !ok { if failingConstraint != nil { log.Debugf("Application %v pruned by '%v' constraint", app.ID, failingConstraint.String()) @@ -384,6 +395,11 @@ func (provider *Marathon) getFrontendRule(application marathon.Application) stri if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil { return label } + if provider.MarathonLBCompatibility { + if label, err := provider.getLabel(application, "HAPROXY_0_VHOST"); err == nil { + return "Host:" + label + } + } return "Host:" + provider.getSubDomain(application.ID) + "." + provider.Domain } diff --git a/provider/marathon_test.go b/provider/marathon_test.go index 030cc2fb8..0e93f3fb0 100644 --- a/provider/marathon_test.go +++ b/provider/marathon_test.go @@ -684,9 +684,10 @@ func TestMarathonTaskFilter(t *testing.T) { func TestMarathonAppConstraints(t *testing.T) { cases := []struct { - application marathon.Application - filteredTasks []marathon.Task - expected bool + application marathon.Application + filteredTasks []marathon.Task + expected bool + marathonLBCompatibility bool }{ { application: marathon.Application{ @@ -698,28 +699,48 @@ func TestMarathonAppConstraints(t *testing.T) { AppID: "foo1", }, }, - expected: false, + marathonLBCompatibility: false, + expected: false, }, { application: marathon.Application{ - ID: "foo", + ID: "foo2", Labels: &map[string]string{ "traefik.tags": "valid", }, }, filteredTasks: []marathon.Task{ { - AppID: "foo", + AppID: "foo2", }, }, - expected: true, + marathonLBCompatibility: false, + expected: true, + }, + { + application: marathon.Application{ + ID: "foo3", + Labels: &map[string]string{ + "HAPROXY_GROUP": "valid", + "traefik.tags": "notvalid", + }, + }, + filteredTasks: []marathon.Task{ + { + AppID: "foo3", + }, + }, + marathonLBCompatibility: true, + expected: true, }, } - provider := &Marathon{} - constraint, _ := types.NewConstraint("tag==valid") - provider.Constraints = []types.Constraint{*constraint} for _, c := range cases { + provider := &Marathon{ + MarathonLBCompatibility: c.marathonLBCompatibility, + } + constraint, _ := types.NewConstraint("tag==valid") + provider.Constraints = []types.Constraint{*constraint} actual := provider.applicationFilter(c.application, c.filteredTasks) if actual != c.expected { t.Fatalf("expected %v, got %v: %v", c.expected, actual, c.application) @@ -729,9 +750,10 @@ func TestMarathonAppConstraints(t *testing.T) { } func TestMarathonTaskConstraints(t *testing.T) { cases := []struct { - applications []marathon.Application - filteredTask marathon.Task - expected bool + applications []marathon.Application + filteredTask marathon.Task + expected bool + marathonLBCompatibility bool }{ { applications: []marathon.Application{ @@ -749,7 +771,8 @@ func TestMarathonTaskConstraints(t *testing.T) { AppID: "foo1", Ports: []int{80}, }, - expected: false, + marathonLBCompatibility: false, + expected: false, }, { applications: []marathon.Application{ @@ -764,14 +787,40 @@ func TestMarathonTaskConstraints(t *testing.T) { AppID: "foo2", Ports: []int{80}, }, - expected: true, + marathonLBCompatibility: false, + expected: true, + }, + { + applications: []marathon.Application{ + { + ID: "foo3", + Labels: &map[string]string{ + "HAPROXY_GROUP": "valid", + "traefik.tags": "notvalid", + }, + }, { + ID: "foo4", + Labels: &map[string]string{ + "HAPROXY_GROUP": "notvalid", + "traefik.tags": "valid", + }, + }, + }, + filteredTask: marathon.Task{ + AppID: "foo3", + Ports: []int{80}, + }, + marathonLBCompatibility: true, + expected: true, }, } - provider := &Marathon{} - constraint, _ := types.NewConstraint("tag==valid") - provider.Constraints = []types.Constraint{*constraint} for _, c := range cases { + provider := &Marathon{ + MarathonLBCompatibility: c.marathonLBCompatibility, + } + constraint, _ := types.NewConstraint("tag==valid") + provider.Constraints = []types.Constraint{*constraint} apps := new(marathon.Applications) apps.Apps = c.applications actual := provider.taskFilter(c.filteredTask, apps, true) @@ -1152,37 +1201,53 @@ func TestMarathonGetEntryPoints(t *testing.T) { } func TestMarathonGetFrontendRule(t *testing.T) { - provider := &Marathon{ - Domain: "docker.localhost", - } - applications := []struct { - application marathon.Application - expected string + application marathon.Application + expected string + marathonLBCompatibility bool }{ { application: marathon.Application{ Labels: &map[string]string{}}, - expected: "Host:.docker.localhost", + marathonLBCompatibility: true, + expected: "Host:.docker.localhost", }, { application: marathon.Application{ - ID: "test", - Labels: &map[string]string{}, + ID: "test", + Labels: &map[string]string{ + "HAPROXY_0_VHOST": "foo.bar", + }, }, - expected: "Host:test.docker.localhost", + marathonLBCompatibility: false, + expected: "Host:test.docker.localhost", }, { application: marathon.Application{ Labels: &map[string]string{ "traefik.frontend.rule": "Host:foo.bar", + "HAPROXY_0_VHOST": "notvalid", }, }, - expected: "Host:foo.bar", + marathonLBCompatibility: true, + expected: "Host:foo.bar", + }, + { + application: marathon.Application{ + Labels: &map[string]string{ + "HAPROXY_0_VHOST": "foo.bar", + }, + }, + marathonLBCompatibility: true, + expected: "Host:foo.bar", }, } for _, a := range applications { + provider := &Marathon{ + Domain: "docker.localhost", + MarathonLBCompatibility: a.marathonLBCompatibility, + } actual := provider.getFrontendRule(a.application) if actual != a.expected { t.Fatalf("expected %q, got %q", a.expected, actual)