2015-11-01 15:35:01 +00:00
package provider
2015-09-12 13:10:03 +00:00
2015-09-07 22:15:14 +00:00
import (
2015-09-15 20:32:09 +00:00
"errors"
2016-04-08 12:20:54 +00:00
"net/http"
2015-09-24 15:16:13 +00:00
"strconv"
"strings"
"text/template"
"time"
2016-04-08 12:20:54 +00:00
"golang.org/x/net/context"
2015-09-10 20:54:37 +00:00
"github.com/BurntSushi/ty/fun"
2015-09-24 12:32:37 +00:00
log "github.com/Sirupsen/logrus"
2015-09-15 20:32:09 +00:00
"github.com/cenkalti/backoff"
2016-03-31 16:57:08 +00:00
"github.com/containous/traefik/safe"
2016-02-24 15:43:39 +00:00
"github.com/containous/traefik/types"
2016-04-08 12:20:54 +00:00
"github.com/docker/engine-api/client"
dockertypes "github.com/docker/engine-api/types"
eventtypes "github.com/docker/engine-api/types/events"
"github.com/docker/engine-api/types/filters"
"github.com/docker/go-connections/sockets"
"github.com/docker/go-connections/tlsconfig"
"github.com/vdemeester/docker-events"
2015-09-07 08:38:58 +00:00
)
2015-09-09 20:39:08 +00:00
2016-04-08 12:20:54 +00:00
// DockerAPIVersion is a constant holding the version of the Docker API traefik will use
const DockerAPIVersion string = "1.21"
2015-11-01 18:29:47 +00:00
// Docker holds configurations of the Docker provider.
2015-11-02 18:48:34 +00:00
type Docker struct {
2016-05-24 15:31:50 +00:00
BaseProvider
Endpoint string ` description:"Docker server endpoint. Can be a tcp or a unix socket endpoint" `
Domain string ` description:"Default domain used" `
TLS * DockerTLS ` description:"Enable Docker TLS support" `
2015-11-20 15:05:06 +00:00
}
// DockerTLS holds TLS specific configurations
type DockerTLS struct {
2016-05-03 14:52:14 +00:00
CA string ` description:"TLS CA" `
Cert string ` description:"TLS cert" `
Key string ` description:"TLS key" `
InsecureSkipVerify bool ` description:"TLS insecure skip verify" `
2015-09-09 20:39:08 +00:00
}
2016-04-08 12:20:54 +00:00
func ( provider * Docker ) createClient ( ) ( client . APIClient , error ) {
var httpClient * http . Client
httpHeaders := map [ string ] string {
// FIXME(vdemeester) use version here O:)
"User-Agent" : "Traefik" ,
}
if provider . TLS != nil {
tlsOptions := tlsconfig . Options {
CAFile : provider . TLS . CA ,
CertFile : provider . TLS . Cert ,
KeyFile : provider . TLS . Key ,
InsecureSkipVerify : provider . TLS . InsecureSkipVerify ,
}
config , err := tlsconfig . Client ( tlsOptions )
if err != nil {
return nil , err
}
tr := & http . Transport {
TLSClientConfig : config ,
}
proto , addr , _ , err := client . ParseHost ( provider . Endpoint )
if err != nil {
return nil , err
}
sockets . ConfigureTransport ( tr , proto , addr )
httpClient = & http . Client {
Transport : tr ,
}
}
return client . NewClient ( provider . Endpoint , DockerAPIVersion , httpClient , httpHeaders )
}
2015-11-01 18:29:47 +00:00
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
2016-04-13 18:36:23 +00:00
func ( provider * Docker ) Provide ( configurationChan chan <- types . ConfigMessage , pool * safe . Pool ) error {
// TODO register this routine in pool, and watch for stop channel
2016-03-31 16:57:08 +00:00
safe . Go ( func ( ) {
2016-02-25 17:30:13 +00:00
operation := func ( ) error {
var err error
2015-11-01 18:29:47 +00:00
2016-04-08 12:20:54 +00:00
dockerClient , err := provider . createClient ( )
2016-02-25 17:30:13 +00:00
if err != nil {
log . Errorf ( "Failed to create a client for docker, error: %s" , err )
return err
}
2016-04-08 12:20:54 +00:00
version , err := dockerClient . ServerVersion ( context . Background ( ) )
log . Debugf ( "Docker connection established with docker %s (API %s)" , version . Version , version . APIVersion )
containers , err := listContainers ( dockerClient )
2016-02-25 17:30:13 +00:00
if err != nil {
2016-04-08 12:20:54 +00:00
log . Errorf ( "Failed to list containers for docker, error %s" , err )
2016-02-25 17:30:13 +00:00
return err
}
2016-04-08 12:20:54 +00:00
configuration := provider . loadDockerConfig ( containers )
2016-02-25 17:30:13 +00:00
configurationChan <- types . ConfigMessage {
ProviderName : "docker" ,
Configuration : configuration ,
}
if provider . Watch {
2016-04-08 12:20:54 +00:00
ctx , cancel := context . WithCancel ( context . Background ( ) )
f := filters . NewArgs ( )
f . Add ( "type" , "container" )
options := dockertypes . EventsOptions {
Filters : f ,
}
eventHandler := events . NewHandler ( events . ByAction )
startStopHandle := func ( m eventtypes . Message ) {
log . Debugf ( "Docker event received %+v" , m )
containers , err := listContainers ( dockerClient )
if err != nil {
log . Errorf ( "Failed to list containers for docker, error %s" , err )
// Call cancel to get out of the monitor
cancel ( )
2015-11-01 18:29:47 +00:00
}
2016-04-08 12:20:54 +00:00
configuration := provider . loadDockerConfig ( containers )
if configuration != nil {
configurationChan <- types . ConfigMessage {
ProviderName : "docker" ,
Configuration : configuration ,
2015-09-10 20:54:37 +00:00
}
2015-09-10 07:06:37 +00:00
}
2015-09-09 20:39:08 +00:00
}
2016-04-08 12:20:54 +00:00
eventHandler . Handle ( "start" , startStopHandle )
eventHandler . Handle ( "die" , startStopHandle )
2016-04-13 18:36:23 +00:00
2016-04-08 12:20:54 +00:00
errChan := events . MonitorWithHandler ( ctx , dockerClient , options , eventHandler )
2016-04-16 12:46:35 +00:00
pool . Go ( func ( stop chan bool ) {
for {
select {
case <- stop :
cancel ( )
return
}
}
} )
2016-04-08 12:20:54 +00:00
if err := <- errChan ; err != nil {
return err
}
2015-11-01 18:29:47 +00:00
}
2016-02-25 17:30:13 +00:00
return nil
}
notify := func ( err error , time time . Duration ) {
log . Errorf ( "Docker connection error %+v, retrying in %s" , err , time )
}
err := backoff . RetryNotify ( operation , backoff . NewExponentialBackOff ( ) , notify )
if err != nil {
log . Fatalf ( "Cannot connect to docker server %+v" , err )
}
2016-03-31 16:57:08 +00:00
} )
2015-11-01 18:29:47 +00:00
2015-10-01 10:04:25 +00:00
return nil
2015-09-07 08:38:58 +00:00
}
2016-04-08 12:20:54 +00:00
func ( provider * Docker ) loadDockerConfig ( containersInspected [ ] dockertypes . ContainerJSON ) * types . Configuration {
2015-10-08 19:21:51 +00:00
var DockerFuncMap = template . FuncMap {
2015-11-13 10:50:32 +00:00
"getBackend" : provider . getBackend ,
"getPort" : provider . getPort ,
"getWeight" : provider . getWeight ,
"getDomain" : provider . getDomain ,
"getProtocol" : provider . getProtocol ,
"getPassHostHeader" : provider . getPassHostHeader ,
2016-02-01 15:08:58 +00:00
"getEntryPoints" : provider . getEntryPoints ,
2015-11-13 10:50:32 +00:00
"getFrontendRule" : provider . getFrontendRule ,
"replace" : replace ,
2015-09-10 20:54:37 +00:00
}
// filter containers
2016-04-08 12:20:54 +00:00
filteredContainers := fun . Filter ( containerFilter , containersInspected ) . ( [ ] dockertypes . ContainerJSON )
2015-09-10 20:54:37 +00:00
2016-04-08 12:20:54 +00:00
frontends := map [ string ] [ ] dockertypes . ContainerJSON { }
2015-09-10 20:54:37 +00:00
for _ , container := range filteredContainers {
2015-10-23 07:49:19 +00:00
frontends [ provider . getFrontendName ( container ) ] = append ( frontends [ provider . getFrontendName ( container ) ] , container )
2015-09-07 08:38:58 +00:00
}
2015-09-09 14:49:51 +00:00
templateObjects := struct {
2016-04-08 12:20:54 +00:00
Containers [ ] dockertypes . ContainerJSON
Frontends map [ string ] [ ] dockertypes . ContainerJSON
2015-09-09 15:50:02 +00:00
Domain string
2015-09-07 08:38:58 +00:00
} {
2015-09-10 20:54:37 +00:00
filteredContainers ,
2015-10-23 07:49:19 +00:00
frontends ,
2015-09-09 15:10:43 +00:00
provider . Domain ,
2015-09-07 08:38:58 +00:00
}
2015-11-13 10:50:32 +00:00
configuration , err := provider . getConfiguration ( "templates/docker.tmpl" , DockerFuncMap , templateObjects )
2015-09-07 08:38:58 +00:00
if err != nil {
2015-11-13 10:50:32 +00:00
log . Error ( err )
2015-09-07 08:38:58 +00:00
}
2015-11-13 10:50:32 +00:00
return configuration
}
2015-09-07 08:38:58 +00:00
2016-04-08 12:20:54 +00:00
func containerFilter ( container dockertypes . ContainerJSON ) bool {
2015-11-13 10:50:32 +00:00
if len ( container . NetworkSettings . Ports ) == 0 {
log . Debugf ( "Filtering container without port %s" , container . Name )
return false
2015-09-07 08:38:58 +00:00
}
2015-11-13 10:50:32 +00:00
_ , err := strconv . Atoi ( container . Config . Labels [ "traefik.port" ] )
if len ( container . NetworkSettings . Ports ) > 1 && err != nil {
log . Debugf ( "Filtering container with more than 1 port and no traefik.port label %s" , container . Name )
return false
}
if container . Config . Labels [ "traefik.enable" ] == "false" {
log . Debugf ( "Filtering disabled container %s" , container . Name )
return false
}
return true
2015-09-09 14:49:51 +00:00
}
2016-04-08 12:20:54 +00:00
func ( provider * Docker ) getFrontendName ( container dockertypes . ContainerJSON ) string {
2015-10-23 07:49:19 +00:00
// Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78
2016-03-27 00:05:17 +00:00
return normalize ( provider . getFrontendRule ( container ) )
2015-10-23 07:49:19 +00:00
}
2015-11-13 10:50:32 +00:00
// GetFrontendRule returns the frontend rule for the specified container, using
// it's label. It returns a default one (Host) if the label is not present.
2016-04-08 12:20:54 +00:00
func ( provider * Docker ) getFrontendRule ( container dockertypes . ContainerJSON ) string {
2016-03-30 17:05:43 +00:00
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
// TODO: backwards compatibility with DEPRECATED rule.Value
if value , ok := container . Config . Labels [ "traefik.frontend.value" ] ; ok {
2016-03-31 10:59:50 +00:00
log . Warnf ( "Label traefik.frontend.value=%s is DEPRECATED (will be removed in v1.0.0), please refer to the rule label: https://github.com/containous/traefik/blob/master/docs/index.md#docker" , value )
2016-03-30 17:05:43 +00:00
rule , _ := container . Config . Labels [ "traefik.frontend.rule" ]
return rule + ":" + value
}
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
2015-11-13 10:50:32 +00:00
if label , err := getLabel ( container , "traefik.frontend.rule" ) ; err == nil {
return label
}
2016-03-27 00:05:17 +00:00
return "Host:" + getEscapedName ( container . Name ) + "." + provider . Domain
2015-11-13 10:50:32 +00:00
}
2016-04-08 12:20:54 +00:00
func ( provider * Docker ) getBackend ( container dockertypes . ContainerJSON ) string {
2015-11-13 10:50:32 +00:00
if label , err := getLabel ( container , "traefik.backend" ) ; err == nil {
return label
}
2016-03-27 00:05:17 +00:00
return normalize ( container . Name )
2015-11-13 10:50:32 +00:00
}
2016-04-08 12:20:54 +00:00
func ( provider * Docker ) getPort ( container dockertypes . ContainerJSON ) string {
2015-11-13 10:50:32 +00:00
if label , err := getLabel ( container , "traefik.port" ) ; err == nil {
return label
}
for key := range container . NetworkSettings . Ports {
return key . Port ( )
}
return ""
}
2016-04-08 12:20:54 +00:00
func ( provider * Docker ) getWeight ( container dockertypes . ContainerJSON ) string {
2015-11-13 10:50:32 +00:00
if label , err := getLabel ( container , "traefik.weight" ) ; err == nil {
return label
}
2016-03-27 00:05:17 +00:00
return "1"
2015-11-13 10:50:32 +00:00
}
2016-04-08 12:20:54 +00:00
func ( provider * Docker ) getDomain ( container dockertypes . ContainerJSON ) string {
2015-11-13 10:50:32 +00:00
if label , err := getLabel ( container , "traefik.domain" ) ; err == nil {
return label
}
return provider . Domain
2015-10-23 07:49:19 +00:00
}
2016-04-08 12:20:54 +00:00
func ( provider * Docker ) getProtocol ( container dockertypes . ContainerJSON ) string {
2015-11-13 10:50:32 +00:00
if label , err := getLabel ( container , "traefik.protocol" ) ; err == nil {
return label
}
return "http"
}
2016-04-08 12:20:54 +00:00
func ( provider * Docker ) getPassHostHeader ( container dockertypes . ContainerJSON ) string {
2015-11-13 10:50:32 +00:00
if passHostHeader , err := getLabel ( container , "traefik.frontend.passHostHeader" ) ; err == nil {
return passHostHeader
}
2016-05-10 11:43:24 +00:00
return "true"
2015-11-13 10:50:32 +00:00
}
2016-04-08 12:20:54 +00:00
func ( provider * Docker ) getEntryPoints ( container dockertypes . ContainerJSON ) [ ] string {
2016-02-01 15:08:58 +00:00
if entryPoints , err := getLabel ( container , "traefik.frontend.entryPoints" ) ; err == nil {
return strings . Split ( entryPoints , "," )
}
return [ ] string { }
}
2016-04-08 12:20:54 +00:00
func getLabel ( container dockertypes . ContainerJSON , label string ) ( string , error ) {
2015-09-09 14:49:51 +00:00
for key , value := range container . Config . Labels {
2015-10-23 07:49:19 +00:00
if key == label {
return value , nil
2015-09-09 14:49:51 +00:00
}
}
2015-10-23 07:49:19 +00:00
return "" , errors . New ( "Label not found:" + label )
}
2016-04-08 12:20:54 +00:00
func getLabels ( container dockertypes . ContainerJSON , labels [ ] string ) ( map [ string ] string , error ) {
2015-11-05 14:14:25 +00:00
var globalErr error
2015-10-26 23:26:35 +00:00
foundLabels := map [ string ] string { }
for _ , label := range labels {
2015-11-13 10:50:32 +00:00
foundLabel , err := getLabel ( container , label )
2015-11-05 14:14:25 +00:00
// Error out only if one of them is defined.
2015-11-01 18:29:47 +00:00
if err != nil {
2015-11-05 14:14:25 +00:00
globalErr = errors . New ( "Label not found: " + label )
continue
2015-10-26 23:26:35 +00:00
}
2015-11-01 18:29:47 +00:00
foundLabels [ label ] = foundLabel
2015-11-05 14:14:25 +00:00
2015-10-26 23:26:35 +00:00
}
2015-11-05 14:14:25 +00:00
return foundLabels , globalErr
2015-10-26 23:26:35 +00:00
}
2016-04-08 12:20:54 +00:00
func listContainers ( dockerClient client . APIClient ) ( [ ] dockertypes . ContainerJSON , error ) {
containerList , err := dockerClient . ContainerList ( context . Background ( ) , dockertypes . ContainerListOptions { } )
if err != nil {
return [ ] dockertypes . ContainerJSON { } , err
}
containersInspected := [ ] dockertypes . ContainerJSON { }
2015-10-23 07:49:19 +00:00
2015-11-13 10:50:32 +00:00
// get inspect containers
for _ , container := range containerList {
2016-04-08 12:20:54 +00:00
containerInspected , err := dockerClient . ContainerInspect ( context . Background ( ) , container . ID )
if err != nil {
log . Warnf ( "Failed to inpsect container %s, error: %s" , container . ID , err )
}
containersInspected = append ( containersInspected , containerInspected )
2015-10-23 07:49:19 +00:00
}
2016-04-08 12:20:54 +00:00
return containersInspected , nil
2015-09-12 13:10:03 +00:00
}