1
0
Fork 0

Refactor providers and add tests

- Add a `baseProvider` struct with common
- Refactor docker, kv(s) and marathon providers (spliting into small pieces)
- Add unit tests

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
This commit is contained in:
Vincent Demeester 2015-11-13 11:50:32 +01:00
parent 3f905ee7d0
commit 4d485e1b6b
14 changed files with 2319 additions and 438 deletions

View file

@ -1,28 +1,29 @@
package provider
import (
"bytes"
"errors"
"net/url"
"strconv"
"strings"
"text/template"
"github.com/BurntSushi/toml"
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus"
"github.com/emilevauge/traefik/autogen"
"github.com/emilevauge/traefik/types"
"github.com/gambol99/go-marathon"
)
// Marathon holds configuration of the Marathon provider.
type Marathon struct {
Watch bool
baseProvider
Endpoint string
Domain string
Filename string
NetworkInterface string
marathonClient marathon.Marathon
marathonClient lightMarathonClient
}
type lightMarathonClient interface {
Applications(url.Values) (*marathon.Applications, error)
AllTasks() (*marathon.Tasks, error)
}
// Provide allows the provider to provide configurations to traefik
@ -48,7 +49,10 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage)
log.Debug("Marathon event receveived", event)
configuration := provider.loadMarathonConfig()
if configuration != nil {
configurationChan <- types.ConfigMessage{"marathon", configuration}
configurationChan <- types.ConfigMessage{
ProviderName: "marathon",
Configuration: configuration,
}
}
}
}()
@ -56,59 +60,24 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage)
}
configuration := provider.loadMarathonConfig()
configurationChan <- types.ConfigMessage{"marathon", configuration}
configurationChan <- types.ConfigMessage{
ProviderName: "marathon",
Configuration: configuration,
}
return nil
}
func (provider *Marathon) loadMarathonConfig() *types.Configuration {
var MarathonFuncMap = template.FuncMap{
"getPort": func(task marathon.Task) string {
for _, port := range task.Ports {
return strconv.Itoa(port)
}
return ""
},
"getWeight": func(task marathon.Task, applications []marathon.Application) string {
application, errApp := getApplication(task, applications)
if errApp != nil {
log.Errorf("Unable to get marathon application from task %s", task.AppID)
return "0"
}
if label, err := provider.getLabel(application, "traefik.weight"); err == nil {
return label
}
return "0"
},
"getDomain": func(application marathon.Application) string {
if label, err := provider.getLabel(application, "traefik.domain"); err == nil {
return label
}
return provider.Domain
},
"replace": func(s1 string, s2 string, s3 string) string {
return strings.Replace(s3, s1, s2, -1)
},
"getProtocol": func(task marathon.Task, applications []marathon.Application) string {
application, errApp := getApplication(task, applications)
if errApp != nil {
log.Errorf("Unable to get marathon application from task %s", task.AppID)
return "http"
}
if label, err := provider.getLabel(application, "traefik.protocol"); err == nil {
return label
}
return "http"
},
"getPassHostHeader": func(application marathon.Application) string {
if passHostHeader, err := provider.getLabel(application, "traefik.frontend.passHostHeader"); err == nil {
return passHostHeader
}
return "false"
},
"getFrontendValue": provider.GetFrontendValue,
"getFrontendRule": provider.GetFrontendRule,
"getPort": provider.getPort,
"getWeight": provider.getWeight,
"getDomain": provider.getDomain,
"getProtocol": provider.getProtocol,
"getPassHostHeader": provider.getPassHostHeader,
"getFrontendValue": provider.getFrontendValue,
"getFrontendRule": provider.getFrontendRule,
"replace": replace,
}
configuration := new(types.Configuration)
applications, err := provider.marathonClient.Applications(nil)
if err != nil {
@ -124,54 +93,12 @@ func (provider *Marathon) loadMarathonConfig() *types.Configuration {
//filter tasks
filteredTasks := fun.Filter(func(task marathon.Task) bool {
if len(task.Ports) == 0 {
log.Debug("Filtering marathon task without port %s", task.AppID)
return false
}
application, errApp := getApplication(task, applications.Apps)
if errApp != 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 healthchecks
if application.HasHealthChecks() {
if task.HasHealthCheckResults() {
for _, healthcheck := range task.HealthCheckResult {
// found one bad healthcheck, return false
if !healthcheck.Alive {
log.Debugf("Filtering marathon task %s with bad healthcheck", task.AppID)
return false
}
}
} else {
log.Debugf("Filtering marathon task %s with bad healthcheck", task.AppID)
return false
}
}
return true
return taskFilter(task, applications)
}, tasks.Tasks).([]marathon.Task)
//filter apps
filteredApps := fun.Filter(func(app marathon.Application) bool {
//get ports from app tasks
if !fun.Exists(func(task marathon.Task) bool {
if task.AppID == app.ID {
return true
}
return false
}, filteredTasks) {
return false
}
return true
return applicationFilter(app, filteredTasks)
}, applications.Apps).([]marathon.Application)
templateObjects := struct {
@ -184,41 +111,56 @@ func (provider *Marathon) loadMarathonConfig() *types.Configuration {
provider.Domain,
}
tmpl := template.New(provider.Filename).Funcs(MarathonFuncMap)
if len(provider.Filename) > 0 {
_, err := tmpl.ParseFiles(provider.Filename)
if err != nil {
log.Error("Error reading file", err)
return nil
}
} else {
buf, err := autogen.Asset("templates/marathon.tmpl")
if err != nil {
log.Error("Error reading file", err)
}
_, err = tmpl.Parse(string(buf))
if err != nil {
log.Error("Error reading file", err)
return nil
}
}
var buffer bytes.Buffer
err = tmpl.Execute(&buffer, templateObjects)
configuration, err := provider.getConfiguration("templates/marathon.tmpl", MarathonFuncMap, templateObjects)
if err != nil {
log.Error("Error with marathon template:", err)
return nil
log.Error(err)
}
if _, err := toml.Decode(buffer.String(), configuration); err != nil {
log.Error("Error creating marathon configuration:", err)
return nil
}
return configuration
}
func taskFilter(task marathon.Task, applications *marathon.Applications) bool {
if len(task.Ports) == 0 {
log.Debug("Filtering marathon task without port %s", task.AppID)
return false
}
application, errApp := getApplication(task, applications.Apps)
if errApp != 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 healthchecks
if application.HasHealthChecks() {
if task.HasHealthCheckResults() {
for _, healthcheck := range task.HealthCheckResult {
// found one bad healthcheck, return false
if !healthcheck.Alive {
log.Debugf("Filtering marathon task %s with bad healthcheck", task.AppID)
return false
}
}
} else {
log.Debugf("Filtering marathon task %s with bad healthcheck", task.AppID)
return false
}
}
return true
}
func applicationFilter(app marathon.Application, filteredTasks []marathon.Task) bool {
return fun.Exists(func(task marathon.Task) bool {
return task.AppID == app.ID
}, filteredTasks)
}
func getApplication(task marathon.Task, apps []marathon.Application) (marathon.Application, error) {
for _, application := range apps {
if application.ID == task.AppID {
@ -237,22 +179,63 @@ func (provider *Marathon) getLabel(application marathon.Application, label strin
return "", errors.New("Label not found:" + label)
}
func (provider *Marathon) getEscapedName(name string) string {
return strings.Replace(name, "/", "", -1)
func (provider *Marathon) getPort(task marathon.Task) string {
for _, port := range task.Ports {
return strconv.Itoa(port)
}
return ""
}
// GetFrontendValue returns the frontend value for the specified application, using
func (provider *Marathon) getWeight(task marathon.Task, applications []marathon.Application) string {
application, errApp := getApplication(task, applications)
if errApp != nil {
log.Errorf("Unable to get marathon application from task %s", task.AppID)
return "0"
}
if label, err := provider.getLabel(application, "traefik.weight"); err == nil {
return label
}
return "0"
}
func (provider *Marathon) getDomain(application marathon.Application) string {
if label, err := provider.getLabel(application, "traefik.domain"); err == nil {
return label
}
return provider.Domain
}
func (provider *Marathon) getProtocol(task marathon.Task, applications []marathon.Application) string {
application, errApp := getApplication(task, applications)
if errApp != nil {
log.Errorf("Unable to get marathon application from task %s", task.AppID)
return "http"
}
if label, err := provider.getLabel(application, "traefik.protocol"); err == nil {
return label
}
return "http"
}
func (provider *Marathon) getPassHostHeader(application marathon.Application) string {
if passHostHeader, err := provider.getLabel(application, "traefik.frontend.passHostHeader"); err == nil {
return passHostHeader
}
return "false"
}
// getFrontendValue returns the frontend value for the specified application, using
// it's label. It returns a default one if the label is not present.
func (provider *Marathon) GetFrontendValue(application marathon.Application) string {
func (provider *Marathon) 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
return getEscapedName(application.ID) + "." + provider.Domain
}
// GetFrontendRule returns the frontend rule for the specified application, using
// getFrontendRule returns the frontend rule for the specified application, using
// it's label. It returns a default one (Host) if the label is not present.
func (provider *Marathon) GetFrontendRule(application marathon.Application) string {
func (provider *Marathon) getFrontendRule(application marathon.Application) string {
if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil {
return label
}