From d671cc382145a1b6c41049ebd52975abb9cb50d1 Mon Sep 17 00:00:00 2001 From: emile Date: Fri, 23 Oct 2015 09:49:19 +0200 Subject: [PATCH] Adds traefik.frontend. rule and value labels in Docker and Marathon. Fixes #64. Fixes #73 --- docker.go | 84 +++++++++++++++++++++++----------- marathon.go | 74 ++++++++++++++++++++---------- templates/docker.tmpl | 13 +++--- templates/marathon-prefix.tmpl | 27 ----------- templates/marathon.tmpl | 15 ++---- tests/whoami.json | 39 ++++++++++++++++ 6 files changed, 157 insertions(+), 95 deletions(-) delete mode 100644 templates/marathon-prefix.tmpl create mode 100644 tests/whoami.json diff --git a/docker.go b/docker.go index 017ef596c..bef97ba8c 100644 --- a/docker.go +++ b/docker.go @@ -73,18 +73,14 @@ func (provider *DockerProvider) Provide(configurationChan chan<- configMessage) func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *Configuration { var DockerFuncMap = template.FuncMap{ "getBackend": func(container docker.Container) string { - for key, value := range container.Config.Labels { - if key == "traefik.backend" { - return value - } + if label, err := provider.getLabel(container, "traefik.backend"); err == nil { + return label } - return getHost(container) + return provider.getEscapedName(container.Name) }, "getPort": func(container docker.Container) string { - for key, value := range container.Config.Labels { - if key == "traefik.port" { - return value - } + if label, err := provider.getLabel(container, "traefik.port"); err == nil { + return label } for key := range container.NetworkSettings.Ports { return key.Port() @@ -92,30 +88,33 @@ func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *C return "" }, "getWeight": func(container docker.Container) string { - for key, value := range container.Config.Labels { - if key == "traefik.weight" { - return value - } + if label, err := provider.getLabel(container, "traefik.weight"); err == nil { + return label } return "0" }, "getDomain": func(container docker.Container) string { - for key, value := range container.Config.Labels { - if key == "traefik.domain" { - return value - } + if label, err := provider.getLabel(container, "traefik.domain"); err == nil { + return label } return provider.Domain }, + "getProtocole": func(container docker.Container) string { + if label, err := provider.getLabel(container, "traefik.protocole"); err == nil { + return label + } + return "http" + }, + "getFrontendValue": provider.GetFrontendValue, + "getFrontendRule": provider.GetFrontendRule, "replace": func(s1 string, s2 string, s3 string) string { return strings.Replace(s3, s1, s2, -1) }, - "getHost": getHost, } configuration := new(Configuration) containerList, _ := dockerClient.ListContainers(docker.ListContainersOptions{}) containersInspected := []docker.Container{} - hosts := map[string][]docker.Container{} + frontends := map[string][]docker.Container{} // get inspect containers for _, container := range containerList { @@ -138,20 +137,28 @@ func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *C log.Debugf("Filtering disabled container %s", container.Name) return false } + _, errRule := provider.getLabel(container, "traefik.frontend.rule") + _, errValue := provider.getLabel(container, "traefik.frontend.value") + + if errRule != nil && errValue == nil || errRule == nil && errValue != nil { + log.Debugf("Filtering bad labeled container %s", container.Name) + return false + } + return true }, containersInspected).([]docker.Container) for _, container := range filteredContainers { - hosts[getHost(container)] = append(hosts[getHost(container)], container) + frontends[provider.getFrontendName(container)] = append(frontends[provider.getFrontendName(container)], container) } templateObjects := struct { Containers []docker.Container - Hosts map[string][]docker.Container + Frontends map[string][]docker.Container Domain string }{ filteredContainers, - hosts, + frontends, provider.Domain, } tmpl := template.New(provider.Filename).Funcs(DockerFuncMap) @@ -181,17 +188,40 @@ func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *C } if _, err := toml.Decode(buffer.String(), configuration); err != nil { - log.Error("Error creating docker configuration", err) + log.Error("Error creating docker configuration ", err) return nil } return configuration } -func getHost(container docker.Container) string { +func (provider *DockerProvider) getFrontendName(container docker.Container) string { + // Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78 + return strings.Replace(provider.GetFrontendRule(container)+"-"+provider.GetFrontendValue(container), ".", "-", -1) +} + +func (provider *DockerProvider) getEscapedName(name string) string { + return strings.Replace(name, "/", "", -1) +} + +func (provider *DockerProvider) getLabel(container docker.Container, label string) (string, error) { for key, value := range container.Config.Labels { - if key == "traefik.host" { - return value + if key == label { + return value, nil } } - return strings.Replace(strings.Replace(container.Name, "/", "", -1), ".", "-", -1) + return "", errors.New("Label not found:" + label) +} + +func (provider *DockerProvider) GetFrontendValue(container docker.Container) string { + if label, err := provider.getLabel(container, "traefik.frontend.value"); err == nil { + return label + } + return provider.getEscapedName(container.Name) + "." + provider.Domain +} + +func (provider *DockerProvider) GetFrontendRule(container docker.Container) string { + if label, err := provider.getLabel(container, "traefik.frontend.rule"); err == nil { + return label + } + return "Host" } diff --git a/marathon.go b/marathon.go index c852ce8c1..f3e345344 100644 --- a/marathon.go +++ b/marathon.go @@ -6,6 +6,7 @@ import ( "strings" "text/template" + "errors" "github.com/BurntSushi/toml" "github.com/BurntSushi/ty/fun" log "github.com/Sirupsen/logrus" @@ -62,41 +63,39 @@ func (provider *MarathonProvider) loadMarathonConfig() *Configuration { } return "" }, - "getHost": func(application marathon.Application) string { - for key, value := range application.Labels { - if key == "traefik.host" { - return value - } + "getWeight": func(task marathon.Task, applications []marathon.Application) string { + application := getApplication(task, applications) + if application == nil { + log.Errorf("Unable to get marathon application from task %s", task.AppID) + return "0" } - return strings.Replace(application.ID, "/", "", 1) - }, - "getWeight": func(application marathon.Application) string { - for key, value := range application.Labels { - if key == "traefik.weight" { - return value - } + if label, err := provider.getLabel(*application, "traefik.weight"); err == nil { + return label } return "0" }, "getDomain": func(application marathon.Application) string { - for key, value := range application.Labels { - if key == "traefik.domain" { - return value - } + if label, err := provider.getLabel(application, "traefik.domain"); err == nil { + return label } return provider.Domain }, - "getPrefixes": func(application marathon.Application) ([]string, error) { - for key, value := range application.Labels { - if key == "traefik.prefixes" { - return strings.Split(value, ","), nil - } - } - return []string{}, nil - }, "replace": func(s1 string, s2 string, s3 string) string { return strings.Replace(s3, s1, s2, -1) }, + "getProtocole": func(task marathon.Task, applications []marathon.Application) string { + application := getApplication(task, applications) + if application == nil { + log.Errorf("Unable to get marathon application from task %s", task.AppID) + return "http" + } + if label, err := provider.getLabel(*application, "traefik.protocole"); err == nil { + return label + } + return "http" + }, + "getFrontendValue": provider.GetFrontendValue, + "getFrontendRule": provider.GetFrontendRule, } configuration := new(Configuration) @@ -202,3 +201,30 @@ func getApplication(task marathon.Task, apps []marathon.Application) *marathon.A } return nil } + +func (provider *MarathonProvider) getLabel(application marathon.Application, label string) (string, error) { + for key, value := range application.Labels { + if key == label { + return value, nil + } + } + return "", errors.New("Label not found:" + label) +} + +func (provider *MarathonProvider) getEscapedName(name string) string { + return strings.Replace(name, "/", "", -1) +} + +func (provider *MarathonProvider) GetFrontendValue(application marathon.Application) string { + if label, err := provider.getLabel(application, "traefik.frontend.value"); err == nil { + return label + } + return provider.getEscapedName(application.ID) + "." + provider.Domain +} + +func (provider *MarathonProvider) GetFrontendRule(application marathon.Application) string { + if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil { + return label + } + return "Host" +} diff --git a/templates/docker.tmpl b/templates/docker.tmpl index 9dfd6eac4..60da39130 100644 --- a/templates/docker.tmpl +++ b/templates/docker.tmpl @@ -1,14 +1,13 @@ [backends]{{range .Containers}} [backends.backend-{{getBackend .}}.servers.server-{{.Name | replace "/" "" | replace "." "-"}}] - url = "http://{{.NetworkSettings.IPAddress}}:{{getPort .}}" + url = "{{getProtocole .}}://{{.NetworkSettings.IPAddress}}:{{getPort .}}" weight = {{getWeight .}} {{end}} -[frontends]{{range $host, $containers := .Hosts}} - [frontends.frontend-{{$host}}] - {{$container := index $containers 0}} +[frontends]{{range $frontend, $containers := .Frontends}} + [frontends."frontend-{{$frontend}}"]{{$container := index $containers 0}} backend = "backend-{{getBackend $container}}" - [frontends.frontend-{{$host}}.routes.route-host-{{$host}}] - rule = "Host" - value = "{{$host}}.{{getDomain $container}}" + [frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"] + rule = "{{getFrontendRule $container}}" + value = "{{getFrontendValue $container}}" {{end}} \ No newline at end of file diff --git a/templates/marathon-prefix.tmpl b/templates/marathon-prefix.tmpl deleted file mode 100644 index 1a331626a..000000000 --- a/templates/marathon-prefix.tmpl +++ /dev/null @@ -1,27 +0,0 @@ -{{$apps := .Applications}} -[backends]{{range .Tasks}} - [backends.backend{{.AppID | replace "/" "-"}}.servers.server-{{.ID | replace "." "-"}}] - url = "http://{{.Host}}:{{getPort .}}" - {{$appID := .AppID}} - {{range $apps}} - {{if eq $appID .ID}} - weight = {{getWeight .}} - {{end}} - {{end}} -{{end}} - -[frontends]{{ range $app := .Applications}} - {{range $prefix := getPrefixes .}} - [frontends.frontend{{$app.ID | replace "/" "-"}}{{$prefix | replace "/" "-"}}] - backend = "backend{{$app.ID | replace "/" "-"}}" - [frontends.frontend-{{getHost $app | replace "/" "-"}}{{$prefix | replace "/" "-"}}.routes.route-prefix{{$prefix | replace "/" "-"}}] - rule = "PathPrefix" - value = "{{.}}" - {{else}} - [frontends.frontend{{.ID | replace "/" "-"}}] - backend = "backend{{.ID | replace "/" "-"}}" - [frontends.frontend-{{getHost $app | replace "/" "-"}}.routes.route-host-{{getHost $app | replace "/" "-"}}] - rule = "Host" - value = "{{getHost $app | replace "/" "-"}}.{{getDomain .}}" - {{end}} -{{end}} diff --git a/templates/marathon.tmpl b/templates/marathon.tmpl index 10f1fa249..ba491ac47 100644 --- a/templates/marathon.tmpl +++ b/templates/marathon.tmpl @@ -1,19 +1,14 @@ {{$apps := .Applications}} [backends]{{range .Tasks}} [backends.backend{{.AppID | replace "/" "-"}}.servers.server-{{.ID | replace "." "-"}}] - url = "http://{{.Host}}:{{getPort .}}" - {{$appID := .AppID}} - {{range $apps}} - {{if eq $appID .ID}} - weight = {{getWeight .}} - {{end}} - {{end}} + url = "{{getProtocole . $apps}}://{{.Host}}:{{getPort .}}" + weight = {{getWeight . $apps}} {{end}} [frontends]{{range .Applications}} [frontends.frontend{{.ID | replace "/" "-"}}] backend = "backend{{.ID | replace "/" "-"}}" - [frontends.frontend-{{getHost . | replace "/" "-"}}.routes.route-host-{{getHost . | replace "/" "-"}}] - rule = "Host" - value = "{{getHost . | replace "/" "-"}}.{{getDomain .}}" + [frontends.frontend-{{.ID | replace "/" ""}}.routes.route-host-{{.ID | replace "/" ""}}] + rule = "{{getFrontendRule .}}" + value = "{{getFrontendValue .}}" {{end}} \ No newline at end of file diff --git a/tests/whoami.json b/tests/whoami.json new file mode 100644 index 000000000..741d399d8 --- /dev/null +++ b/tests/whoami.json @@ -0,0 +1,39 @@ +{ + "id": "whoami", + "cpus": 0.1, + "mem": 64.0, + "instances": 3, + "container": { + "type": "DOCKER", + "docker": { + "image": "emilevauge/whoami", + "network": "BRIDGE", + "portMappings": [ + { "containerPort": 80, "hostPort": 0, "protocol": "tcp" } + ], + "parameters": [{ + "key": "log-driver", + "value": "gelf" + }, { + "key": "log-opt", + "value": "gelf-address=udp://172.17.42.1:12201" + }] + } + }, + "healthChecks": [ + { + "protocol": "HTTP", + "portIndex": 0, + "path": "/", + "gracePeriodSeconds": 5, + "intervalSeconds": 20, + "maxConsecutiveFailures": 3 + } + ], + "labels": { + "traefik.weight": "1", + "traefik.protocole": "https", + "traefik.frontend.rule": "Path", + "traefik.frontend.value": "/test" + } +}