2017-12-02 19:27:47 +01:00
package marathon
import (
"errors"
"fmt"
"math"
2018-06-13 10:08:03 +02:00
"net"
2017-12-02 19:27:47 +01:00
"strconv"
"strings"
"text/template"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
"github.com/gambol99/go-marathon"
)
2018-01-10 11:58:03 +01:00
const defaultService = ""
2018-03-26 15:32:04 +02:00
type appData struct {
marathon . Application
SegmentLabels map [ string ] string
SegmentName string
2018-04-04 12:28:03 +02:00
LinkedApps [ ] * appData
2018-03-26 15:32:04 +02:00
}
func ( p * Provider ) buildConfigurationV2 ( applications * marathon . Applications ) * types . Configuration {
2017-12-02 19:27:47 +01:00
var MarathonFuncMap = template . FuncMap {
2018-03-26 15:32:04 +02:00
"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 ,
2017-12-20 16:33:57 +01:00
// Backend functions
2018-01-10 11:58:03 +01:00
"getPort" : getPort ,
2018-03-26 15:32:04 +02:00
"getCircuitBreaker" : label . GetCircuitBreaker ,
"getLoadBalancer" : label . GetLoadBalancer ,
"getMaxConn" : label . GetMaxConn ,
"getHealthCheck" : label . GetHealthCheck ,
"getBuffering" : label . GetBuffering ,
2018-01-10 11:58:03 +01:00
"getServers" : p . getServers ,
2017-12-20 16:33:57 +01:00
// Frontend functions
2018-03-26 15:32:04 +02:00
"getSegmentNameSuffix" : getSegmentNameSuffix ,
2018-03-23 17:40:04 +01:00
"getFrontendRule" : p . getFrontendRule ,
"getFrontendName" : p . getFrontendName ,
2018-04-11 16:30:04 +02:00
"getPassHostHeader" : label . GetFuncBool ( label . TraefikFrontendPassHostHeader , label . DefaultPassHostHeader ) ,
2018-03-26 15:32:04 +02:00
"getPassTLSCert" : label . GetFuncBool ( label . TraefikFrontendPassTLSCert , label . DefaultPassTLSCert ) ,
2018-04-11 16:30:04 +02:00
"getPriority" : label . GetFuncInt ( label . TraefikFrontendPriority , label . DefaultFrontendPriority ) ,
2018-03-26 15:32:04 +02:00
"getEntryPoints" : label . GetFuncSliceString ( label . TraefikFrontendEntryPoints ) ,
2018-07-06 16:52:04 +02:00
"getBasicAuth" : label . GetFuncSliceString ( label . TraefikFrontendAuthBasic ) , // Deprecated
"getAuth" : label . GetAuth ,
2018-03-26 15:32:04 +02:00
"getRedirect" : label . GetRedirect ,
"getErrorPages" : label . GetErrorPages ,
"getRateLimit" : label . GetRateLimit ,
"getHeaders" : label . GetHeaders ,
"getWhiteList" : label . GetWhiteList ,
}
2018-04-04 12:28:03 +02:00
apps := make ( map [ string ] * appData )
2018-03-26 15:32:04 +02:00
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 )
}
}
2017-12-02 19:27:47 +01:00
2018-03-26 15:32:04 +02:00
app . Tasks = filteredTasks
// segments
segmentProperties := label . ExtractTraefikLabels ( stringValueMap ( app . Labels ) )
for segmentName , labels := range segmentProperties {
data := & appData {
Application : app ,
SegmentLabels : labels ,
SegmentName : segmentName ,
}
2018-04-04 12:28:03 +02:00
backendName := p . getBackendName ( * data )
if baseApp , ok := apps [ backendName ] ; ok {
baseApp . LinkedApps = append ( baseApp . LinkedApps , data )
} else {
apps [ backendName ] = data
}
2017-12-02 19:27:47 +01:00
}
2018-03-26 15:32:04 +02:00
}
2017-12-02 19:27:47 +01:00
}
templateObjects := struct {
2018-04-04 12:28:03 +02:00
Applications map [ string ] * appData
2017-12-20 16:33:57 +01:00
Domain string
2017-12-02 19:27:47 +01:00
} {
2018-03-26 15:32:04 +02:00
Applications : apps ,
2017-12-20 16:33:57 +01:00
Domain : p . Domain ,
2017-12-02 19:27:47 +01:00
}
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.
2018-03-26 15:32:04 +02:00
if ! label . IsEnabled ( stringValueMap ( app . Labels ) , p . ExposedByDefault ) {
2017-12-02 19:27:47 +01:00
log . Debugf ( "Filtering disabled Marathon application %s" , app . ID )
return false
}
// Filter by constraints.
2018-03-26 15:32:04 +02:00
constraintTags := label . GetSliceStringValue ( stringValueMap ( app . Labels ) , label . TraefikTags )
2017-12-02 19:27:47 +01:00
if p . MarathonLBCompatibility {
2018-03-26 15:32:04 +02:00
if haGroup := label . GetStringValue ( stringValueMap ( app . Labels ) , labelLbCompatibilityGroup , "" ) ; len ( haGroup ) > 0 {
2017-12-02 19:27:47 +01:00
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
}
2018-03-26 15:32:04 +02:00
// 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
2017-12-02 19:27:47 +01:00
}
2018-03-26 15:32:04 +02:00
// 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 ) )
}
2017-12-02 19:27:47 +01:00
}
}
2018-03-26 15:32:04 +02:00
func getSegmentNameSuffix ( serviceName string ) string {
if len ( serviceName ) > 0 {
return "-service-" + provider . Normalize ( serviceName )
2017-12-02 19:27:47 +01:00
}
2018-03-26 15:32:04 +02:00
return ""
2017-12-02 19:27:47 +01:00
}
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 )
}
2018-03-26 15:32:04 +02:00
func ( p * Provider ) getBackendName ( app appData ) string {
value := label . GetStringValue ( app . SegmentLabels , label . TraefikBackend , "" )
if len ( value ) > 0 {
return provider . Normalize ( "backend" + value )
2017-12-02 19:27:47 +01:00
}
2018-04-04 12:28:03 +02:00
2018-03-26 15:32:04 +02:00
return provider . Normalize ( "backend" + app . ID + getSegmentNameSuffix ( app . SegmentName ) )
2017-12-02 19:27:47 +01:00
}
2018-03-26 15:32:04 +02:00
func ( p * Provider ) getFrontendName ( app appData ) string {
return provider . Normalize ( "frontend" + app . ID + getSegmentNameSuffix ( app . SegmentName ) )
2017-12-02 19:27:47 +01:00
}
2018-03-26 15:32:04 +02:00
// 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
2017-12-02 19:27:47 +01:00
}
2018-03-26 15:32:04 +02:00
if p . MarathonLBCompatibility {
if value := label . GetStringValue ( stringValueMap ( app . Labels ) , labelLbCompatibility , "" ) ; len ( value ) > 0 {
return "Host:" + value
2017-12-02 19:27:47 +01:00
}
}
2018-04-17 20:58:24 +02:00
domain := label . GetStringValue ( app . SegmentLabels , label . TraefikDomain , p . Domain )
2018-03-26 15:32:04 +02:00
if len ( app . SegmentName ) > 0 {
2018-04-17 20:58:24 +02:00
return "Host:" + strings . ToLower ( provider . Normalize ( app . SegmentName ) ) + "." + p . getSubDomain ( app . ID ) + "." + domain
2017-12-02 19:27:47 +01:00
}
2018-04-17 20:58:24 +02:00
return "Host:" + p . getSubDomain ( app . ID ) + "." + domain
2017-12-02 19:27:47 +01:00
}
2018-03-26 15:32:04 +02:00
func getPort ( task marathon . Task , app appData ) string {
port , err := processPorts ( app . Application , task , app . SegmentLabels )
2017-12-02 19:27:47 +01:00
if err != nil {
2018-03-26 15:32:04 +02:00
log . Errorf ( "Unable to process ports for %s: %s" , identifier ( app . Application , task , app . SegmentName ) , err )
2017-12-02 19:27:47 +01:00
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.
2018-03-26 15:32:04 +02:00
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 )
2017-12-02 19:27:47 +01:00
if port <= 0 {
return 0 , fmt . Errorf ( "explicitly specified port %d must be larger than zero" , port )
} else if port > 0 {
return port , nil
}
}
2018-03-26 15:32:04 +02:00
ports := retrieveAvailablePorts ( app , task )
2017-12-02 19:27:47 +01:00
if len ( ports ) == 0 {
return 0 , errors . New ( "no port found" )
}
2018-03-26 15:32:04 +02:00
portIndex := label . GetIntValue ( labels , label . TraefikPortIndex , 0 )
2017-12-02 19:27:47 +01:00
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
}
2018-03-26 15:32:04 +02:00
func retrieveAvailablePorts ( app marathon . Application , task marathon . Task ) [ ] int {
2017-12-02 19:27:47 +01:00
// Using default port configuration
if len ( task . Ports ) > 0 {
return task . Ports
}
// Using port definition if available
2018-03-26 15:32:04 +02:00
if app . PortDefinitions != nil && len ( * app . PortDefinitions ) > 0 {
2017-12-02 19:27:47 +01:00
var ports [ ] int
2018-03-26 15:32:04 +02:00
for _ , def := range * app . PortDefinitions {
2017-12-02 19:27:47 +01:00
if def . Port != nil {
ports = append ( ports , * def . Port )
}
}
return ports
}
2018-03-26 15:32:04 +02:00
2017-12-02 19:27:47 +01:00
// If using IP-per-task using this port definition
2018-03-26 15:32:04 +02:00
if app . IPAddressPerTask != nil && app . IPAddressPerTask . Discovery != nil && len ( * ( app . IPAddressPerTask . Discovery . Ports ) ) > 0 {
2017-12-02 19:27:47 +01:00
var ports [ ] int
2018-03-26 15:32:04 +02:00
for _ , def := range * ( app . IPAddressPerTask . Discovery . Ports ) {
2017-12-02 19:27:47 +01:00
ports = append ( ports , def . Number )
}
return ports
}
return [ ] int { }
}
2018-03-26 15:32:04 +02:00
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 )
2018-01-31 15:32:04 +01:00
}
2018-03-26 15:32:04 +02:00
return id
2018-01-31 15:32:04 +01:00
}
2018-03-26 15:32:04 +02:00
func ( p * Provider ) getServers ( app appData ) map [ string ] types . Server {
2018-01-10 11:58:03 +01:00
var servers map [ string ] types . Server
2018-03-26 15:32:04 +02:00
for _ , task := range app . Tasks {
2018-04-04 12:28:03 +02:00
name , server , err := p . getServer ( app , * task )
if err != nil {
log . Error ( err )
2018-01-10 11:58:03 +01:00
continue
}
if servers == nil {
servers = make ( map [ string ] types . Server )
}
2018-04-04 12:28:03 +02:00
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
}
2018-01-10 11:58:03 +01:00
2018-04-04 12:28:03 +02:00
if servers == nil {
servers = make ( map [ string ] types . Server )
}
servers [ name ] = * server
2018-01-10 11:58:03 +01:00
}
}
return servers
}
2018-04-04 12:28:03 +02:00
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 {
2018-06-13 10:08:03 +02:00
URL : fmt . Sprintf ( "%s://%s" , protocol , net . JoinHostPort ( host , port ) ) ,
2018-04-11 16:30:04 +02:00
Weight : label . GetIntValue ( app . SegmentLabels , label . TraefikWeight , label . DefaultWeight ) ,
2018-04-04 12:28:03 +02:00
} , nil
}
func ( p * Provider ) getServerHost ( task marathon . Task , app appData ) ( string , error ) {
2018-07-03 16:42:03 -05:00
networks := app . Networks
var hostFlag bool
if networks == nil {
hostFlag = app . IPAddressPerTask == nil
} else {
hostFlag = ( * networks ) [ 0 ] . Mode != marathon . ContainerNetworkMode
}
if hostFlag || p . ForceTaskHostname {
2018-04-17 20:58:24 +02:00
if len ( task . Host ) == 0 {
return "" , fmt . Errorf ( "host is undefined for task %q app %q" , task . ID , app . ID )
}
2018-04-04 12:28:03 +02:00
return task . Host , nil
2018-03-23 17:40:04 +01:00
}
2018-03-26 15:32:04 +02:00
numTaskIPAddresses := len ( task . IPAddresses )
switch numTaskIPAddresses {
case 0 :
2018-04-04 12:28:03 +02:00
return "" , fmt . Errorf ( "missing IP address for Marathon application %s on task %s" , app . ID , task . ID )
2018-03-26 15:32:04 +02:00
case 1 :
2018-04-04 12:28:03 +02:00
return task . IPAddresses [ 0 ] . IPAddress , nil
2018-03-26 15:32:04 +02:00
default :
ipAddressIdx := label . GetIntValue ( stringValueMap ( app . Labels ) , labelIPAddressIdx , math . MinInt32 )
2018-01-31 19:10:04 +01:00
2018-03-26 15:32:04 +02:00
if ipAddressIdx == math . MinInt32 {
2018-04-04 12:28:03 +02:00
return "" , fmt . Errorf ( "found %d task IP addresses but missing IP address index for Marathon application %s on task %s" ,
2018-03-26 15:32:04 +02:00
numTaskIPAddresses , app . ID , task . ID )
2018-01-10 11:58:03 +01:00
}
2018-03-26 15:32:04 +02:00
if ipAddressIdx < 0 || ipAddressIdx > numTaskIPAddresses {
2018-04-04 12:28:03 +02:00
return "" , fmt . Errorf ( "cannot use IP address index to select from %d task IP addresses for Marathon application %s on task %s" ,
2018-03-26 15:32:04 +02:00
numTaskIPAddresses , app . ID , task . ID )
2018-01-10 11:58:03 +01:00
}
2017-12-20 16:33:57 +01:00
2018-04-04 12:28:03 +02:00
return task . IPAddresses [ ipAddressIdx ] . IPAddress , nil
2017-12-02 19:27:47 +01:00
}
}