Adds Marathon support.
Co-authored-by: Julien Salleyron <julien@containo.us>
This commit is contained in:
parent
a433e469cc
commit
246b245959
22 changed files with 2223 additions and 2203 deletions
|
@ -15,13 +15,13 @@ import (
|
||||||
"github.com/containous/traefik/old/provider/etcd"
|
"github.com/containous/traefik/old/provider/etcd"
|
||||||
"github.com/containous/traefik/old/provider/eureka"
|
"github.com/containous/traefik/old/provider/eureka"
|
||||||
"github.com/containous/traefik/old/provider/kubernetes"
|
"github.com/containous/traefik/old/provider/kubernetes"
|
||||||
"github.com/containous/traefik/old/provider/marathon"
|
|
||||||
"github.com/containous/traefik/old/provider/mesos"
|
"github.com/containous/traefik/old/provider/mesos"
|
||||||
"github.com/containous/traefik/old/provider/rancher"
|
"github.com/containous/traefik/old/provider/rancher"
|
||||||
"github.com/containous/traefik/old/provider/zk"
|
"github.com/containous/traefik/old/provider/zk"
|
||||||
"github.com/containous/traefik/ping"
|
"github.com/containous/traefik/ping"
|
||||||
"github.com/containous/traefik/provider/docker"
|
"github.com/containous/traefik/provider/docker"
|
||||||
"github.com/containous/traefik/provider/file"
|
"github.com/containous/traefik/provider/file"
|
||||||
|
"github.com/containous/traefik/provider/marathon"
|
||||||
"github.com/containous/traefik/provider/rest"
|
"github.com/containous/traefik/provider/rest"
|
||||||
"github.com/containous/traefik/tracing/datadog"
|
"github.com/containous/traefik/tracing/datadog"
|
||||||
"github.com/containous/traefik/tracing/jaeger"
|
"github.com/containous/traefik/tracing/jaeger"
|
||||||
|
@ -170,6 +170,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||||
defaultMarathon.ResponseHeaderTimeout = parse.Duration(60 * time.Second)
|
defaultMarathon.ResponseHeaderTimeout = parse.Duration(60 * time.Second)
|
||||||
defaultMarathon.TLSHandshakeTimeout = parse.Duration(5 * time.Second)
|
defaultMarathon.TLSHandshakeTimeout = parse.Duration(5 * time.Second)
|
||||||
defaultMarathon.KeepAlive = parse.Duration(10 * time.Second)
|
defaultMarathon.KeepAlive = parse.Duration(10 * time.Second)
|
||||||
|
defaultMarathon.DefaultRule = marathon.DefaultTemplateRule
|
||||||
|
|
||||||
// default Consul
|
// default Consul
|
||||||
var defaultConsul consul.Provider
|
var defaultConsul consul.Provider
|
||||||
|
|
|
@ -16,7 +16,6 @@ import (
|
||||||
"github.com/containous/traefik/old/provider/etcd"
|
"github.com/containous/traefik/old/provider/etcd"
|
||||||
"github.com/containous/traefik/old/provider/eureka"
|
"github.com/containous/traefik/old/provider/eureka"
|
||||||
"github.com/containous/traefik/old/provider/kubernetes"
|
"github.com/containous/traefik/old/provider/kubernetes"
|
||||||
"github.com/containous/traefik/old/provider/marathon"
|
|
||||||
"github.com/containous/traefik/old/provider/mesos"
|
"github.com/containous/traefik/old/provider/mesos"
|
||||||
"github.com/containous/traefik/old/provider/rancher"
|
"github.com/containous/traefik/old/provider/rancher"
|
||||||
"github.com/containous/traefik/old/provider/zk"
|
"github.com/containous/traefik/old/provider/zk"
|
||||||
|
@ -24,6 +23,7 @@ import (
|
||||||
acmeprovider "github.com/containous/traefik/provider/acme"
|
acmeprovider "github.com/containous/traefik/provider/acme"
|
||||||
"github.com/containous/traefik/provider/docker"
|
"github.com/containous/traefik/provider/docker"
|
||||||
"github.com/containous/traefik/provider/file"
|
"github.com/containous/traefik/provider/file"
|
||||||
|
"github.com/containous/traefik/provider/marathon"
|
||||||
"github.com/containous/traefik/provider/rest"
|
"github.com/containous/traefik/provider/rest"
|
||||||
"github.com/containous/traefik/tls"
|
"github.com/containous/traefik/tls"
|
||||||
"github.com/containous/traefik/tracing/datadog"
|
"github.com/containous/traefik/tracing/datadog"
|
||||||
|
|
|
@ -43,12 +43,8 @@ func init() {
|
||||||
// check.Suite(&ConsulSuite{})
|
// check.Suite(&ConsulSuite{})
|
||||||
// check.Suite(&DynamoDBSuite{})
|
// check.Suite(&DynamoDBSuite{})
|
||||||
// check.Suite(&EurekaSuite{})
|
// check.Suite(&EurekaSuite{})
|
||||||
// check.Suite(&MarathonSuite{})
|
|
||||||
// check.Suite(&MarathonSuite15{})
|
|
||||||
// check.Suite(&MesosSuite{})
|
// check.Suite(&MesosSuite{})
|
||||||
|
|
||||||
// FIXME use docker
|
|
||||||
|
|
||||||
// FIXME use consulcatalog
|
// FIXME use consulcatalog
|
||||||
// check.Suite(&ConstraintSuite{})
|
// check.Suite(&ConstraintSuite{})
|
||||||
|
|
||||||
|
@ -64,6 +60,8 @@ func init() {
|
||||||
check.Suite(&HostResolverSuite{})
|
check.Suite(&HostResolverSuite{})
|
||||||
check.Suite(&HTTPSSuite{})
|
check.Suite(&HTTPSSuite{})
|
||||||
check.Suite(&LogRotationSuite{})
|
check.Suite(&LogRotationSuite{})
|
||||||
|
check.Suite(&MarathonSuite{})
|
||||||
|
check.Suite(&MarathonSuite15{})
|
||||||
check.Suite(&RateLimitSuite{})
|
check.Suite(&RateLimitSuite{})
|
||||||
check.Suite(&RestSuite{})
|
check.Suite(&RestSuite{})
|
||||||
check.Suite(&RetrySuite{})
|
check.Suite(&RetrySuite{})
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containous/traefik/integration/try"
|
"github.com/containous/traefik/integration/try"
|
||||||
"github.com/containous/traefik/old/provider/label"
|
|
||||||
"github.com/gambol99/go-marathon"
|
"github.com/gambol99/go-marathon"
|
||||||
"github.com/go-check/check"
|
"github.com/go-check/check"
|
||||||
checker "github.com/vdemeester/shakers"
|
checker "github.com/vdemeester/shakers"
|
||||||
|
@ -98,7 +97,7 @@ func (s *MarathonSuite15) TestConfigurationUpdate(c *check.C) {
|
||||||
CPU(0.1).
|
CPU(0.1).
|
||||||
Memory(32).
|
Memory(32).
|
||||||
EmptyNetworks().
|
EmptyNetworks().
|
||||||
AddLabel(label.TraefikFrontendRule, "PathPrefix:/service")
|
AddLabel("traefik.Routers.rt.Rule", "PathPrefix:/service")
|
||||||
app.Container.
|
app.Container.
|
||||||
Expose(80).
|
Expose(80).
|
||||||
Docker.
|
Docker.
|
||||||
|
@ -118,7 +117,7 @@ func (s *MarathonSuite15) TestConfigurationUpdate(c *check.C) {
|
||||||
CPU(0.1).
|
CPU(0.1).
|
||||||
Memory(32).
|
Memory(32).
|
||||||
EmptyNetworks().
|
EmptyNetworks().
|
||||||
AddLabel(label.Prefix+"app"+label.TraefikFrontendRule, "PathPrefix:/app")
|
AddLabel("traefik.Routers.app.Rule", "PathPrefix:/app")
|
||||||
app.Container.
|
app.Container.
|
||||||
Expose(80).
|
Expose(80).
|
||||||
Docker.
|
Docker.
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containous/traefik/integration/try"
|
"github.com/containous/traefik/integration/try"
|
||||||
"github.com/containous/traefik/old/provider/label"
|
|
||||||
"github.com/gambol99/go-marathon"
|
"github.com/gambol99/go-marathon"
|
||||||
"github.com/go-check/check"
|
"github.com/go-check/check"
|
||||||
checker "github.com/vdemeester/shakers"
|
checker "github.com/vdemeester/shakers"
|
||||||
|
@ -109,7 +108,7 @@ func (s *MarathonSuite) TestConfigurationUpdate(c *check.C) {
|
||||||
Name("/whoami").
|
Name("/whoami").
|
||||||
CPU(0.1).
|
CPU(0.1).
|
||||||
Memory(32).
|
Memory(32).
|
||||||
AddLabel(label.TraefikFrontendRule, "PathPrefix:/service")
|
AddLabel("traefik.Routers.rt.Rule", "PathPrefix:/service")
|
||||||
app.Container.Docker.Bridged().
|
app.Container.Docker.Bridged().
|
||||||
Expose(80).
|
Expose(80).
|
||||||
Container("containous/whoami")
|
Container("containous/whoami")
|
||||||
|
@ -126,7 +125,7 @@ func (s *MarathonSuite) TestConfigurationUpdate(c *check.C) {
|
||||||
Name("/whoami").
|
Name("/whoami").
|
||||||
CPU(0.1).
|
CPU(0.1).
|
||||||
Memory(32).
|
Memory(32).
|
||||||
AddLabel(label.Prefix+"app"+label.TraefikFrontendRule, "PathPrefix:/app")
|
AddLabel("traefik.Routers.app.Rule", "PathPrefix:/app")
|
||||||
app.Container.Docker.Bridged().
|
app.Container.Docker.Bridged().
|
||||||
Expose(80).
|
Expose(80).
|
||||||
Container("containous/whoami")
|
Container("containous/whoami")
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import "github.com/sirupsen/logrus"
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
// Debug logs a message at level Debug on the standard logger.
|
// Debug logs a message at level Debug on the standard logger.
|
||||||
// Deprecated
|
// Deprecated
|
||||||
|
@ -78,3 +84,56 @@ func Fatalf(format string, args ...interface{}) {
|
||||||
func AddHook(hook logrus.Hook) {
|
func AddHook(hook logrus.Hook) {
|
||||||
logrus.AddHook(hook)
|
logrus.AddHook(hook)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CustomWriterLevel logs writer for a specific level. (with a custom scanner buffer size.)
|
||||||
|
// adapted from github.com/Sirupsen/logrus/writer.go
|
||||||
|
func CustomWriterLevel(level logrus.Level, maxScanTokenSize int) *io.PipeWriter {
|
||||||
|
reader, writer := io.Pipe()
|
||||||
|
|
||||||
|
var printFunc func(args ...interface{})
|
||||||
|
|
||||||
|
switch level {
|
||||||
|
case logrus.DebugLevel:
|
||||||
|
printFunc = Debug
|
||||||
|
case logrus.InfoLevel:
|
||||||
|
printFunc = Info
|
||||||
|
case logrus.WarnLevel:
|
||||||
|
printFunc = Warn
|
||||||
|
case logrus.ErrorLevel:
|
||||||
|
printFunc = Error
|
||||||
|
case logrus.FatalLevel:
|
||||||
|
printFunc = Fatal
|
||||||
|
case logrus.PanicLevel:
|
||||||
|
printFunc = Panic
|
||||||
|
default:
|
||||||
|
printFunc = mainLogger.Print
|
||||||
|
}
|
||||||
|
|
||||||
|
go writerScanner(reader, maxScanTokenSize, printFunc)
|
||||||
|
runtime.SetFinalizer(writer, writerFinalizer)
|
||||||
|
|
||||||
|
return writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract from github.com/Sirupsen/logrus/writer.go
|
||||||
|
// Hack the buffer size
|
||||||
|
func writerScanner(reader io.ReadCloser, scanTokenSize int, printFunc func(args ...interface{})) {
|
||||||
|
scanner := bufio.NewScanner(reader)
|
||||||
|
|
||||||
|
if scanTokenSize > bufio.MaxScanTokenSize {
|
||||||
|
buf := make([]byte, bufio.MaxScanTokenSize)
|
||||||
|
scanner.Buffer(buf, scanTokenSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
printFunc(scanner.Text())
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
Errorf("Error while reading from Writer: %s", err)
|
||||||
|
}
|
||||||
|
reader.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func writerFinalizer(writer *io.PipeWriter) {
|
||||||
|
writer.Close()
|
||||||
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ import (
|
||||||
"github.com/containous/traefik/old/provider/etcd"
|
"github.com/containous/traefik/old/provider/etcd"
|
||||||
"github.com/containous/traefik/old/provider/eureka"
|
"github.com/containous/traefik/old/provider/eureka"
|
||||||
"github.com/containous/traefik/old/provider/kubernetes"
|
"github.com/containous/traefik/old/provider/kubernetes"
|
||||||
"github.com/containous/traefik/old/provider/marathon"
|
|
||||||
"github.com/containous/traefik/old/provider/mesos"
|
"github.com/containous/traefik/old/provider/mesos"
|
||||||
"github.com/containous/traefik/old/provider/rancher"
|
"github.com/containous/traefik/old/provider/rancher"
|
||||||
"github.com/containous/traefik/old/provider/rest"
|
"github.com/containous/traefik/old/provider/rest"
|
||||||
|
@ -88,7 +87,6 @@ type GlobalConfiguration struct {
|
||||||
KeepTrailingSlash bool `description:"Do not remove trailing slash." export:"true"` // Deprecated
|
KeepTrailingSlash bool `description:"Do not remove trailing slash." export:"true"` // Deprecated
|
||||||
Docker *docker.Provider `description:"Enable Docker backend with default settings" export:"true"`
|
Docker *docker.Provider `description:"Enable Docker backend with default settings" export:"true"`
|
||||||
File *file.Provider `description:"Enable File backend with default settings" export:"true"`
|
File *file.Provider `description:"Enable File backend with default settings" export:"true"`
|
||||||
Marathon *marathon.Provider `description:"Enable Marathon backend with default settings" export:"true"`
|
|
||||||
Consul *consul.Provider `description:"Enable Consul backend with default settings" export:"true"`
|
Consul *consul.Provider `description:"Enable Consul backend with default settings" export:"true"`
|
||||||
ConsulCatalog *consulcatalog.Provider `description:"Enable Consul catalog backend with default settings" export:"true"`
|
ConsulCatalog *consulcatalog.Provider `description:"Enable Consul catalog backend with default settings" export:"true"`
|
||||||
Etcd *etcd.Provider `description:"Enable Etcd backend with default settings" export:"true"`
|
Etcd *etcd.Provider `description:"Enable Etcd backend with default settings" export:"true"`
|
||||||
|
|
|
@ -1,390 +0,0 @@
|
||||||
package marathon
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/containous/traefik/old/log"
|
|
||||||
"github.com/containous/traefik/old/provider"
|
|
||||||
"github.com/containous/traefik/old/provider/label"
|
|
||||||
"github.com/containous/traefik/old/types"
|
|
||||||
"github.com/gambol99/go-marathon"
|
|
||||||
)
|
|
||||||
|
|
||||||
type appData struct {
|
|
||||||
marathon.Application
|
|
||||||
SegmentLabels map[string]string
|
|
||||||
SegmentName string
|
|
||||||
LinkedApps []*appData
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) buildConfiguration(applications *marathon.Applications) *types.Configuration {
|
|
||||||
var MarathonFuncMap = template.FuncMap{
|
|
||||||
"getDomain": label.GetFuncString(label.TraefikDomain, p.Domain), // see https://github.com/containous/traefik/pull/1693
|
|
||||||
"getSubDomain": p.getSubDomain, // see https://github.com/containous/traefik/pull/1693
|
|
||||||
"getBackendName": p.getBackendName,
|
|
||||||
|
|
||||||
// Backend functions
|
|
||||||
"getPort": getPort,
|
|
||||||
"getCircuitBreaker": label.GetCircuitBreaker,
|
|
||||||
"getLoadBalancer": label.GetLoadBalancer,
|
|
||||||
"getMaxConn": label.GetMaxConn,
|
|
||||||
"getHealthCheck": label.GetHealthCheck,
|
|
||||||
"getBuffering": label.GetBuffering,
|
|
||||||
"getResponseForwarding": label.GetResponseForwarding,
|
|
||||||
"getServers": p.getServers,
|
|
||||||
|
|
||||||
// Frontend functions
|
|
||||||
"getSegmentNameSuffix": getSegmentNameSuffix,
|
|
||||||
"getFrontendRule": p.getFrontendRule,
|
|
||||||
"getFrontendName": p.getFrontendName,
|
|
||||||
"getPassHostHeader": label.GetFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader),
|
|
||||||
"getPassTLSCert": label.GetFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
|
|
||||||
"getPassTLSClientCert": label.GetTLSClientCert,
|
|
||||||
"getPriority": label.GetFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriority),
|
|
||||||
"getEntryPoints": label.GetFuncSliceString(label.TraefikFrontendEntryPoints),
|
|
||||||
"getBasicAuth": label.GetFuncSliceString(label.TraefikFrontendAuthBasic), // Deprecated
|
|
||||||
"getAuth": label.GetAuth,
|
|
||||||
"getRedirect": label.GetRedirect,
|
|
||||||
"getErrorPages": label.GetErrorPages,
|
|
||||||
"getRateLimit": label.GetRateLimit,
|
|
||||||
"getHeaders": label.GetHeaders,
|
|
||||||
"getWhiteList": label.GetWhiteList,
|
|
||||||
}
|
|
||||||
|
|
||||||
apps := make(map[string]*appData)
|
|
||||||
for _, app := range applications.Apps {
|
|
||||||
if p.applicationFilter(app) {
|
|
||||||
// Tasks
|
|
||||||
var filteredTasks []*marathon.Task
|
|
||||||
for _, task := range app.Tasks {
|
|
||||||
if p.taskFilter(*task, app) {
|
|
||||||
filteredTasks = append(filteredTasks, task)
|
|
||||||
logIllegalServices(*task, app)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
app.Tasks = filteredTasks
|
|
||||||
|
|
||||||
// segments
|
|
||||||
segmentProperties := label.ExtractTraefikLabels(stringValueMap(app.Labels))
|
|
||||||
for segmentName, labels := range segmentProperties {
|
|
||||||
data := &appData{
|
|
||||||
Application: app,
|
|
||||||
SegmentLabels: labels,
|
|
||||||
SegmentName: segmentName,
|
|
||||||
}
|
|
||||||
|
|
||||||
backendName := p.getBackendName(*data)
|
|
||||||
if baseApp, ok := apps[backendName]; ok {
|
|
||||||
baseApp.LinkedApps = append(baseApp.LinkedApps, data)
|
|
||||||
} else {
|
|
||||||
apps[backendName] = data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
templateObjects := struct {
|
|
||||||
Applications map[string]*appData
|
|
||||||
Domain string
|
|
||||||
}{
|
|
||||||
Applications: apps,
|
|
||||||
Domain: p.Domain,
|
|
||||||
}
|
|
||||||
|
|
||||||
configuration, err := p.GetConfiguration("templates/marathon.tmpl", MarathonFuncMap, templateObjects)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to render Marathon configuration template: %v", err)
|
|
||||||
}
|
|
||||||
return configuration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) applicationFilter(app marathon.Application) bool {
|
|
||||||
// Filter disabled application.
|
|
||||||
if !label.IsEnabled(stringValueMap(app.Labels), p.ExposedByDefault) {
|
|
||||||
log.Debugf("Filtering disabled Marathon application %s", app.ID)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter by constraints.
|
|
||||||
constraintTags := label.GetSliceStringValue(stringValueMap(app.Labels), label.TraefikTags)
|
|
||||||
if p.MarathonLBCompatibility {
|
|
||||||
if haGroup := label.GetStringValue(stringValueMap(app.Labels), labelLbCompatibilityGroup, ""); len(haGroup) > 0 {
|
|
||||||
constraintTags = append(constraintTags, haGroup)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 %s pruned by %q constraint", app.ID, failingConstraint.String())
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) taskFilter(task marathon.Task, application marathon.Application) bool {
|
|
||||||
if task.State != string(taskStateRunning) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if ready := p.readyChecker.Do(task, application); !ready {
|
|
||||||
log.Infof("Filtering unready task %s from application %s", task.ID, application.ID)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// logIllegalServices logs illegal service configurations.
|
|
||||||
// While we cannot filter on the service level, they will eventually get
|
|
||||||
// rejected once the server configuration is rendered.
|
|
||||||
func logIllegalServices(task marathon.Task, app marathon.Application) {
|
|
||||||
segmentProperties := label.ExtractTraefikLabels(stringValueMap(app.Labels))
|
|
||||||
for segmentName, labels := range segmentProperties {
|
|
||||||
// Check for illegal/missing ports.
|
|
||||||
if _, err := processPorts(app, task, labels); err != nil {
|
|
||||||
log.Warnf("%s has an illegal configuration: no proper port available", identifier(app, task, segmentName))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for illegal port label combinations.
|
|
||||||
hasPortLabel := label.Has(labels, label.TraefikPort)
|
|
||||||
hasPortIndexLabel := label.Has(labels, label.TraefikPortIndex)
|
|
||||||
if hasPortLabel && hasPortIndexLabel {
|
|
||||||
log.Warnf("%s has both port and port index specified; port will take precedence", identifier(app, task, segmentName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSegmentNameSuffix(serviceName string) string {
|
|
||||||
if len(serviceName) > 0 {
|
|
||||||
return "-service-" + provider.Normalize(serviceName)
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) getSubDomain(name string) string {
|
|
||||||
if p.GroupsAsSubDomains {
|
|
||||||
splitedName := strings.Split(strings.TrimPrefix(name, "/"), "/")
|
|
||||||
provider.ReverseStringSlice(&splitedName)
|
|
||||||
reverseName := strings.Join(splitedName, ".")
|
|
||||||
return reverseName
|
|
||||||
}
|
|
||||||
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) getBackendName(app appData) string {
|
|
||||||
value := label.GetStringValue(app.SegmentLabels, label.TraefikBackend, "")
|
|
||||||
if len(value) > 0 {
|
|
||||||
return provider.Normalize("backend" + value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider.Normalize("backend" + app.ID + getSegmentNameSuffix(app.SegmentName))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) getFrontendName(app appData) string {
|
|
||||||
return provider.Normalize("frontend" + app.ID + getSegmentNameSuffix(app.SegmentName))
|
|
||||||
}
|
|
||||||
|
|
||||||
// getFrontendRule returns the frontend rule for the specified application, using
|
|
||||||
// its label. If service is provided, it will look for serviceName label before generic one.
|
|
||||||
// It returns a default one (Host) if the label is not present.
|
|
||||||
func (p *Provider) getFrontendRule(app appData) string {
|
|
||||||
if value := label.GetStringValue(app.SegmentLabels, label.TraefikFrontendRule, ""); len(value) > 0 {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.MarathonLBCompatibility {
|
|
||||||
if value := label.GetStringValue(stringValueMap(app.Labels), labelLbCompatibility, ""); len(value) > 0 {
|
|
||||||
return "Host:" + value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
domain := label.GetStringValue(app.SegmentLabels, label.TraefikDomain, p.Domain)
|
|
||||||
if len(domain) > 0 {
|
|
||||||
domain = "." + domain
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(app.SegmentName) > 0 {
|
|
||||||
return "Host:" + strings.ToLower(provider.Normalize(app.SegmentName)) + "." + p.getSubDomain(app.ID) + domain
|
|
||||||
}
|
|
||||||
return "Host:" + p.getSubDomain(app.ID) + domain
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPort(task marathon.Task, app appData) string {
|
|
||||||
port, err := processPorts(app.Application, task, app.SegmentLabels)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Unable to process ports for %s: %s", identifier(app.Application, task, app.SegmentName), err)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return strconv.Itoa(port)
|
|
||||||
}
|
|
||||||
|
|
||||||
// processPorts returns the configured port.
|
|
||||||
// An explicitly specified port is preferred. If none is specified, it selects
|
|
||||||
// one of the available port. The first such found port is returned unless an
|
|
||||||
// optional index is provided.
|
|
||||||
func processPorts(app marathon.Application, task marathon.Task, labels map[string]string) (int, error) {
|
|
||||||
if label.Has(labels, label.TraefikPort) {
|
|
||||||
port := label.GetIntValue(labels, label.TraefikPort, 0)
|
|
||||||
|
|
||||||
if port <= 0 {
|
|
||||||
return 0, fmt.Errorf("explicitly specified port %d must be larger than zero", port)
|
|
||||||
} else if port > 0 {
|
|
||||||
return port, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ports := retrieveAvailablePorts(app, task)
|
|
||||||
if len(ports) == 0 {
|
|
||||||
return 0, errors.New("no port found")
|
|
||||||
}
|
|
||||||
|
|
||||||
portIndex := label.GetIntValue(labels, label.TraefikPortIndex, 0)
|
|
||||||
if portIndex < 0 || portIndex > len(ports)-1 {
|
|
||||||
return 0, fmt.Errorf("index %d must be within range (0, %d)", portIndex, len(ports)-1)
|
|
||||||
}
|
|
||||||
return ports[portIndex], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func retrieveAvailablePorts(app marathon.Application, task marathon.Task) []int {
|
|
||||||
// Using default port configuration
|
|
||||||
if len(task.Ports) > 0 {
|
|
||||||
return task.Ports
|
|
||||||
}
|
|
||||||
|
|
||||||
// Using port definition if available
|
|
||||||
if app.PortDefinitions != nil && len(*app.PortDefinitions) > 0 {
|
|
||||||
var ports []int
|
|
||||||
for _, def := range *app.PortDefinitions {
|
|
||||||
if def.Port != nil {
|
|
||||||
ports = append(ports, *def.Port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ports
|
|
||||||
}
|
|
||||||
|
|
||||||
// If using IP-per-task using this port definition
|
|
||||||
if app.IPAddressPerTask != nil && app.IPAddressPerTask.Discovery != nil && len(*(app.IPAddressPerTask.Discovery.Ports)) > 0 {
|
|
||||||
var ports []int
|
|
||||||
for _, def := range *(app.IPAddressPerTask.Discovery.Ports) {
|
|
||||||
ports = append(ports, def.Number)
|
|
||||||
}
|
|
||||||
return ports
|
|
||||||
}
|
|
||||||
|
|
||||||
return []int{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func identifier(app marathon.Application, task marathon.Task, segmentName string) string {
|
|
||||||
id := fmt.Sprintf("Marathon task %s from application %s", task.ID, app.ID)
|
|
||||||
if segmentName != "" {
|
|
||||||
id += fmt.Sprintf(" (segment: %s)", segmentName)
|
|
||||||
}
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) getServers(app appData) map[string]types.Server {
|
|
||||||
var servers map[string]types.Server
|
|
||||||
|
|
||||||
for _, task := range app.Tasks {
|
|
||||||
name, server, err := p.getServer(app, *task)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if servers == nil {
|
|
||||||
servers = make(map[string]types.Server)
|
|
||||||
}
|
|
||||||
|
|
||||||
servers[name] = *server
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, linkedApp := range app.LinkedApps {
|
|
||||||
for _, task := range linkedApp.Tasks {
|
|
||||||
name, server, err := p.getServer(*linkedApp, *task)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if servers == nil {
|
|
||||||
servers = make(map[string]types.Server)
|
|
||||||
}
|
|
||||||
|
|
||||||
servers[name] = *server
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return servers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) getServer(app appData, task marathon.Task) (string, *types.Server, error) {
|
|
||||||
host, err := p.getServerHost(task, app)
|
|
||||||
if len(host) == 0 {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
port := getPort(task, app)
|
|
||||||
protocol := label.GetStringValue(app.SegmentLabels, label.TraefikProtocol, label.DefaultProtocol)
|
|
||||||
|
|
||||||
serverName := provider.Normalize("server-" + app.ID + "-" + task.ID + getSegmentNameSuffix(app.SegmentName))
|
|
||||||
|
|
||||||
return serverName, &types.Server{
|
|
||||||
URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(host, port)),
|
|
||||||
Weight: label.GetIntValue(app.SegmentLabels, label.TraefikWeight, label.DefaultWeight),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) getServerHost(task marathon.Task, app appData) (string, error) {
|
|
||||||
networks := app.Networks
|
|
||||||
var hostFlag bool
|
|
||||||
|
|
||||||
if networks == nil {
|
|
||||||
hostFlag = app.IPAddressPerTask == nil
|
|
||||||
} else {
|
|
||||||
hostFlag = (*networks)[0].Mode != marathon.ContainerNetworkMode
|
|
||||||
}
|
|
||||||
|
|
||||||
if hostFlag || p.ForceTaskHostname {
|
|
||||||
if len(task.Host) == 0 {
|
|
||||||
return "", fmt.Errorf("host is undefined for task %q app %q", task.ID, app.ID)
|
|
||||||
}
|
|
||||||
return task.Host, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
numTaskIPAddresses := len(task.IPAddresses)
|
|
||||||
switch numTaskIPAddresses {
|
|
||||||
case 0:
|
|
||||||
return "", fmt.Errorf("missing IP address for Marathon application %s on task %s", app.ID, task.ID)
|
|
||||||
case 1:
|
|
||||||
return task.IPAddresses[0].IPAddress, nil
|
|
||||||
default:
|
|
||||||
ipAddressIdx := label.GetIntValue(stringValueMap(app.Labels), labelIPAddressIdx, math.MinInt32)
|
|
||||||
|
|
||||||
if ipAddressIdx == math.MinInt32 {
|
|
||||||
return "", fmt.Errorf("found %d task IP addresses but missing IP address index for Marathon application %s on task %s",
|
|
||||||
numTaskIPAddresses, app.ID, task.ID)
|
|
||||||
}
|
|
||||||
if ipAddressIdx < 0 || ipAddressIdx > numTaskIPAddresses {
|
|
||||||
return "", fmt.Errorf("cannot use IP address index to select from %d task IP addresses for Marathon application %s on task %s",
|
|
||||||
numTaskIPAddresses, app.ID, task.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return task.IPAddresses[ipAddressIdx].IPAddress, nil
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,388 +0,0 @@
|
||||||
package marathon
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/containous/flaeg/parse"
|
|
||||||
"github.com/containous/traefik/old/provider/label"
|
|
||||||
"github.com/containous/traefik/old/types"
|
|
||||||
"github.com/gambol99/go-marathon"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBuildConfigurationSegments(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
desc string
|
|
||||||
applications *marathon.Applications
|
|
||||||
expectedFrontends map[string]*types.Frontend
|
|
||||||
expectedBackends map[string]*types.Backend
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "multiple ports with segments",
|
|
||||||
applications: withApplications(
|
|
||||||
application(
|
|
||||||
appID("/app"),
|
|
||||||
appPorts(80, 81),
|
|
||||||
withTasks(localhostTask(taskPorts(80, 81))),
|
|
||||||
|
|
||||||
withLabel(label.TraefikBackendMaxConnAmount, "1000"),
|
|
||||||
withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
|
|
||||||
withSegmentLabel(label.TraefikPort, "80", "web"),
|
|
||||||
withSegmentLabel(label.TraefikPort, "81", "admin"),
|
|
||||||
withLabel("traefik..port", "82"), // This should be ignored, as it fails to match the segmentPropertiesRegexp regex.
|
|
||||||
withSegmentLabel(label.TraefikFrontendRule, "Host:web.app.marathon.localhost", "web"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendRule, "Host:admin.app.marathon.localhost", "admin"),
|
|
||||||
)),
|
|
||||||
expectedFrontends: map[string]*types.Frontend{
|
|
||||||
"frontend-app-service-web": {
|
|
||||||
Backend: "backend-app-service-web",
|
|
||||||
Routes: map[string]types.Route{
|
|
||||||
`route-host-app-service-web`: {
|
|
||||||
Rule: "Host:web.app.marathon.localhost",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PassHostHeader: true,
|
|
||||||
EntryPoints: []string{},
|
|
||||||
},
|
|
||||||
"frontend-app-service-admin": {
|
|
||||||
Backend: "backend-app-service-admin",
|
|
||||||
Routes: map[string]types.Route{
|
|
||||||
`route-host-app-service-admin`: {
|
|
||||||
Rule: "Host:admin.app.marathon.localhost",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PassHostHeader: true,
|
|
||||||
EntryPoints: []string{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedBackends: map[string]*types.Backend{
|
|
||||||
"backend-app-service-web": {
|
|
||||||
Servers: map[string]types.Server{
|
|
||||||
"server-app-taskID-service-web": {
|
|
||||||
URL: "http://localhost:80",
|
|
||||||
Weight: label.DefaultWeight,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
MaxConn: &types.MaxConn{
|
|
||||||
Amount: 1000,
|
|
||||||
ExtractorFunc: "client.ip",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"backend-app-service-admin": {
|
|
||||||
Servers: map[string]types.Server{
|
|
||||||
"server-app-taskID-service-admin": {
|
|
||||||
URL: "http://localhost:81",
|
|
||||||
Weight: label.DefaultWeight,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
MaxConn: &types.MaxConn{
|
|
||||||
Amount: 1000,
|
|
||||||
ExtractorFunc: "client.ip",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "when all labels are set",
|
|
||||||
applications: withApplications(
|
|
||||||
application(
|
|
||||||
appID("/app"),
|
|
||||||
appPorts(80, 81),
|
|
||||||
withTasks(localhostTask(taskPorts(80, 81))),
|
|
||||||
|
|
||||||
// withLabel(label.TraefikBackend, "foobar"),
|
|
||||||
|
|
||||||
withLabel(label.TraefikBackendCircuitBreakerExpression, "NetworkErrorRatio() > 0.5"),
|
|
||||||
withLabel(label.TraefikBackendHealthCheckPath, "/health"),
|
|
||||||
withLabel(label.TraefikBackendHealthCheckPort, "880"),
|
|
||||||
withLabel(label.TraefikBackendHealthCheckInterval, "6"),
|
|
||||||
withLabel(label.TraefikBackendHealthCheckTimeout, "3"),
|
|
||||||
withLabel(label.TraefikBackendLoadBalancerMethod, "drr"),
|
|
||||||
withLabel(label.TraefikBackendLoadBalancerStickiness, "true"),
|
|
||||||
withLabel(label.TraefikBackendLoadBalancerStickinessCookieName, "chocolate"),
|
|
||||||
withLabel(label.TraefikBackendMaxConnAmount, "666"),
|
|
||||||
withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
|
|
||||||
withLabel(label.TraefikBackendBufferingMaxResponseBodyBytes, "10485760"),
|
|
||||||
withLabel(label.TraefikBackendBufferingMemResponseBodyBytes, "2097152"),
|
|
||||||
withLabel(label.TraefikBackendBufferingMaxRequestBodyBytes, "10485760"),
|
|
||||||
withLabel(label.TraefikBackendBufferingMemRequestBodyBytes, "2097152"),
|
|
||||||
withLabel(label.TraefikBackendBufferingRetryExpression, "IsNetworkError() && Attempts() <= 2"),
|
|
||||||
|
|
||||||
withSegmentLabel(label.TraefikPort, "80", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikProtocol, "https", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikWeight, "12", "containous"),
|
|
||||||
|
|
||||||
withSegmentLabel(label.TraefikFrontendPassTLSClientCertPem, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPassTLSClientCertInfosNotBefore, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPassTLSClientCertInfosNotAfter, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPassTLSClientCertInfosSans, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPassTLSClientCertInfosIssuerCommonName, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPassTLSClientCertInfosIssuerCountry, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPassTLSClientCertInfosIssuerDomainComponent, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPassTLSClientCertInfosIssuerLocality, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPassTLSClientCertInfosIssuerOrganization, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPassTLSClientCertInfosIssuerProvince, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPassTLSClientCertInfosIssuerSerialNumber, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPassTLSClientCertInfosSubjectCommonName, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPassTLSClientCertInfosSubjectCountry, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPassTLSClientCertInfosSubjectDomainComponent, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPassTLSClientCertInfosSubjectLocality, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPassTLSClientCertInfosSubjectOrganization, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPassTLSClientCertInfosSubjectProvince, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPassTLSClientCertInfosSubjectSerialNumber, "true", "containous"),
|
|
||||||
|
|
||||||
withSegmentLabel(label.TraefikFrontendAuthBasic, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendAuthBasicRemoveHeader, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendAuthBasicUsers, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendAuthBasicUsersFile, ".htpasswd", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendAuthDigestRemoveHeader, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendAuthDigestUsers, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendAuthDigestUsersFile, ".htpasswd", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendAuthForwardAddress, "auth.server", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendAuthForwardTrustForwardHeader, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendAuthForwardTLSCa, "ca.crt", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendAuthForwardTLSCaOptional, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendAuthForwardTLSCert, "server.crt", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendAuthForwardTLSKey, "server.key", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendAuthForwardTLSInsecureSkipVerify, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendAuthHeaderField, "X-WebAuth-User", "containous"),
|
|
||||||
|
|
||||||
withSegmentLabel(label.TraefikFrontendEntryPoints, "http,https", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPassHostHeader, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPassTLSCert, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPriority, "666", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendRedirectEntryPoint, "https", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendRedirectRegex, "nope", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendRedirectReplacement, "nope", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendRedirectPermanent, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendRule, "Host:traefik.io", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10", "containous"),
|
|
||||||
|
|
||||||
withSegmentLabel(label.TraefikFrontendRequestHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendResponseHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendSSLProxyHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendAllowedHosts, "foo,bar,bor", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendHostsProxyHeaders, "foo,bar,bor", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendSSLForceHost, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendSSLHost, "foo", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendCustomFrameOptionsValue, "foo", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendContentSecurityPolicy, "foo", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendPublicKey, "foo", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendReferrerPolicy, "foo", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendCustomBrowserXSSValue, "foo", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendSTSSeconds, "666", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendSSLRedirect, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendSSLTemporaryRedirect, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendSTSIncludeSubdomains, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendSTSPreload, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendForceSTSHeader, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendFrameDeny, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendContentTypeNosniff, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendBrowserXSSFilter, "true", "containous"),
|
|
||||||
withSegmentLabel(label.TraefikFrontendIsDevelopment, "true", "containous"),
|
|
||||||
|
|
||||||
withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageStatus, "404"),
|
|
||||||
withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageBackend, "foobar"),
|
|
||||||
withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageQuery, "foo_query"),
|
|
||||||
withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"bar."+label.SuffixErrorPageStatus, "500,600"),
|
|
||||||
withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"bar."+label.SuffixErrorPageBackend, "foobar"),
|
|
||||||
withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"bar."+label.SuffixErrorPageQuery, "bar_query"),
|
|
||||||
|
|
||||||
withSegmentLabel(label.TraefikFrontendRateLimitExtractorFunc, "client.ip", "containous"),
|
|
||||||
withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitPeriod, "6"),
|
|
||||||
withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitAverage, "12"),
|
|
||||||
withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitBurst, "18"),
|
|
||||||
withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitPeriod, "3"),
|
|
||||||
withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitAverage, "6"),
|
|
||||||
withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitBurst, "9"),
|
|
||||||
)),
|
|
||||||
expectedFrontends: map[string]*types.Frontend{
|
|
||||||
"frontend-app-service-containous": {
|
|
||||||
EntryPoints: []string{
|
|
||||||
"http",
|
|
||||||
"https",
|
|
||||||
},
|
|
||||||
Backend: "backend-app-service-containous",
|
|
||||||
Routes: map[string]types.Route{
|
|
||||||
"route-host-app-service-containous": {
|
|
||||||
Rule: "Host:traefik.io",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PassHostHeader: true,
|
|
||||||
PassTLSCert: true,
|
|
||||||
Priority: 666,
|
|
||||||
PassTLSClientCert: &types.TLSClientHeaders{
|
|
||||||
PEM: true,
|
|
||||||
Infos: &types.TLSClientCertificateInfos{
|
|
||||||
NotBefore: true,
|
|
||||||
Sans: true,
|
|
||||||
NotAfter: true,
|
|
||||||
Subject: &types.TLSCLientCertificateDNInfos{
|
|
||||||
CommonName: true,
|
|
||||||
Country: true,
|
|
||||||
DomainComponent: true,
|
|
||||||
Locality: true,
|
|
||||||
Organization: true,
|
|
||||||
Province: true,
|
|
||||||
SerialNumber: true,
|
|
||||||
},
|
|
||||||
Issuer: &types.TLSCLientCertificateDNInfos{
|
|
||||||
CommonName: true,
|
|
||||||
Country: true,
|
|
||||||
DomainComponent: true,
|
|
||||||
Locality: true,
|
|
||||||
Organization: true,
|
|
||||||
Province: true,
|
|
||||||
SerialNumber: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Auth: &types.Auth{
|
|
||||||
HeaderField: "X-WebAuth-User",
|
|
||||||
Basic: &types.Basic{
|
|
||||||
RemoveHeader: true,
|
|
||||||
Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
|
||||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
|
|
||||||
UsersFile: ".htpasswd",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
WhiteList: &types.WhiteList{
|
|
||||||
SourceRange: []string{"10.10.10.10"},
|
|
||||||
},
|
|
||||||
Headers: &types.Headers{
|
|
||||||
CustomRequestHeaders: map[string]string{
|
|
||||||
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
|
|
||||||
"Content-Type": "application/json; charset=utf-8",
|
|
||||||
},
|
|
||||||
CustomResponseHeaders: map[string]string{
|
|
||||||
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
|
|
||||||
"Content-Type": "application/json; charset=utf-8",
|
|
||||||
},
|
|
||||||
AllowedHosts: []string{
|
|
||||||
"foo",
|
|
||||||
"bar",
|
|
||||||
"bor",
|
|
||||||
},
|
|
||||||
HostsProxyHeaders: []string{
|
|
||||||
"foo",
|
|
||||||
"bar",
|
|
||||||
"bor",
|
|
||||||
},
|
|
||||||
SSLRedirect: true,
|
|
||||||
SSLTemporaryRedirect: true,
|
|
||||||
SSLForceHost: true,
|
|
||||||
SSLHost: "foo",
|
|
||||||
SSLProxyHeaders: map[string]string{
|
|
||||||
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
|
|
||||||
"Content-Type": "application/json; charset=utf-8",
|
|
||||||
},
|
|
||||||
STSSeconds: 666,
|
|
||||||
STSIncludeSubdomains: true,
|
|
||||||
STSPreload: true,
|
|
||||||
ForceSTSHeader: true,
|
|
||||||
FrameDeny: true,
|
|
||||||
CustomFrameOptionsValue: "foo",
|
|
||||||
ContentTypeNosniff: true,
|
|
||||||
BrowserXSSFilter: true,
|
|
||||||
CustomBrowserXSSValue: "foo",
|
|
||||||
ContentSecurityPolicy: "foo",
|
|
||||||
PublicKey: "foo",
|
|
||||||
ReferrerPolicy: "foo",
|
|
||||||
IsDevelopment: true,
|
|
||||||
},
|
|
||||||
Errors: map[string]*types.ErrorPage{
|
|
||||||
"bar": {
|
|
||||||
Status: []string{
|
|
||||||
"500",
|
|
||||||
"600",
|
|
||||||
},
|
|
||||||
Backend: "backendfoobar",
|
|
||||||
Query: "bar_query",
|
|
||||||
},
|
|
||||||
"foo": {
|
|
||||||
Status: []string{
|
|
||||||
"404",
|
|
||||||
},
|
|
||||||
Backend: "backendfoobar",
|
|
||||||
Query: "foo_query",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RateLimit: &types.RateLimit{
|
|
||||||
RateSet: map[string]*types.Rate{
|
|
||||||
"bar": {
|
|
||||||
Period: parse.Duration(3 * time.Second),
|
|
||||||
Average: 6,
|
|
||||||
Burst: 9,
|
|
||||||
},
|
|
||||||
"foo": {
|
|
||||||
Period: parse.Duration(6 * time.Second),
|
|
||||||
Average: 12,
|
|
||||||
Burst: 18,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ExtractorFunc: "client.ip",
|
|
||||||
},
|
|
||||||
Redirect: &types.Redirect{
|
|
||||||
EntryPoint: "https",
|
|
||||||
Permanent: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedBackends: map[string]*types.Backend{
|
|
||||||
"backend-app-service-containous": {
|
|
||||||
Servers: map[string]types.Server{
|
|
||||||
"server-app-taskID-service-containous": {
|
|
||||||
URL: "https://localhost:80",
|
|
||||||
Weight: 12,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CircuitBreaker: &types.CircuitBreaker{
|
|
||||||
Expression: "NetworkErrorRatio() > 0.5",
|
|
||||||
},
|
|
||||||
LoadBalancer: &types.LoadBalancer{
|
|
||||||
Method: "drr",
|
|
||||||
Stickiness: &types.Stickiness{
|
|
||||||
CookieName: "chocolate",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
MaxConn: &types.MaxConn{
|
|
||||||
Amount: 666,
|
|
||||||
ExtractorFunc: "client.ip",
|
|
||||||
},
|
|
||||||
HealthCheck: &types.HealthCheck{
|
|
||||||
Path: "/health",
|
|
||||||
Port: 880,
|
|
||||||
Interval: "6",
|
|
||||||
Timeout: "3",
|
|
||||||
},
|
|
||||||
Buffering: &types.Buffering{
|
|
||||||
MaxResponseBodyBytes: 10485760,
|
|
||||||
MemResponseBodyBytes: 2097152,
|
|
||||||
MaxRequestBodyBytes: 10485760,
|
|
||||||
MemRequestBodyBytes: 2097152,
|
|
||||||
RetryExpression: "IsNetworkError() && Attempts() <= 2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range testCases {
|
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
p := &Provider{
|
|
||||||
Domain: "marathon.localhost",
|
|
||||||
ExposedByDefault: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
actualConfig := p.buildConfiguration(test.applications)
|
|
||||||
|
|
||||||
assert.NotNil(t, actualConfig)
|
|
||||||
assert.Equal(t, test.expectedBackends, actualConfig.Backends)
|
|
||||||
assert.Equal(t, test.expectedFrontends, actualConfig.Frontends)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,8 +0,0 @@
|
||||||
package marathon
|
|
||||||
|
|
||||||
func stringValueMap(mp *map[string]string) map[string]string {
|
|
||||||
if mp != nil {
|
|
||||||
return *mp
|
|
||||||
}
|
|
||||||
return make(map[string]string)
|
|
||||||
}
|
|
|
@ -27,6 +27,10 @@ func NewProviderAggregator(conf static.Providers) ProviderAggregator {
|
||||||
p.quietAddProvider(conf.Docker)
|
p.quietAddProvider(conf.Docker)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if conf.Marathon != nil {
|
||||||
|
p.quietAddProvider(conf.Marathon)
|
||||||
|
}
|
||||||
|
|
||||||
if conf.Rest != nil {
|
if conf.Rest != nil {
|
||||||
p.quietAddProvider(conf.Rest)
|
p.quietAddProvider(conf.Rest)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,22 +4,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containous/traefik/old/provider/label"
|
|
||||||
"github.com/gambol99/go-marathon"
|
"github.com/gambol99/go-marathon"
|
||||||
)
|
)
|
||||||
|
|
||||||
const testTaskName = "taskID"
|
const testTaskName = "taskID"
|
||||||
|
|
||||||
func withAppData(app marathon.Application, segmentName string) appData {
|
|
||||||
segmentProperties := label.ExtractTraefikLabels(stringValueMap(app.Labels))
|
|
||||||
return appData{
|
|
||||||
Application: app,
|
|
||||||
SegmentLabels: segmentProperties[segmentName],
|
|
||||||
SegmentName: segmentName,
|
|
||||||
LinkedApps: nil,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Functions related to building applications.
|
// Functions related to building applications.
|
||||||
|
|
||||||
func withApplications(apps ...marathon.Application) *marathon.Applications {
|
func withApplications(apps ...marathon.Application) *marathon.Applications {
|
||||||
|
@ -64,17 +53,6 @@ func constraint(value string) func(*marathon.Application) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func withSegmentLabel(key, value string, segmentName string) func(*marathon.Application) {
|
|
||||||
if len(segmentName) == 0 {
|
|
||||||
panic("segmentName can not be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
property := strings.TrimPrefix(key, label.Prefix)
|
|
||||||
return func(app *marathon.Application) {
|
|
||||||
app.AddLabel(label.Prefix+segmentName+"."+property, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func portDefinition(port int) func(*marathon.Application) {
|
func portDefinition(port int) func(*marathon.Application) {
|
||||||
return func(app *marathon.Application) {
|
return func(app *marathon.Application) {
|
||||||
app.AddPortDefinition(marathon.PortDefinition{
|
app.AddPortDefinition(marathon.PortDefinition{
|
276
provider/marathon/config.go
Normal file
276
provider/marathon/config.go
Normal file
|
@ -0,0 +1,276 @@
|
||||||
|
package marathon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/config"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
"github.com/containous/traefik/provider"
|
||||||
|
"github.com/containous/traefik/provider/label"
|
||||||
|
"github.com/gambol99/go-marathon"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Provider) buildConfiguration(ctx context.Context, applications *marathon.Applications) *config.Configuration {
|
||||||
|
configurations := make(map[string]*config.Configuration)
|
||||||
|
|
||||||
|
for _, app := range applications.Apps {
|
||||||
|
ctxApp := log.With(ctx, log.Str("applicationID", app.ID))
|
||||||
|
logger := log.FromContext(ctxApp)
|
||||||
|
|
||||||
|
extraConf, err := p.getConfiguration(app)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Skip application: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.keepApplication(ctxApp, extraConf) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
confFromLabel, err := label.DecodeConfiguration(stringValueMap(app.Labels))
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.buildServiceConfiguration(ctxApp, app, extraConf, confFromLabel)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
model := struct {
|
||||||
|
Name string
|
||||||
|
Labels map[string]string
|
||||||
|
}{
|
||||||
|
Name: app.ID,
|
||||||
|
Labels: stringValueMap(app.Labels),
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceName := getServiceName(app)
|
||||||
|
|
||||||
|
provider.BuildRouterConfiguration(ctxApp, confFromLabel, serviceName, p.defaultRuleTpl, model)
|
||||||
|
|
||||||
|
configurations[app.ID] = confFromLabel
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.Merge(ctx, configurations)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getServiceName(app marathon.Application) string {
|
||||||
|
return strings.Replace(strings.TrimPrefix(app.ID, "/"), "/", "_", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) buildServiceConfiguration(ctx context.Context, app marathon.Application, extraConf configuration, conf *config.Configuration) error {
|
||||||
|
appName := getServiceName(app)
|
||||||
|
appCtx := log.With(ctx, log.Str("ApplicationID", appName))
|
||||||
|
|
||||||
|
if len(conf.Services) == 0 {
|
||||||
|
conf.Services = make(map[string]*config.Service)
|
||||||
|
lb := &config.LoadBalancerService{}
|
||||||
|
lb.SetDefaults()
|
||||||
|
conf.Services[appName] = &config.Service{
|
||||||
|
LoadBalancer: lb,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for serviceName, service := range conf.Services {
|
||||||
|
var servers []config.Server
|
||||||
|
|
||||||
|
defaultServer := config.Server{}
|
||||||
|
defaultServer.SetDefaults()
|
||||||
|
|
||||||
|
if len(service.LoadBalancer.Servers) > 0 {
|
||||||
|
defaultServer = service.LoadBalancer.Servers[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, task := range app.Tasks {
|
||||||
|
if p.taskFilter(ctx, *task, app) {
|
||||||
|
server, err := p.getServer(app, *task, extraConf, defaultServer)
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(appCtx).Errorf("Skip task: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(servers) == 0 {
|
||||||
|
return fmt.Errorf("no server for the service %s", serviceName)
|
||||||
|
}
|
||||||
|
service.LoadBalancer.Servers = servers
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) keepApplication(ctx context.Context, extraConf configuration) bool {
|
||||||
|
logger := log.FromContext(ctx)
|
||||||
|
|
||||||
|
// Filter disabled application.
|
||||||
|
if !extraConf.Enable {
|
||||||
|
logger.Debug("Filtering disabled Marathon application")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by constraints.
|
||||||
|
if ok, failingConstraint := p.MatchConstraints(extraConf.Tags); !ok {
|
||||||
|
if failingConstraint != nil {
|
||||||
|
logger.Debugf("Filtering Marathon application, pruned by %q constraint", failingConstraint.String())
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) taskFilter(ctx context.Context, task marathon.Task, application marathon.Application) bool {
|
||||||
|
if task.State != string(taskStateRunning) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ready := p.readyChecker.Do(task, application); !ready {
|
||||||
|
log.FromContext(ctx).Infof("Filtering unready task %s from application %s", task.ID, application.ID)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) getServer(app marathon.Application, task marathon.Task, extraConf configuration, defaultServer config.Server) (config.Server, error) {
|
||||||
|
host, err := p.getServerHost(task, app, extraConf)
|
||||||
|
if len(host) == 0 {
|
||||||
|
return config.Server{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := getPort(task, app, defaultServer.Port)
|
||||||
|
if err != nil {
|
||||||
|
return config.Server{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
server := config.Server{
|
||||||
|
URL: fmt.Sprintf("%s://%s", defaultServer.Scheme, net.JoinHostPort(host, port)),
|
||||||
|
Weight: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
return server, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) getServerHost(task marathon.Task, app marathon.Application, extraConf configuration) (string, error) {
|
||||||
|
networks := app.Networks
|
||||||
|
var hostFlag bool
|
||||||
|
|
||||||
|
if networks == nil {
|
||||||
|
hostFlag = app.IPAddressPerTask == nil
|
||||||
|
} else {
|
||||||
|
hostFlag = (*networks)[0].Mode != marathon.ContainerNetworkMode
|
||||||
|
}
|
||||||
|
|
||||||
|
if hostFlag || p.ForceTaskHostname {
|
||||||
|
if len(task.Host) == 0 {
|
||||||
|
return "", fmt.Errorf("host is undefined for task %q app %q", task.ID, app.ID)
|
||||||
|
}
|
||||||
|
return task.Host, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
numTaskIPAddresses := len(task.IPAddresses)
|
||||||
|
switch numTaskIPAddresses {
|
||||||
|
case 0:
|
||||||
|
return "", fmt.Errorf("missing IP address for Marathon application %s on task %s", app.ID, task.ID)
|
||||||
|
case 1:
|
||||||
|
return task.IPAddresses[0].IPAddress, nil
|
||||||
|
default:
|
||||||
|
if extraConf.Marathon.IPAddressIdx == math.MinInt32 {
|
||||||
|
return "", fmt.Errorf("found %d task IP addresses but missing IP address index for Marathon application %s on task %s",
|
||||||
|
numTaskIPAddresses, app.ID, task.ID)
|
||||||
|
}
|
||||||
|
if extraConf.Marathon.IPAddressIdx < 0 || extraConf.Marathon.IPAddressIdx > numTaskIPAddresses {
|
||||||
|
return "", fmt.Errorf("cannot use IP address index to select from %d task IP addresses for Marathon application %s on task %s",
|
||||||
|
numTaskIPAddresses, app.ID, task.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return task.IPAddresses[extraConf.Marathon.IPAddressIdx].IPAddress, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPort(task marathon.Task, app marathon.Application, serverPort string) (string, error) {
|
||||||
|
port, err := processPorts(app, task, serverPort)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("unable to process ports for %s %s: %v", app.ID, task.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strconv.Itoa(port), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processPorts returns the configured port.
|
||||||
|
// An explicitly specified port is preferred. If none is specified, it selects
|
||||||
|
// one of the available port. The first such found port is returned unless an
|
||||||
|
// optional index is provided.
|
||||||
|
func processPorts(app marathon.Application, task marathon.Task, serverPort string) (int, error) {
|
||||||
|
if len(serverPort) > 0 && !strings.HasPrefix(serverPort, "index:") {
|
||||||
|
port, err := strconv.Atoi(serverPort)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if port <= 0 {
|
||||||
|
return 0, fmt.Errorf("explicitly specified port %d must be greater than zero", port)
|
||||||
|
} else if port > 0 {
|
||||||
|
return port, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ports := retrieveAvailablePorts(app, task)
|
||||||
|
if len(ports) == 0 {
|
||||||
|
return 0, errors.New("no port found")
|
||||||
|
}
|
||||||
|
|
||||||
|
portIndex := 0
|
||||||
|
if strings.HasPrefix(serverPort, "index:") {
|
||||||
|
split := strings.SplitN(serverPort, ":", 2)
|
||||||
|
index, err := strconv.Atoi(split[1])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if index < 0 || index > len(ports)-1 {
|
||||||
|
return 0, fmt.Errorf("index %d must be within range (0, %d)", index, len(ports)-1)
|
||||||
|
}
|
||||||
|
portIndex = index
|
||||||
|
}
|
||||||
|
return ports[portIndex], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieveAvailablePorts(app marathon.Application, task marathon.Task) []int {
|
||||||
|
// Using default port configuration
|
||||||
|
if len(task.Ports) > 0 {
|
||||||
|
return task.Ports
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using port definition if available
|
||||||
|
if app.PortDefinitions != nil && len(*app.PortDefinitions) > 0 {
|
||||||
|
var ports []int
|
||||||
|
for _, def := range *app.PortDefinitions {
|
||||||
|
if def.Port != nil {
|
||||||
|
ports = append(ports, *def.Port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ports
|
||||||
|
}
|
||||||
|
|
||||||
|
// If using IP-per-task using this port definition
|
||||||
|
if app.IPAddressPerTask != nil && app.IPAddressPerTask.Discovery != nil && len(*(app.IPAddressPerTask.Discovery.Ports)) > 0 {
|
||||||
|
var ports []int
|
||||||
|
for _, def := range *(app.IPAddressPerTask.Discovery.Ports) {
|
||||||
|
ports = append(ports, def.Number)
|
||||||
|
}
|
||||||
|
return ports
|
||||||
|
}
|
||||||
|
|
||||||
|
return []int{}
|
||||||
|
}
|
1587
provider/marathon/config_test.go
Normal file
1587
provider/marathon/config_test.go
Normal file
File diff suppressed because it is too large
Load diff
|
@ -3,7 +3,7 @@ package marathon
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/containous/traefik/old/provider/marathon/mocks"
|
"github.com/containous/traefik/provider/marathon/mocks"
|
||||||
"github.com/gambol99/go-marathon"
|
"github.com/gambol99/go-marathon"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
)
|
)
|
51
provider/marathon/label.go
Normal file
51
provider/marathon/label.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package marathon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/provider/label"
|
||||||
|
"github.com/gambol99/go-marathon"
|
||||||
|
)
|
||||||
|
|
||||||
|
type configuration struct {
|
||||||
|
Enable bool
|
||||||
|
Tags []string
|
||||||
|
Marathon specificConfiguration
|
||||||
|
}
|
||||||
|
|
||||||
|
type specificConfiguration struct {
|
||||||
|
IPAddressIdx int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) getConfiguration(app marathon.Application) (configuration, error) {
|
||||||
|
labels := stringValueMap(app.Labels)
|
||||||
|
|
||||||
|
conf := configuration{
|
||||||
|
Enable: p.ExposedByDefault,
|
||||||
|
Tags: nil,
|
||||||
|
Marathon: specificConfiguration{
|
||||||
|
IPAddressIdx: math.MinInt32,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := label.Decode(labels, &conf, "traefik.marathon.", "traefik.enable", "traefik.tags")
|
||||||
|
if err != nil {
|
||||||
|
return configuration{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.FilterMarathonConstraints && app.Constraints != nil {
|
||||||
|
for _, constraintParts := range *app.Constraints {
|
||||||
|
conf.Tags = append(conf.Tags, strings.Join(constraintParts, ":"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return conf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringValueMap(mp *map[string]string) map[string]string {
|
||||||
|
if mp != nil {
|
||||||
|
return *mp
|
||||||
|
}
|
||||||
|
return make(map[string]string)
|
||||||
|
}
|
178
provider/marathon/label_test.go
Normal file
178
provider/marathon/label_test.go
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
package marathon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/provider"
|
||||||
|
"github.com/gambol99/go-marathon"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetConfiguration(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
app marathon.Application
|
||||||
|
p Provider
|
||||||
|
expected configuration
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Empty labels",
|
||||||
|
app: marathon.Application{
|
||||||
|
Constraints: &[][]string{},
|
||||||
|
Labels: &map[string]string{},
|
||||||
|
},
|
||||||
|
p: Provider{
|
||||||
|
BaseProvider: provider.BaseProvider{},
|
||||||
|
ExposedByDefault: false,
|
||||||
|
FilterMarathonConstraints: false,
|
||||||
|
},
|
||||||
|
expected: configuration{
|
||||||
|
Enable: false,
|
||||||
|
Tags: nil,
|
||||||
|
Marathon: specificConfiguration{
|
||||||
|
IPAddressIdx: math.MinInt32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "label enable",
|
||||||
|
app: marathon.Application{
|
||||||
|
Constraints: &[][]string{},
|
||||||
|
Labels: &map[string]string{
|
||||||
|
"traefik.enable": "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
p: Provider{
|
||||||
|
BaseProvider: provider.BaseProvider{},
|
||||||
|
ExposedByDefault: false,
|
||||||
|
FilterMarathonConstraints: false,
|
||||||
|
},
|
||||||
|
expected: configuration{
|
||||||
|
Enable: true,
|
||||||
|
Tags: nil,
|
||||||
|
Marathon: specificConfiguration{
|
||||||
|
IPAddressIdx: math.MinInt32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Use ip address index",
|
||||||
|
app: marathon.Application{
|
||||||
|
Constraints: &[][]string{},
|
||||||
|
Labels: &map[string]string{
|
||||||
|
"traefik.marathon.IPAddressIdx": "4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
p: Provider{
|
||||||
|
BaseProvider: provider.BaseProvider{},
|
||||||
|
ExposedByDefault: false,
|
||||||
|
FilterMarathonConstraints: false,
|
||||||
|
},
|
||||||
|
expected: configuration{
|
||||||
|
Enable: false,
|
||||||
|
Tags: nil,
|
||||||
|
Marathon: specificConfiguration{
|
||||||
|
IPAddressIdx: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Use marathon constraints",
|
||||||
|
app: marathon.Application{
|
||||||
|
Constraints: &[][]string{
|
||||||
|
{"key", "value"},
|
||||||
|
},
|
||||||
|
Labels: &map[string]string{},
|
||||||
|
},
|
||||||
|
p: Provider{
|
||||||
|
BaseProvider: provider.BaseProvider{},
|
||||||
|
ExposedByDefault: false,
|
||||||
|
FilterMarathonConstraints: true,
|
||||||
|
},
|
||||||
|
expected: configuration{
|
||||||
|
Enable: false,
|
||||||
|
Tags: []string{
|
||||||
|
"key:value",
|
||||||
|
},
|
||||||
|
Marathon: specificConfiguration{
|
||||||
|
IPAddressIdx: math.MinInt32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ExposedByDefault and no enable label",
|
||||||
|
app: marathon.Application{
|
||||||
|
Constraints: &[][]string{},
|
||||||
|
Labels: &map[string]string{},
|
||||||
|
},
|
||||||
|
p: Provider{
|
||||||
|
BaseProvider: provider.BaseProvider{},
|
||||||
|
ExposedByDefault: true,
|
||||||
|
FilterMarathonConstraints: false,
|
||||||
|
},
|
||||||
|
expected: configuration{
|
||||||
|
Enable: true,
|
||||||
|
Tags: nil,
|
||||||
|
Marathon: specificConfiguration{
|
||||||
|
IPAddressIdx: math.MinInt32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ExposedByDefault and enable label false",
|
||||||
|
app: marathon.Application{
|
||||||
|
Constraints: &[][]string{},
|
||||||
|
Labels: &map[string]string{
|
||||||
|
"traefik.enable": "false",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
p: Provider{
|
||||||
|
BaseProvider: provider.BaseProvider{},
|
||||||
|
ExposedByDefault: true,
|
||||||
|
FilterMarathonConstraints: false,
|
||||||
|
},
|
||||||
|
expected: configuration{
|
||||||
|
Enable: false,
|
||||||
|
Tags: nil,
|
||||||
|
Marathon: specificConfiguration{
|
||||||
|
IPAddressIdx: math.MinInt32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Tags in label",
|
||||||
|
app: marathon.Application{
|
||||||
|
Constraints: &[][]string{},
|
||||||
|
Labels: &map[string]string{
|
||||||
|
"traefik.tags": "mytags",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
p: Provider{
|
||||||
|
BaseProvider: provider.BaseProvider{},
|
||||||
|
ExposedByDefault: true,
|
||||||
|
FilterMarathonConstraints: false,
|
||||||
|
},
|
||||||
|
expected: configuration{
|
||||||
|
Enable: true,
|
||||||
|
Tags: []string{"mytags"},
|
||||||
|
Marathon: specificConfiguration{
|
||||||
|
IPAddressIdx: math.MinInt32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
extraConf, err := test.p.getConfiguration(test.app)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, extraConf)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,23 +1,29 @@
|
||||||
package marathon
|
package marathon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cenk/backoff"
|
"github.com/cenk/backoff"
|
||||||
"github.com/containous/flaeg/parse"
|
"github.com/containous/flaeg/parse"
|
||||||
|
"github.com/containous/traefik/config"
|
||||||
"github.com/containous/traefik/job"
|
"github.com/containous/traefik/job"
|
||||||
"github.com/containous/traefik/old/log"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/containous/traefik/old/provider"
|
|
||||||
"github.com/containous/traefik/old/types"
|
"github.com/containous/traefik/old/types"
|
||||||
|
"github.com/containous/traefik/provider"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/gambol99/go-marathon"
|
"github.com/gambol99/go-marathon"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// DefaultTemplateRule The default template for the default rule.
|
||||||
|
DefaultTemplateRule = "Host:{{ normalize .Name }}"
|
||||||
traceMaxScanTokenSize = 1024 * 1024
|
traceMaxScanTokenSize = 1024 * 1024
|
||||||
marathonEventIDs = marathon.EventIDApplications |
|
marathonEventIDs = marathon.EventIDApplications |
|
||||||
marathon.EventIDAddHealthCheck |
|
marathon.EventIDAddHealthCheck |
|
||||||
|
@ -36,23 +42,15 @@ const (
|
||||||
taskStateStaging TaskState = "TASK_STAGING"
|
taskStateStaging TaskState = "TASK_STAGING"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
labelIPAddressIdx = "traefik.ipAddressIdx"
|
|
||||||
labelLbCompatibilityGroup = "HAPROXY_GROUP"
|
|
||||||
labelLbCompatibility = "HAPROXY_0_VHOST"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ provider.Provider = (*Provider)(nil)
|
var _ provider.Provider = (*Provider)(nil)
|
||||||
|
|
||||||
// Provider holds configuration of the provider.
|
// Provider holds configuration of the provider.
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
provider.BaseProvider
|
provider.BaseProvider
|
||||||
Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon" export:"true"`
|
Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon" export:"true"`
|
||||||
Domain string `description:"Default domain used" export:"true"`
|
DefaultRule string `description:"Default rule"`
|
||||||
ExposedByDefault bool `description:"Expose Marathon apps by default" 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"`
|
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"`
|
FilterMarathonConstraints bool `description:"Enable use of Marathon constraints in constraint filtering" export:"true"`
|
||||||
TLS *types.ClientTLS `description:"Enable TLS support" export:"true"`
|
TLS *types.ClientTLS `description:"Enable TLS support" export:"true"`
|
||||||
DialerTimeout parse.Duration `description:"Set a dialer timeout for Marathon" export:"true"`
|
DialerTimeout parse.Duration `description:"Set a dialer timeout for Marathon" export:"true"`
|
||||||
|
@ -64,6 +62,7 @@ type Provider struct {
|
||||||
RespectReadinessChecks bool `description:"Filter out tasks with non-successful readiness checks during deployments" export:"true"`
|
RespectReadinessChecks bool `description:"Filter out tasks with non-successful readiness checks during deployments" export:"true"`
|
||||||
readyChecker *readinessChecker
|
readyChecker *readinessChecker
|
||||||
marathonClient marathon.Marathon
|
marathonClient marathon.Marathon
|
||||||
|
defaultRuleTpl *template.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basic holds basic authentication specific configurations
|
// Basic holds basic authentication specific configurations
|
||||||
|
@ -73,39 +72,59 @@ type Basic struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init the provider
|
// Init the provider
|
||||||
func (p *Provider) Init(constraints types.Constraints) error {
|
func (p *Provider) Init() error {
|
||||||
return p.BaseProvider.Init(constraints)
|
fm := template.FuncMap{
|
||||||
|
"strsToItfs": func(values []string) []interface{} {
|
||||||
|
var r []interface{}
|
||||||
|
for _, v := range values {
|
||||||
|
r = append(r, v)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultRuleTpl, err := provider.MakeDefaultRuleTemplate(p.DefaultRule, fm)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error while parsing default rule: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.defaultRuleTpl = defaultRuleTpl
|
||||||
|
return p.BaseProvider.Init()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide allows the marathon provider to provide configurations to traefik
|
// Provide allows the marathon provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (p *Provider) Provide(configurationChan chan<- config.Message, pool *safe.Pool) error {
|
||||||
|
ctx := log.With(context.Background(), log.Str(log.ProviderName, "marathon"))
|
||||||
|
logger := log.FromContext(ctx)
|
||||||
|
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
config := marathon.NewDefaultConfig()
|
|
||||||
config.URL = p.Endpoint
|
confg := marathon.NewDefaultConfig()
|
||||||
config.EventsTransport = marathon.EventsTransportSSE
|
confg.URL = p.Endpoint
|
||||||
|
confg.EventsTransport = marathon.EventsTransportSSE
|
||||||
if p.Trace {
|
if p.Trace {
|
||||||
config.LogOutput = log.CustomWriterLevel(logrus.DebugLevel, traceMaxScanTokenSize)
|
confg.LogOutput = log.CustomWriterLevel(logrus.DebugLevel, traceMaxScanTokenSize)
|
||||||
}
|
}
|
||||||
if p.Basic != nil {
|
if p.Basic != nil {
|
||||||
config.HTTPBasicAuthUser = p.Basic.HTTPBasicAuthUser
|
confg.HTTPBasicAuthUser = p.Basic.HTTPBasicAuthUser
|
||||||
config.HTTPBasicPassword = p.Basic.HTTPBasicPassword
|
confg.HTTPBasicPassword = p.Basic.HTTPBasicPassword
|
||||||
}
|
}
|
||||||
var rc *readinessChecker
|
var rc *readinessChecker
|
||||||
if p.RespectReadinessChecks {
|
if p.RespectReadinessChecks {
|
||||||
log.Debug("Enabling Marathon readiness checker")
|
logger.Debug("Enabling Marathon readiness checker")
|
||||||
rc = defaultReadinessChecker(p.Trace)
|
rc = defaultReadinessChecker(p.Trace)
|
||||||
}
|
}
|
||||||
p.readyChecker = rc
|
p.readyChecker = rc
|
||||||
|
|
||||||
if len(p.DCOSToken) > 0 {
|
if len(p.DCOSToken) > 0 {
|
||||||
config.DCOSToken = p.DCOSToken
|
confg.DCOSToken = p.DCOSToken
|
||||||
}
|
}
|
||||||
TLSConfig, err := p.TLS.CreateTLSConfig()
|
TLSConfig, err := p.TLS.CreateTLSConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
config.HTTPClient = &http.Client{
|
confg.HTTPClient = &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
DialContext: (&net.Dialer{
|
DialContext: (&net.Dialer{
|
||||||
KeepAlive: time.Duration(p.KeepAlive),
|
KeepAlive: time.Duration(p.KeepAlive),
|
||||||
|
@ -116,9 +135,9 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||||
TLSClientConfig: TLSConfig,
|
TLSClientConfig: TLSConfig,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
client, err := marathon.NewClient(config)
|
client, err := marathon.NewClient(confg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to create a client for marathon, error: %s", err)
|
logger.Errorf("Failed to create a client for marathon, error: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
p.marathonClient = client
|
p.marathonClient = client
|
||||||
|
@ -126,7 +145,7 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||||
if p.Watch {
|
if p.Watch {
|
||||||
update, err := client.AddEventsListener(marathonEventIDs)
|
update, err := client.AddEventsListener(marathonEventIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to register for events, %s", err)
|
logger.Errorf("Failed to register for events, %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
pool.Go(func(stop chan bool) {
|
pool.Go(func(stop chan bool) {
|
||||||
|
@ -136,13 +155,13 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||||
case <-stop:
|
case <-stop:
|
||||||
return
|
return
|
||||||
case event := <-update:
|
case event := <-update:
|
||||||
log.Debugf("Received provider event %s", event)
|
logger.Debugf("Received provider event %s", event)
|
||||||
|
|
||||||
configuration := p.getConfiguration()
|
conf := p.getConfigurations(ctx)
|
||||||
if configuration != nil {
|
if conf != nil {
|
||||||
configurationChan <- types.ConfigMessage{
|
configurationChan <- config.Message{
|
||||||
ProviderName: "marathon",
|
ProviderName: "marathon",
|
||||||
Configuration: configuration,
|
Configuration: conf,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,8 +169,8 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
configuration := p.getConfiguration()
|
configuration := p.getConfigurations(ctx)
|
||||||
configurationChan <- types.ConfigMessage{
|
configurationChan <- config.Message{
|
||||||
ProviderName: "marathon",
|
ProviderName: "marathon",
|
||||||
Configuration: configuration,
|
Configuration: configuration,
|
||||||
}
|
}
|
||||||
|
@ -159,23 +178,23 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||||
}
|
}
|
||||||
|
|
||||||
notify := func(err error, time time.Duration) {
|
notify := func(err error, time time.Duration) {
|
||||||
log.Errorf("Provider connection error %+v, retrying in %s", err, time)
|
logger.Errorf("Provider connection error %+v, retrying in %s", err, time)
|
||||||
}
|
}
|
||||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Cannot connect to Provider server %+v", err)
|
logger.Errorf("Cannot connect to Provider server: %+v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) getConfiguration() *types.Configuration {
|
func (p *Provider) getConfigurations(ctx context.Context) *config.Configuration {
|
||||||
applications, err := p.getApplications()
|
applications, err := p.getApplications()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to retrieve Marathon applications: %v", err)
|
log.FromContext(ctx).Errorf("Failed to retrieve Marathon applications: %v", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.buildConfiguration(applications)
|
return p.buildConfiguration(ctx, applications)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) getApplications() (*marathon.Applications, error) {
|
func (p *Provider) getApplications() (*marathon.Applications, error) {
|
Loading…
Reference in a new issue