2017-04-17 12:50:02 +02:00
package marathon
2015-09-12 15:10:03 +02:00
2015-09-09 22:39:08 +02:00
import (
2019-01-29 17:54:05 +01:00
"context"
"fmt"
2016-10-05 11:42:58 -04:00
"net"
2016-12-30 09:21:13 +01:00
"net/http"
2018-03-26 15:32:04 +02:00
"net/url"
2019-01-29 17:54:05 +01:00
"text/template"
2016-08-13 12:55:15 -04:00
"time"
2020-02-26 10:36:05 +01:00
"github.com/cenkalti/backoff/v4"
2019-08-03 03:58:23 +02:00
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/job"
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/provider"
"github.com/containous/traefik/v2/pkg/safe"
"github.com/containous/traefik/v2/pkg/types"
2015-09-12 15:10:03 +02:00
"github.com/gambol99/go-marathon"
2018-01-22 12:16:03 +01:00
"github.com/sirupsen/logrus"
2015-09-09 22:39:08 +02:00
)
2017-04-20 22:05:21 +02:00
const (
2019-01-29 17:54:05 +01:00
// DefaultTemplateRule The default template for the default rule.
2019-01-30 16:24:07 +01:00
DefaultTemplateRule = "Host(`{{ normalize .Name }}`)"
2017-07-10 16:58:12 +02:00
traceMaxScanTokenSize = 1024 * 1024
2017-08-18 03:08:03 +02:00
marathonEventIDs = marathon . EventIDApplications |
marathon . EventIDAddHealthCheck |
marathon . EventIDDeploymentSuccess |
marathon . EventIDDeploymentFailed |
marathon . EventIDDeploymentInfo |
marathon . EventIDDeploymentStepSuccess |
marathon . EventIDDeploymentStepFailed
2017-07-17 13:42:48 +02:00
)
// TaskState denotes the Mesos state a task can have.
type TaskState string
const (
taskStateRunning TaskState = "TASK_RUNNING"
taskStateStaging TaskState = "TASK_STAGING"
2017-04-20 22:05:21 +02:00
)
2017-12-02 19:27:47 +01:00
var _ provider . Provider = ( * Provider ) ( nil )
2017-08-21 10:46:03 +02:00
2017-04-17 12:50:02 +02:00
// Provider holds configuration of the provider.
type Provider struct {
2019-07-01 11:30:05 +02:00
Constraints string ` description:"Constraints is an expression that Traefik matches against the application's labels to determine whether to create any route for that application." json:"constraints,omitempty" toml:"constraints,omitempty" yaml:"constraints,omitempty" export:"true" `
Trace bool ` description:"Display additional provider logs." json:"trace,omitempty" toml:"trace,omitempty" yaml:"trace,omitempty" export:"true" `
Watch bool ` description:"Watch provider." json:"watch,omitempty" toml:"watch,omitempty" yaml:"watch,omitempty" export:"true" `
Endpoint string ` description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty" export:"true" `
DefaultRule string ` description:"Default rule." json:"defaultRule,omitempty" toml:"defaultRule,omitempty" yaml:"defaultRule,omitempty" `
ExposedByDefault bool ` description:"Expose Marathon apps by default." json:"exposedByDefault,omitempty" toml:"exposedByDefault,omitempty" yaml:"exposedByDefault,omitempty" export:"true" `
DCOSToken string ` description:"DCOSToken for DCOS environment, This will override the Authorization header." json:"dcosToken,omitempty" toml:"dcosToken,omitempty" yaml:"dcosToken,omitempty" export:"true" `
TLS * types . ClientTLS ` description:"Enable TLS support." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true" `
DialerTimeout types . Duration ` description:"Set a dialer timeout for Marathon." json:"dialerTimeout,omitempty" toml:"dialerTimeout,omitempty" yaml:"dialerTimeout,omitempty" export:"true" `
ResponseHeaderTimeout types . Duration ` description:"Set a response header timeout for Marathon." json:"responseHeaderTimeout,omitempty" toml:"responseHeaderTimeout,omitempty" yaml:"responseHeaderTimeout,omitempty" export:"true" `
TLSHandshakeTimeout types . Duration ` description:"Set a TLS handshake timeout for Marathon." json:"tlsHandshakeTimeout,omitempty" toml:"tlsHandshakeTimeout,omitempty" yaml:"tlsHandshakeTimeout,omitempty" export:"true" `
KeepAlive types . Duration ` description:"Set a TCP Keep Alive time." json:"keepAlive,omitempty" toml:"keepAlive,omitempty" yaml:"keepAlive,omitempty" export:"true" `
ForceTaskHostname bool ` description:"Force to use the task's hostname." json:"forceTaskHostname,omitempty" toml:"forceTaskHostname,omitempty" yaml:"forceTaskHostname,omitempty" export:"true" `
Basic * Basic ` description:"Enable basic authentication." json:"basic,omitempty" toml:"basic,omitempty" yaml:"basic,omitempty" export:"true" `
RespectReadinessChecks bool ` description:"Filter out tasks with non-successful readiness checks during deployments." json:"respectReadinessChecks,omitempty" toml:"respectReadinessChecks,omitempty" yaml:"respectReadinessChecks,omitempty" export:"true" `
2019-06-21 09:24:04 +02:00
readyChecker * readinessChecker
marathonClient marathon . Marathon
defaultRuleTpl * template . Template
2015-11-13 11:50:32 +01:00
}
2019-06-17 11:48:05 +02:00
// SetDefaults sets the default values.
func ( p * Provider ) SetDefaults ( ) {
p . Watch = true
p . Endpoint = "http://127.0.0.1:8080"
p . ExposedByDefault = true
p . DialerTimeout = types . Duration ( 5 * time . Second )
p . ResponseHeaderTimeout = types . Duration ( 60 * time . Second )
p . TLSHandshakeTimeout = types . Duration ( 5 * time . Second )
p . KeepAlive = types . Duration ( 10 * time . Second )
p . DefaultRule = DefaultTemplateRule
}
2020-05-11 12:06:07 +02:00
// Basic holds basic authentication specific configurations.
2017-04-17 12:50:02 +02:00
type Basic struct {
2019-07-01 11:30:05 +02:00
HTTPBasicAuthUser string ` description:"Basic authentication User." json:"httpBasicAuthUser,omitempty" toml:"httpBasicAuthUser,omitempty" yaml:"httpBasicAuthUser,omitempty" `
HTTPBasicPassword string ` description:"Basic authentication Password." json:"httpBasicPassword,omitempty" toml:"httpBasicPassword,omitempty" yaml:"httpBasicPassword,omitempty" `
2016-01-18 11:52:18 +01:00
}
2020-05-11 12:06:07 +02:00
// Init the provider.
2019-01-29 17:54:05 +01:00
func ( p * Provider ) Init ( ) error {
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 {
2020-05-11 12:06:07 +02:00
return fmt . Errorf ( "error while parsing default rule: %w" , err )
2019-01-29 17:54:05 +01:00
}
p . defaultRuleTpl = defaultRuleTpl
2019-03-27 15:02:06 +01:00
return nil
2018-07-11 09:08:03 +02:00
}
2017-04-17 12:50:02 +02:00
// Provide allows the marathon provider to provide configurations to traefik
2015-11-01 19:29:47 +01:00
// using the given configuration channel.
2019-07-10 09:26:04 +02:00
func ( p * Provider ) Provide ( configurationChan chan <- dynamic . Message , pool * safe . Pool ) error {
2019-01-29 17:54:05 +01:00
ctx := log . With ( context . Background ( ) , log . Str ( log . ProviderName , "marathon" ) )
logger := log . FromContext ( ctx )
2016-04-15 18:59:51 +02:00
operation := func ( ) error {
2019-01-29 17:54:05 +01:00
confg := marathon . NewDefaultConfig ( )
confg . URL = p . Endpoint
confg . EventsTransport = marathon . EventsTransportSSE
2017-07-07 23:48:28 +02:00
if p . Trace {
2019-01-29 17:54:05 +01:00
confg . LogOutput = log . CustomWriterLevel ( logrus . DebugLevel , traceMaxScanTokenSize )
2017-07-07 23:48:28 +02:00
}
2017-04-17 12:50:02 +02:00
if p . Basic != nil {
2019-01-29 17:54:05 +01:00
confg . HTTPBasicAuthUser = p . Basic . HTTPBasicAuthUser
confg . HTTPBasicPassword = p . Basic . HTTPBasicPassword
2016-04-15 18:59:51 +02:00
}
2017-08-18 03:08:03 +02:00
var rc * readinessChecker
if p . RespectReadinessChecks {
2019-01-29 17:54:05 +01:00
logger . Debug ( "Enabling Marathon readiness checker" )
2017-08-18 03:08:03 +02:00
rc = defaultReadinessChecker ( p . Trace )
}
p . readyChecker = rc
2017-04-17 12:50:02 +02:00
if len ( p . DCOSToken ) > 0 {
2019-01-29 17:54:05 +01:00
confg . DCOSToken = p . DCOSToken
2016-06-18 14:51:52 +02:00
}
2019-03-18 11:30:07 +01:00
TLSConfig , err := p . TLS . CreateTLSConfig ( ctx )
2016-06-27 16:14:56 +02:00
if err != nil {
return err
}
2019-01-29 17:54:05 +01:00
confg . HTTPClient = & http . Client {
2016-04-15 18:59:51 +02:00
Transport : & http . Transport {
2016-10-05 11:42:58 -04:00
DialContext : ( & net . Dialer {
2017-04-17 12:50:02 +02:00
KeepAlive : time . Duration ( p . KeepAlive ) ,
Timeout : time . Duration ( p . DialerTimeout ) ,
2016-10-05 11:42:58 -04:00
} ) . DialContext ,
2018-05-22 22:38:03 +02:00
ResponseHeaderTimeout : time . Duration ( p . ResponseHeaderTimeout ) ,
TLSHandshakeTimeout : time . Duration ( p . TLSHandshakeTimeout ) ,
TLSClientConfig : TLSConfig ,
2016-04-15 18:59:51 +02:00
} ,
}
2019-01-29 17:54:05 +01:00
client , err := marathon . NewClient ( confg )
2016-04-15 18:59:51 +02:00
if err != nil {
2019-01-29 17:54:05 +01:00
logger . Errorf ( "Failed to create a client for marathon, error: %s" , err )
2016-04-15 18:59:51 +02:00
return err
}
2017-04-17 12:50:02 +02:00
p . marathonClient = client
2017-01-06 13:26:50 -02:00
2017-04-17 12:50:02 +02:00
if p . Watch {
2017-08-18 03:08:03 +02:00
update , err := client . AddEventsListener ( marathonEventIDs )
2017-01-06 13:26:50 -02:00
if err != nil {
2019-01-29 17:54:05 +01:00
logger . Errorf ( "Failed to register for events, %s" , err )
2016-04-15 18:59:51 +02:00
return err
}
2020-02-03 17:56:04 +01:00
pool . GoCtx ( func ( ctxPool context . Context ) {
2016-04-19 12:00:22 +02:00
defer close ( update )
2015-09-24 17:16:13 +02:00
for {
2016-04-13 20:36:23 +02:00
select {
2020-02-03 17:56:04 +01:00
case <- ctxPool . Done ( ) :
2016-04-13 20:36:23 +02:00
return
case event := <- update :
2019-01-29 17:54:05 +01:00
logger . Debugf ( "Received provider event %s" , event )
2018-03-26 15:32:04 +02:00
2019-01-29 17:54:05 +01:00
conf := p . getConfigurations ( ctx )
if conf != nil {
2019-07-10 09:26:04 +02:00
configurationChan <- dynamic . Message {
2016-04-13 20:36:23 +02:00
ProviderName : "marathon" ,
2019-01-29 17:54:05 +01:00
Configuration : conf ,
2016-04-13 20:36:23 +02:00
}
2015-11-13 11:50:32 +01:00
}
2015-09-09 23:09:16 +02:00
}
2015-09-24 17:16:13 +02:00
}
2016-03-31 18:57:08 +02:00
} )
2015-09-09 23:09:16 +02:00
}
2018-03-26 15:32:04 +02:00
2019-01-29 17:54:05 +01:00
configuration := p . getConfigurations ( ctx )
2019-07-10 09:26:04 +02:00
configurationChan <- dynamic . Message {
2016-04-19 12:00:22 +02:00
ProviderName : "marathon" ,
Configuration : configuration ,
}
2016-04-15 18:59:51 +02:00
return nil
2015-09-09 22:39:08 +02:00
}
2015-09-24 17:16:13 +02:00
2016-04-15 18:59:51 +02:00
notify := func ( err error , time time . Duration ) {
2019-01-29 17:54:05 +01:00
logger . Errorf ( "Provider connection error %+v, retrying in %s" , err , time )
2016-04-15 18:59:51 +02:00
}
2016-12-08 13:32:12 +01:00
err := backoff . RetryNotify ( safe . OperationWithRecover ( operation ) , job . NewBackOff ( backoff . NewExponentialBackOff ( ) ) , notify )
2016-04-15 18:59:51 +02:00
if err != nil {
2019-01-29 17:54:05 +01:00
logger . Errorf ( "Cannot connect to Provider server: %+v" , err )
2015-11-13 11:50:32 +01:00
}
2015-10-01 12:04:25 +02:00
return nil
2015-09-09 22:39:08 +02:00
}
2018-03-26 15:32:04 +02:00
2019-07-10 09:26:04 +02:00
func ( p * Provider ) getConfigurations ( ctx context . Context ) * dynamic . Configuration {
2018-03-26 15:32:04 +02:00
applications , err := p . getApplications ( )
if err != nil {
2019-01-29 17:54:05 +01:00
log . FromContext ( ctx ) . Errorf ( "Failed to retrieve Marathon applications: %v" , err )
2018-03-26 15:32:04 +02:00
return nil
}
2019-01-29 17:54:05 +01:00
return p . buildConfiguration ( ctx , applications )
2018-03-26 15:32:04 +02:00
}
func ( p * Provider ) getApplications ( ) ( * marathon . Applications , error ) {
v := url . Values { }
v . Add ( "embed" , "apps.tasks" )
v . Add ( "embed" , "apps.deployments" )
v . Add ( "embed" , "apps.readiness" )
return p . marathonClient . Applications ( v )
}