diff --git a/docs/index.md b/docs/index.md index 573c552c3..3a22ed66e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -487,7 +487,8 @@ domain = "marathon.localhost" Labels can be used on containers to override default behaviour: - `traefik.backend=foo`: assign the application to `foo` backend -- `traefik.port=80`: register this port. Useful when the application exposes multiples ports. +- `traefik.portIndex=1`: register port by index in the application's ports array. Useful when the application exposes multiple ports. +- `traefik.port=80`: register the explicit application port value. Cannot be used alongside `traefik.portIndex`. - `traefik.protocol=https`: override the default `http` protocol - `traefik.weight=10`: assign this weight to the application - `traefik.enable=false`: disable this application in Træfɪk diff --git a/provider/marathon.go b/provider/marathon.go index e86dc8e9e..7094b1f9a 100644 --- a/provider/marathon.go +++ b/provider/marathon.go @@ -123,20 +123,55 @@ func taskFilter(task marathon.Task, applications *marathon.Applications) bool { log.Debug("Filtering marathon task without port %s", task.AppID) return false } - application, errApp := getApplication(task, applications.Apps) - if errApp != nil { + application, err := getApplication(task, applications.Apps) + if err != nil { log.Errorf("Unable to get marathon application from task %s", task.AppID) return false } - _, err := strconv.Atoi(application.Labels["traefik.port"]) - if len(application.Ports) > 1 && err != nil { - log.Debugf("Filtering marathon task %s with more than 1 port and no traefik.port label", task.AppID) - return false - } if application.Labels["traefik.enable"] == "false" { log.Debugf("Filtering disabled marathon task %s", task.AppID) return false } + + //filter indeterminable task port + portIndexLabel := application.Labels["traefik.portIndex"] + portValueLabel := application.Labels["traefik.port"] + if portIndexLabel != "" && portValueLabel != "" { + log.Debugf("Filtering marathon task %s specifying both traefik.portIndex and traefik.port labels", task.AppID) + return false + } + if portIndexLabel == "" && portValueLabel == "" && len(application.Ports) > 1 { + log.Debugf("Filtering marathon task %s with more than 1 port and no traefik.portIndex or traefik.port label", task.AppID) + return false + } + if portIndexLabel != "" { + index, err := strconv.Atoi(application.Labels["traefik.portIndex"]) + if err != nil || index < 0 || index > len(application.Ports)-1 { + log.Debugf("Filtering marathon task %s with unexpected value for traefik.portIndex label", task.AppID) + return false + } + } + if portValueLabel != "" { + port, err := strconv.Atoi(application.Labels["traefik.port"]) + if err != nil { + log.Debugf("Filtering marathon task %s with unexpected value for traefik.port label", task.AppID) + return false + } + + var foundPort bool + for _, exposedPort := range task.Ports { + if port == exposedPort { + foundPort = true + break + } + } + + if !foundPort { + log.Debugf("Filtering marathon task %s without a matching port for traefik.port label", task.AppID) + return false + } + } + //filter healthchecks if application.HasHealthChecks() { if task.HasHealthCheckResults() { @@ -179,7 +214,22 @@ func (provider *Marathon) getLabel(application marathon.Application, label strin return "", errors.New("Label not found:" + label) } -func (provider *Marathon) getPort(task marathon.Task) string { +func (provider *Marathon) getPort(task marathon.Task, applications []marathon.Application) string { + application, err := getApplication(task, applications) + if err != nil { + log.Errorf("Unable to get marathon application from task %s", task.AppID) + return "" + } + + if portIndexLabel, err := provider.getLabel(application, "traefik.portIndex"); err == nil { + if index, err := strconv.Atoi(portIndexLabel); err == nil { + return strconv.Itoa(task.Ports[index]) + } + } + if portValueLabel, err := provider.getLabel(application, "traefik.port"); err == nil { + return portValueLabel + } + for _, port := range task.Ports { return strconv.Itoa(port) } diff --git a/provider/marathon_test.go b/provider/marathon_test.go index 98c88b738..bac35f1f4 100644 --- a/provider/marathon_test.go +++ b/provider/marathon_test.go @@ -201,6 +201,97 @@ func TestMarathonTaskFilter(t *testing.T) { }, expected: false, }, + { + task: marathon.Task{ + AppID: "specify-port-number", + Ports: []int{80, 443}, + }, + applications: &marathon.Applications{ + Apps: []marathon.Application{ + { + ID: "specify-port-number", + Ports: []int{80, 443}, + Labels: map[string]string{ + "traefik.port": "80", + }, + }, + }, + }, + expected: true, + }, + { + task: marathon.Task{ + AppID: "specify-unknown-port-number", + Ports: []int{80, 443}, + }, + applications: &marathon.Applications{ + Apps: []marathon.Application{ + { + ID: "specify-unknown-port-number", + Ports: []int{80, 443}, + Labels: map[string]string{ + "traefik.port": "8080", + }, + }, + }, + }, + expected: false, + }, + { + task: marathon.Task{ + AppID: "specify-port-index", + Ports: []int{80, 443}, + }, + applications: &marathon.Applications{ + Apps: []marathon.Application{ + { + ID: "specify-port-index", + Ports: []int{80, 443}, + Labels: map[string]string{ + "traefik.portIndex": "0", + }, + }, + }, + }, + expected: true, + }, + { + task: marathon.Task{ + AppID: "specify-out-of-range-port-index", + Ports: []int{80, 443}, + }, + applications: &marathon.Applications{ + Apps: []marathon.Application{ + { + ID: "specify-out-of-range-port-index", + Ports: []int{80, 443}, + Labels: map[string]string{ + "traefik.portIndex": "2", + }, + }, + }, + }, + expected: false, + }, + { + task: marathon.Task{ + AppID: "specify-both-port-index-and-number", + Ports: []int{80, 443}, + }, + applications: &marathon.Applications{ + Apps: []marathon.Application{ + { + ID: "specify-both-port-index-and-number", + Ports: []int{80, 443}, + Labels: map[string]string{ + "traefik.port": "443", + "traefik.portIndex": "1", + }, + }, + }, + }, + expected: false, + }, { task: marathon.Task{ AppID: "foo", @@ -370,29 +461,84 @@ func TestMarathonGetPort(t *testing.T) { provider := &Marathon{} cases := []struct { - task marathon.Task - expected string + applications []marathon.Application + task marathon.Task + expected string }{ { - task: marathon.Task{}, + applications: []marathon.Application{}, + task: marathon.Task{}, + expected: "", + }, + { + applications: []marathon.Application{ + { + ID: "test1", + }, + }, + task: marathon.Task{ + AppID: "test2", + }, expected: "", }, { + applications: []marathon.Application{ + { + ID: "test1", + }, + }, task: marathon.Task{ + AppID: "test1", Ports: []int{80}, }, expected: "80", }, { + applications: []marathon.Application{ + { + ID: "test1", + }, + }, task: marathon.Task{ + AppID: "test1", Ports: []int{80, 443}, }, expected: "80", }, + { + applications: []marathon.Application{ + { + ID: "specify-port-number", + Labels: map[string]string{ + "traefik.port": "443", + }, + }, + }, + task: marathon.Task{ + AppID: "specify-port-number", + Ports: []int{80, 443}, + }, + expected: "443", + }, + { + applications: []marathon.Application{ + { + ID: "specify-port-index", + Labels: map[string]string{ + "traefik.portIndex": "1", + }, + }, + }, + task: marathon.Task{ + AppID: "specify-port-index", + Ports: []int{80, 443}, + }, + expected: "443", + }, } for _, c := range cases { - actual := provider.getPort(c.task) + actual := provider.getPort(c.task, c.applications) if actual != c.expected { t.Fatalf("expected %q, got %q", c.expected, actual) } diff --git a/templates/marathon.tmpl b/templates/marathon.tmpl index 429f6eab7..9e689174e 100644 --- a/templates/marathon.tmpl +++ b/templates/marathon.tmpl @@ -1,7 +1,7 @@ {{$apps := .Applications}} [backends]{{range .Tasks}} [backends.backend{{.AppID | replace "/" "-"}}.servers.server-{{.ID | replace "." "-"}}] - url = "{{getProtocol . $apps}}://{{.Host}}:{{getPort .}}" + url = "{{getProtocol . $apps}}://{{.Host}}:{{getPort . $apps}}" weight = {{getWeight . $apps}} {{end}} @@ -12,4 +12,4 @@ [frontends.frontend{{.ID | replace "/" "-"}}.routes.route-host{{.ID | replace "/" "-"}}] rule = "{{getFrontendRule .}}" value = "{{getFrontendValue .}}" -{{end}} \ No newline at end of file +{{end}}