2017-12-02 18:26:44 +00:00
package docker
import (
2018-03-23 12:30:03 +00:00
"context"
"fmt"
2017-12-02 18:26:44 +00:00
"strconv"
2018-03-23 12:30:03 +00:00
"strings"
2017-12-02 18:26:44 +00:00
"text/template"
"github.com/BurntSushi/ty/fun"
"github.com/containous/traefik/log"
2018-03-23 12:30:03 +00:00
"github.com/containous/traefik/provider"
2017-12-02 18:26:44 +00:00
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
2018-03-23 12:30:03 +00:00
"github.com/docker/go-connections/nat"
2017-12-02 18:26:44 +00:00
)
2018-03-23 12:30:03 +00:00
const (
labelDockerNetwork = "traefik.docker.network"
labelBackendLoadBalancerSwarm = "traefik.backend.loadbalancer.swarm"
labelDockerComposeProject = "com.docker.compose.project"
labelDockerComposeService = "com.docker.compose.service"
)
func ( p * Provider ) buildConfigurationV2 ( containersInspected [ ] dockerData ) * types . Configuration {
dockerFuncMap := template . FuncMap {
"getLabelValue" : label . GetStringValue ,
2018-01-09 15:26:03 +00:00
"getSubDomain" : getSubDomain ,
2018-03-23 12:30:03 +00:00
"isBackendLBSwarm" : isBackendLBSwarm ,
2018-03-26 13:32:04 +00:00
"getDomain" : label . GetFuncString ( label . TraefikDomain , p . Domain ) ,
2017-12-18 10:02:04 +00:00
// Backend functions
2018-01-09 15:26:03 +00:00
"getIPAddress" : p . getIPAddress ,
2018-03-23 12:30:03 +00:00
"getServers" : p . getServers ,
2018-03-26 13:32:04 +00:00
"getMaxConn" : label . GetMaxConn ,
"getHealthCheck" : label . GetHealthCheck ,
"getBuffering" : label . GetBuffering ,
"getCircuitBreaker" : label . GetCircuitBreaker ,
"getLoadBalancer" : label . GetLoadBalancer ,
2018-01-09 15:26:03 +00:00
2017-12-18 10:02:04 +00:00
// Frontend functions
2018-03-23 16:40:04 +00:00
"getBackendName" : getBackendName ,
2018-03-26 13:32:04 +00:00
"getPriority" : label . GetFuncInt ( label . TraefikFrontendPriority , label . DefaultFrontendPriorityInt ) ,
"getPassHostHeader" : label . GetFuncBool ( label . TraefikFrontendPassHostHeader , label . DefaultPassHostHeaderBool ) ,
"getPassTLSCert" : label . GetFuncBool ( label . TraefikFrontendPassTLSCert , label . DefaultPassTLSCert ) ,
"getEntryPoints" : label . GetFuncSliceString ( label . TraefikFrontendEntryPoints ) ,
"getBasicAuth" : label . GetFuncSliceString ( label . TraefikFrontendAuthBasic ) ,
2018-03-23 16:40:04 +00:00
"getFrontendRule" : p . getFrontendRule ,
2018-03-26 13:32:04 +00:00
"getRedirect" : label . GetRedirect ,
"getErrorPages" : label . GetErrorPages ,
"getRateLimit" : label . GetRateLimit ,
"getHeaders" : label . GetHeaders ,
"getWhiteList" : label . GetWhiteList ,
2017-12-02 18:26:44 +00:00
}
2018-03-23 12:30:03 +00:00
2017-12-02 18:26:44 +00:00
// filter containers
2018-03-23 12:30:03 +00:00
filteredContainers := fun . Filter ( p . containerFilter , containersInspected ) . ( [ ] dockerData )
2017-12-02 18:26:44 +00:00
frontends := map [ string ] [ ] dockerData { }
servers := map [ string ] [ ] dockerData { }
2018-03-23 12:30:03 +00:00
2017-12-02 18:26:44 +00:00
serviceNames := make ( map [ string ] struct { } )
2018-03-23 12:30:03 +00:00
2017-12-02 18:26:44 +00:00
for idx , container := range filteredContainers {
2018-03-23 12:30:03 +00:00
segmentProperties := label . ExtractTraefikLabels ( container . Labels )
for segmentName , labels := range segmentProperties {
container . SegmentLabels = labels
container . SegmentName = segmentName
// Frontends
if _ , exists := serviceNames [ container . ServiceName + segmentName ] ; ! exists {
frontendName := p . getFrontendName ( container , idx )
frontends [ frontendName ] = append ( frontends [ frontendName ] , container )
if len ( container . ServiceName + segmentName ) > 0 {
serviceNames [ container . ServiceName + segmentName ] = struct { } { }
}
2017-12-02 18:26:44 +00:00
}
2018-03-23 12:30:03 +00:00
// Backends
backendName := getBackendName ( container )
// Servers
servers [ backendName ] = append ( servers [ backendName ] , container )
2017-12-02 18:26:44 +00:00
}
}
templateObjects := struct {
Containers [ ] dockerData
Frontends map [ string ] [ ] dockerData
Servers map [ string ] [ ] dockerData
Domain string
} {
2017-12-15 21:16:48 +00:00
Containers : filteredContainers ,
Frontends : frontends ,
Servers : servers ,
Domain : p . Domain ,
2017-12-02 18:26:44 +00:00
}
2018-03-23 12:30:03 +00:00
configuration , err := p . GetConfiguration ( "templates/docker.tmpl" , dockerFuncMap , templateObjects )
2017-12-02 18:26:44 +00:00
if err != nil {
log . Error ( err )
}
return configuration
}
2018-03-23 12:30:03 +00:00
func ( p * Provider ) containerFilter ( container dockerData ) bool {
2017-12-02 18:26:44 +00:00
if ! label . IsEnabled ( container . Labels , p . ExposedByDefault ) {
log . Debugf ( "Filtering disabled container %s" , container . Name )
return false
}
2018-03-23 12:30:03 +00:00
segmentProperties := label . ExtractTraefikLabels ( container . Labels )
var errPort error
for segmentName , labels := range segmentProperties {
errPort = checkSegmentPort ( labels , segmentName )
2018-03-28 15:18:04 +00:00
if len ( p . getFrontendRule ( container , labels ) ) == 0 {
2018-03-23 12:30:03 +00:00
log . Debugf ( "Filtering container with empty frontend rule %s %s" , container . Name , segmentName )
return false
}
2017-12-02 18:26:44 +00:00
}
2018-03-23 12:30:03 +00:00
if len ( container . NetworkSettings . Ports ) == 0 && errPort != nil {
log . Debugf ( "Filtering container without port, %s: %v" , container . Name , errPort )
2017-12-02 18:26:44 +00:00
return false
}
constraintTags := label . SplitAndTrimString ( container . Labels [ label . TraefikTags ] , "," )
if ok , failingConstraint := p . MatchConstraints ( constraintTags ) ; ! ok {
if failingConstraint != nil {
2018-03-23 12:30:03 +00:00
log . Debugf ( "Container %s pruned by %q constraint" , container . Name , failingConstraint . String ( ) )
2017-12-02 18:26:44 +00:00
}
return false
}
if container . Health != "" && container . Health != "healthy" {
log . Debugf ( "Filtering unhealthy or starting container %s" , container . Name )
return false
}
2018-03-23 12:30:03 +00:00
return true
}
func checkSegmentPort ( labels map [ string ] string , segmentName string ) error {
if port , ok := labels [ label . TraefikPort ] ; ok {
_ , err := strconv . Atoi ( port )
if err != nil {
return fmt . Errorf ( "invalid port value %q for the segment %q: %v" , port , segmentName , err )
}
} else {
return fmt . Errorf ( "port label is missing, please use %s as default value or define port label for all segments ('traefik.<segment_name>.port')" , label . TraefikPort )
2017-12-02 18:26:44 +00:00
}
2018-03-23 12:30:03 +00:00
return nil
}
2017-12-02 18:26:44 +00:00
2018-03-23 12:30:03 +00:00
func ( p * Provider ) getFrontendName ( container dockerData , idx int ) string {
var name string
if len ( container . SegmentName ) > 0 {
name = getBackendName ( container )
} else {
2018-03-28 15:18:04 +00:00
name = p . getFrontendRule ( container , container . SegmentLabels ) + "-" + strconv . Itoa ( idx )
2018-03-23 12:30:03 +00:00
}
return provider . Normalize ( name )
}
2018-03-28 15:18:04 +00:00
func ( p * Provider ) getFrontendRule ( container dockerData , segmentLabels map [ string ] string ) string {
if value := label . GetStringValue ( segmentLabels , label . TraefikFrontendRule , "" ) ; len ( value ) != 0 {
2018-03-23 12:30:03 +00:00
return value
}
if values , err := label . GetStringMultipleStrict ( container . Labels , labelDockerComposeProject , labelDockerComposeService ) ; err == nil {
return "Host:" + getSubDomain ( values [ labelDockerComposeService ] + "." + values [ labelDockerComposeProject ] ) + "." + p . Domain
}
if len ( p . Domain ) > 0 {
return "Host:" + getSubDomain ( container . ServiceName ) + "." + p . Domain
}
return ""
}
func ( p Provider ) getIPAddress ( container dockerData ) string {
if value := label . GetStringValue ( container . Labels , labelDockerNetwork , "" ) ; value != "" {
networkSettings := container . NetworkSettings
if networkSettings . Networks != nil {
network := networkSettings . Networks [ value ]
if network != nil {
return network . Addr
}
log . Warnf ( "Could not find network named '%s' for container '%s'! Maybe you're missing the project's prefix in the label? Defaulting to first available network." , value , container . Name )
}
}
if container . NetworkSettings . NetworkMode . IsHost ( ) {
if container . Node != nil {
if container . Node . IPAddress != "" {
return container . Node . IPAddress
}
}
return "127.0.0.1"
}
if container . NetworkSettings . NetworkMode . IsContainer ( ) {
dockerClient , err := p . createClient ( )
if err != nil {
log . Warnf ( "Unable to get IP address for container %s, error: %s" , container . Name , err )
return ""
}
connectedContainer := container . NetworkSettings . NetworkMode . ConnectedContainer ( )
containerInspected , err := dockerClient . ContainerInspect ( context . Background ( ) , connectedContainer )
if err != nil {
log . Warnf ( "Unable to get IP address for container %s : Failed to inspect container ID %s, error: %s" , container . Name , connectedContainer , err )
return ""
}
return p . getIPAddress ( parseContainer ( containerInspected ) )
}
if p . UseBindPortIP {
port := getPortV1 ( container )
for netPort , portBindings := range container . NetworkSettings . Ports {
if string ( netPort ) == port + "/TCP" || string ( netPort ) == port + "/UDP" {
for _ , p := range portBindings {
return p . HostIP
}
}
}
}
for _ , network := range container . NetworkSettings . Networks {
return network . Addr
}
return ""
}
// Escape beginning slash "/", convert all others to dash "-", and convert underscores "_" to dash "-"
func getSubDomain ( name string ) string {
return strings . Replace ( strings . Replace ( strings . TrimPrefix ( name , "/" ) , "/" , "-" , - 1 ) , "_" , "-" , - 1 )
}
func isBackendLBSwarm ( container dockerData ) bool {
return label . GetBoolValue ( container . Labels , labelBackendLoadBalancerSwarm , false )
}
func getSegmentBackendName ( container dockerData ) string {
if value := label . GetStringValue ( container . SegmentLabels , label . TraefikFrontendBackend , "" ) ; len ( value ) > 0 {
return provider . Normalize ( container . ServiceName + "-" + value )
}
return provider . Normalize ( container . ServiceName + "-" + getDefaultBackendName ( container ) + "-" + container . SegmentName )
}
func getDefaultBackendName ( container dockerData ) string {
if value := label . GetStringValue ( container . SegmentLabels , label . TraefikBackend , "" ) ; len ( value ) != 0 {
return provider . Normalize ( value )
}
if values , err := label . GetStringMultipleStrict ( container . Labels , labelDockerComposeProject , labelDockerComposeService ) ; err == nil {
return provider . Normalize ( values [ labelDockerComposeService ] + "_" + values [ labelDockerComposeProject ] )
}
return provider . Normalize ( container . ServiceName )
}
func getBackendName ( container dockerData ) string {
if len ( container . SegmentName ) > 0 {
return getSegmentBackendName ( container )
}
return getDefaultBackendName ( container )
}
func getPort ( container dockerData ) string {
if value := label . GetStringValue ( container . SegmentLabels , label . TraefikPort , "" ) ; len ( value ) != 0 {
return value
}
// See iteration order in https://blog.golang.org/go-maps-in-action
var ports [ ] nat . Port
for port := range container . NetworkSettings . Ports {
ports = append ( ports , port )
}
less := func ( i , j nat . Port ) bool {
return i . Int ( ) < j . Int ( )
}
nat . Sort ( ports , less )
if len ( ports ) > 0 {
min := ports [ 0 ]
return min . Port ( )
}
return ""
}
func ( p * Provider ) getServers ( containers [ ] dockerData ) map [ string ] types . Server {
var servers map [ string ] types . Server
for i , container := range containers {
if servers == nil {
servers = make ( map [ string ] types . Server )
}
protocol := label . GetStringValue ( container . SegmentLabels , label . TraefikProtocol , label . DefaultProtocol )
ip := p . getIPAddress ( container )
port := getPort ( container )
serverName := "server-" + container . SegmentName + "-" + container . Name
if len ( container . SegmentName ) > 0 {
serverName += "-" + strconv . Itoa ( i )
}
servers [ provider . Normalize ( serverName ) ] = types . Server {
URL : fmt . Sprintf ( "%s://%s:%s" , protocol , ip , port ) ,
Weight : label . GetIntValue ( container . SegmentLabels , label . TraefikWeight , label . DefaultWeightInt ) ,
}
}
return servers
}