2017-04-17 10:50:02 +00:00
package marathon
2015-09-12 13:10:03 +00:00
2015-09-09 20:39:08 +00:00
import (
2019-01-29 16:54:05 +00:00
"context"
"fmt"
2016-10-05 15:42:58 +00:00
"net"
2016-12-30 08:21:13 +00:00
"net/http"
2018-03-26 13:32:04 +00:00
"net/url"
2019-01-29 16:54:05 +00:00
"text/template"
2016-08-13 16:55:15 +00:00
"time"
2020-02-26 09:36:05 +00:00
"github.com/cenkalti/backoff/v4"
2019-08-03 01:58:23 +00: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 13:10:03 +00:00
"github.com/gambol99/go-marathon"
2018-01-22 11:16:03 +00:00
"github.com/sirupsen/logrus"
2015-09-09 20:39:08 +00:00
)
2017-04-20 20:05:21 +00:00
const (
2019-01-29 16:54:05 +00:00
// DefaultTemplateRule The default template for the default rule.
2019-01-30 15:24:07 +00:00
DefaultTemplateRule = "Host(`{{ normalize .Name }}`)"
2017-07-10 14:58:12 +00:00
traceMaxScanTokenSize = 1024 * 1024
2017-08-18 01:08:03 +00:00
marathonEventIDs = marathon . EventIDApplications |
marathon . EventIDAddHealthCheck |
marathon . EventIDDeploymentSuccess |
marathon . EventIDDeploymentFailed |
marathon . EventIDDeploymentInfo |
marathon . EventIDDeploymentStepSuccess |
marathon . EventIDDeploymentStepFailed
2017-07-17 11:42:48 +00: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 20:05:21 +00:00
)
2017-12-02 18:27:47 +00:00
var _ provider . Provider = ( * Provider ) ( nil )
2017-08-21 08:46:03 +00:00
2017-04-17 10:50:02 +00:00
// Provider holds configuration of the provider.
type Provider struct {
2019-07-01 09:30:05 +00: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 07:24:04 +00:00
readyChecker * readinessChecker
marathonClient marathon . Marathon
defaultRuleTpl * template . Template
2015-11-13 10:50:32 +00:00
}
2019-06-17 09:48:05 +00: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 10:06:07 +00:00
// Basic holds basic authentication specific configurations.
2017-04-17 10:50:02 +00:00
type Basic struct {
2019-07-01 09:30:05 +00: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 10:52:18 +00:00
}
2020-05-11 10:06:07 +00:00
// Init the provider.
2019-01-29 16:54:05 +00: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 10:06:07 +00:00
return fmt . Errorf ( "error while parsing default rule: %w" , err )
2019-01-29 16:54:05 +00:00
}
p . defaultRuleTpl = defaultRuleTpl
2019-03-27 14:02:06 +00:00
return nil
2018-07-11 07:08:03 +00:00
}
2017-04-17 10:50:02 +00:00
// Provide allows the marathon provider to provide configurations to traefik
2015-11-01 18:29:47 +00:00
// using the given configuration channel.
2019-07-10 07:26:04 +00:00
func ( p * Provider ) Provide ( configurationChan chan <- dynamic . Message , pool * safe . Pool ) error {
2019-01-29 16:54:05 +00:00
ctx := log . With ( context . Background ( ) , log . Str ( log . ProviderName , "marathon" ) )
logger := log . FromContext ( ctx )
2016-04-15 16:59:51 +00:00
operation := func ( ) error {
2019-01-29 16:54:05 +00:00
confg := marathon . NewDefaultConfig ( )
confg . URL = p . Endpoint
confg . EventsTransport = marathon . EventsTransportSSE
2017-07-07 21:48:28 +00:00
if p . Trace {
2019-01-29 16:54:05 +00:00
confg . LogOutput = log . CustomWriterLevel ( logrus . DebugLevel , traceMaxScanTokenSize )
2017-07-07 21:48:28 +00:00
}
2017-04-17 10:50:02 +00:00
if p . Basic != nil {
2019-01-29 16:54:05 +00:00
confg . HTTPBasicAuthUser = p . Basic . HTTPBasicAuthUser
confg . HTTPBasicPassword = p . Basic . HTTPBasicPassword
2016-04-15 16:59:51 +00:00
}
2017-08-18 01:08:03 +00:00
var rc * readinessChecker
if p . RespectReadinessChecks {
2019-01-29 16:54:05 +00:00
logger . Debug ( "Enabling Marathon readiness checker" )
2017-08-18 01:08:03 +00:00
rc = defaultReadinessChecker ( p . Trace )
}
p . readyChecker = rc
2017-04-17 10:50:02 +00:00
if len ( p . DCOSToken ) > 0 {
2019-01-29 16:54:05 +00:00
confg . DCOSToken = p . DCOSToken
2016-06-18 12:51:52 +00:00
}
2019-03-18 10:30:07 +00:00
TLSConfig , err := p . TLS . CreateTLSConfig ( ctx )
2016-06-27 14:14:56 +00:00
if err != nil {
return err
}
2019-01-29 16:54:05 +00:00
confg . HTTPClient = & http . Client {
2016-04-15 16:59:51 +00:00
Transport : & http . Transport {
2016-10-05 15:42:58 +00:00
DialContext : ( & net . Dialer {
2017-04-17 10:50:02 +00:00
KeepAlive : time . Duration ( p . KeepAlive ) ,
Timeout : time . Duration ( p . DialerTimeout ) ,
2016-10-05 15:42:58 +00:00
} ) . DialContext ,
2018-05-22 20:38:03 +00:00
ResponseHeaderTimeout : time . Duration ( p . ResponseHeaderTimeout ) ,
TLSHandshakeTimeout : time . Duration ( p . TLSHandshakeTimeout ) ,
TLSClientConfig : TLSConfig ,
2016-04-15 16:59:51 +00:00
} ,
}
2019-01-29 16:54:05 +00:00
client , err := marathon . NewClient ( confg )
2016-04-15 16:59:51 +00:00
if err != nil {
2019-01-29 16:54:05 +00:00
logger . Errorf ( "Failed to create a client for marathon, error: %s" , err )
2016-04-15 16:59:51 +00:00
return err
}
2017-04-17 10:50:02 +00:00
p . marathonClient = client
2017-01-06 15:26:50 +00:00
2017-04-17 10:50:02 +00:00
if p . Watch {
2017-08-18 01:08:03 +00:00
update , err := client . AddEventsListener ( marathonEventIDs )
2017-01-06 15:26:50 +00:00
if err != nil {
2019-01-29 16:54:05 +00:00
logger . Errorf ( "Failed to register for events, %s" , err )
2016-04-15 16:59:51 +00:00
return err
}
2020-02-03 16:56:04 +00:00
pool . GoCtx ( func ( ctxPool context . Context ) {
2016-04-19 10:00:22 +00:00
defer close ( update )
2015-09-24 15:16:13 +00:00
for {
2016-04-13 18:36:23 +00:00
select {
2020-02-03 16:56:04 +00:00
case <- ctxPool . Done ( ) :
2016-04-13 18:36:23 +00:00
return
case event := <- update :
2019-01-29 16:54:05 +00:00
logger . Debugf ( "Received provider event %s" , event )
2018-03-26 13:32:04 +00:00
2019-01-29 16:54:05 +00:00
conf := p . getConfigurations ( ctx )
if conf != nil {
2019-07-10 07:26:04 +00:00
configurationChan <- dynamic . Message {
2016-04-13 18:36:23 +00:00
ProviderName : "marathon" ,
2019-01-29 16:54:05 +00:00
Configuration : conf ,
2016-04-13 18:36:23 +00:00
}
2015-11-13 10:50:32 +00:00
}
2015-09-09 21:09:16 +00:00
}
2015-09-24 15:16:13 +00:00
}
2016-03-31 16:57:08 +00:00
} )
2015-09-09 21:09:16 +00:00
}
2018-03-26 13:32:04 +00:00
2019-01-29 16:54:05 +00:00
configuration := p . getConfigurations ( ctx )
2019-07-10 07:26:04 +00:00
configurationChan <- dynamic . Message {
2016-04-19 10:00:22 +00:00
ProviderName : "marathon" ,
Configuration : configuration ,
}
2016-04-15 16:59:51 +00:00
return nil
2015-09-09 20:39:08 +00:00
}
2015-09-24 15:16:13 +00:00
2016-04-15 16:59:51 +00:00
notify := func ( err error , time time . Duration ) {
2019-01-29 16:54:05 +00:00
logger . Errorf ( "Provider connection error %+v, retrying in %s" , err , time )
2016-04-15 16:59:51 +00:00
}
2016-12-08 12:32:12 +00:00
err := backoff . RetryNotify ( safe . OperationWithRecover ( operation ) , job . NewBackOff ( backoff . NewExponentialBackOff ( ) ) , notify )
2016-04-15 16:59:51 +00:00
if err != nil {
2019-01-29 16:54:05 +00:00
logger . Errorf ( "Cannot connect to Provider server: %+v" , err )
2015-11-13 10:50:32 +00:00
}
2015-10-01 10:04:25 +00:00
return nil
2015-09-09 20:39:08 +00:00
}
2018-03-26 13:32:04 +00:00
2019-07-10 07:26:04 +00:00
func ( p * Provider ) getConfigurations ( ctx context . Context ) * dynamic . Configuration {
2018-03-26 13:32:04 +00:00
applications , err := p . getApplications ( )
if err != nil {
2019-01-29 16:54:05 +00:00
log . FromContext ( ctx ) . Errorf ( "Failed to retrieve Marathon applications: %v" , err )
2018-03-26 13:32:04 +00:00
return nil
}
2019-01-29 16:54:05 +00:00
return p . buildConfiguration ( ctx , applications )
2018-03-26 13:32:04 +00: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 )
}