2017-04-17 12:50:02 +02:00
package rancher
2017-01-29 00:01:56 +01:00
import (
2017-02-06 16:28:12 +01:00
"context"
"errors"
"fmt"
2017-02-20 20:41:28 +01:00
"math"
"os"
"strconv"
"strings"
"text/template"
"time"
2017-02-05 22:54:24 +01:00
"github.com/BurntSushi/ty/fun"
2017-01-29 00:01:56 +01:00
"github.com/cenk/backoff"
"github.com/containous/traefik/job"
2017-02-05 22:54:24 +01:00
"github.com/containous/traefik/log"
2017-04-17 12:50:02 +02:00
"github.com/containous/traefik/provider"
2017-02-05 22:54:24 +01:00
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
rancher "github.com/rancher/go-rancher/client"
2017-02-06 16:28:12 +01:00
)
const (
2017-02-06 16:30:21 +01:00
// RancherDefaultWatchTime is the duration of the interval when polling rancher
2017-02-06 16:28:12 +01:00
RancherDefaultWatchTime = 15 * time . Second
2017-01-29 00:01:56 +01:00
)
2017-04-18 12:01:11 +10:00
var (
withoutPagination * rancher . ListOpts
)
2017-04-17 12:50:02 +02:00
var _ provider . Provider = ( * Provider ) ( nil )
2017-01-29 00:01:56 +01:00
2017-04-17 12:50:02 +02:00
// Provider holds configurations of the provider.
type Provider struct {
provider . BaseProvider ` mapstructure:",squash" `
Endpoint string ` description:"Rancher server HTTP(S) endpoint." `
AccessKey string ` description:"Rancher server access key." `
SecretKey string ` description:"Rancher server Secret Key." `
ExposedByDefault bool ` description:"Expose Services by default" `
Domain string ` description:"Default domain used" `
2017-01-29 00:01:56 +01:00
}
type rancherData struct {
2017-02-05 22:54:24 +01:00
Name string
Labels map [ string ] string // List of labels set to container or service
Containers [ ] string
Health string
2017-01-29 00:01:56 +01:00
}
2017-04-18 12:01:11 +10:00
func init ( ) {
withoutPagination = & rancher . ListOpts {
Filters : map [ string ] interface { } { "limit" : 0 } ,
}
}
2017-02-05 22:54:24 +01:00
func ( r rancherData ) String ( ) string {
2017-01-29 00:01:56 +01:00
return fmt . Sprintf ( "{name:%s, labels:%v, containers: %v, health: %s}" , r . Name , r . Labels , r . Containers , r . Health )
}
// Frontend Labels
2017-04-17 12:50:02 +02:00
func ( p * Provider ) getPassHostHeader ( service rancherData ) string {
2017-01-29 00:01:56 +01:00
if passHostHeader , err := getServiceLabel ( service , "traefik.frontend.passHostHeader" ) ; err == nil {
return passHostHeader
}
return "true"
}
2017-04-17 12:50:02 +02:00
func ( p * Provider ) getPriority ( service rancherData ) string {
2017-01-29 00:01:56 +01:00
if priority , err := getServiceLabel ( service , "traefik.frontend.priority" ) ; err == nil {
return priority
}
return "0"
}
2017-04-17 12:50:02 +02:00
func ( p * Provider ) getEntryPoints ( service rancherData ) [ ] string {
2017-01-29 00:01:56 +01:00
if entryPoints , err := getServiceLabel ( service , "traefik.frontend.entryPoints" ) ; err == nil {
return strings . Split ( entryPoints , "," )
}
return [ ] string { }
}
2017-04-17 12:50:02 +02:00
func ( p * Provider ) getFrontendRule ( service rancherData ) string {
2017-01-29 00:01:56 +01:00
if label , err := getServiceLabel ( service , "traefik.frontend.rule" ) ; err == nil {
return label
}
2017-04-17 12:50:02 +02:00
return "Host:" + strings . ToLower ( strings . Replace ( service . Name , "/" , "." , - 1 ) ) + "." + p . Domain
2017-01-29 00:01:56 +01:00
}
2017-04-19 11:14:05 +02:00
func ( p * Provider ) getBasicAuth ( service rancherData ) [ ] string {
if basicAuth , err := getServiceLabel ( service , "traefik.frontend.auth.basic" ) ; err == nil {
return strings . Split ( basicAuth , "," )
}
return [ ] string { }
}
2017-04-17 12:50:02 +02:00
func ( p * Provider ) getFrontendName ( service rancherData ) string {
2017-01-29 00:01:56 +01:00
// Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78
2017-04-17 12:50:02 +02:00
return provider . Normalize ( p . getFrontendRule ( service ) )
2017-01-29 00:01:56 +01:00
}
// Backend Labels
2017-04-17 12:50:02 +02:00
func ( p * Provider ) getLoadBalancerMethod ( service rancherData ) string {
2017-01-29 00:01:56 +01:00
if label , err := getServiceLabel ( service , "traefik.backend.loadbalancer.method" ) ; err == nil {
return label
}
return "wrr"
}
2017-04-17 12:50:02 +02:00
func ( p * Provider ) hasLoadBalancerLabel ( service rancherData ) bool {
2017-01-29 00:01:56 +01:00
_ , errMethod := getServiceLabel ( service , "traefik.backend.loadbalancer.method" )
_ , errSticky := getServiceLabel ( service , "traefik.backend.loadbalancer.sticky" )
if errMethod != nil && errSticky != nil {
return false
}
return true
}
2017-04-17 12:50:02 +02:00
func ( p * Provider ) hasCircuitBreakerLabel ( service rancherData ) bool {
2017-01-29 00:01:56 +01:00
if _ , err := getServiceLabel ( service , "traefik.backend.circuitbreaker.expression" ) ; err != nil {
return false
}
return true
}
2017-04-17 12:50:02 +02:00
func ( p * Provider ) getCircuitBreakerExpression ( service rancherData ) string {
2017-01-29 00:01:56 +01:00
if label , err := getServiceLabel ( service , "traefik.backend.circuitbreaker.expression" ) ; err == nil {
return label
}
return "NetworkErrorRatio() > 1"
}
2017-04-17 12:50:02 +02:00
func ( p * Provider ) getSticky ( service rancherData ) string {
2017-01-29 00:01:56 +01:00
if _ , err := getServiceLabel ( service , "traefik.backend.loadbalancer.sticky" ) ; err == nil {
return "true"
}
return "false"
}
2017-04-17 12:50:02 +02:00
func ( p * Provider ) getBackend ( service rancherData ) string {
2017-01-29 00:01:56 +01:00
if label , err := getServiceLabel ( service , "traefik.backend" ) ; err == nil {
2017-04-17 12:50:02 +02:00
return provider . Normalize ( label )
2017-01-29 00:01:56 +01:00
}
2017-04-17 12:50:02 +02:00
return provider . Normalize ( service . Name )
2017-01-29 00:01:56 +01:00
}
// Generall Application Stuff
2017-04-17 12:50:02 +02:00
func ( p * Provider ) getPort ( service rancherData ) string {
2017-01-29 00:01:56 +01:00
if label , err := getServiceLabel ( service , "traefik.port" ) ; err == nil {
return label
}
return ""
}
2017-04-17 12:50:02 +02:00
func ( p * Provider ) getProtocol ( service rancherData ) string {
2017-01-29 00:01:56 +01:00
if label , err := getServiceLabel ( service , "traefik.protocol" ) ; err == nil {
return label
}
return "http"
}
2017-04-17 12:50:02 +02:00
func ( p * Provider ) getWeight ( service rancherData ) string {
2017-01-29 00:01:56 +01:00
if label , err := getServiceLabel ( service , "traefik.weight" ) ; err == nil {
return label
}
return "0"
}
2017-04-17 12:50:02 +02:00
func ( p * Provider ) getDomain ( service rancherData ) string {
2017-01-29 00:01:56 +01:00
if label , err := getServiceLabel ( service , "traefik.domain" ) ; err == nil {
return label
}
2017-04-17 12:50:02 +02:00
return p . Domain
2017-01-29 00:01:56 +01:00
}
2017-04-17 12:50:02 +02:00
func ( p * Provider ) hasMaxConnLabels ( service rancherData ) bool {
2017-01-29 00:01:56 +01:00
if _ , err := getServiceLabel ( service , "traefik.backend.maxconn.amount" ) ; err != nil {
return false
}
if _ , err := getServiceLabel ( service , "traefik.backend.maxconn.extractorfunc" ) ; err != nil {
return false
}
return true
}
2017-04-17 12:50:02 +02:00
func ( p * Provider ) getMaxConnAmount ( service rancherData ) int64 {
2017-01-29 00:01:56 +01:00
if label , err := getServiceLabel ( service , "traefik.backend.maxconn.amount" ) ; err == nil {
i , errConv := strconv . ParseInt ( label , 10 , 64 )
if errConv != nil {
log . Errorf ( "Unable to parse traefik.backend.maxconn.amount %s" , label )
return math . MaxInt64
}
return i
}
return math . MaxInt64
}
2017-04-17 12:50:02 +02:00
func ( p * Provider ) getMaxConnExtractorFunc ( service rancherData ) string {
2017-01-29 00:01:56 +01:00
if label , err := getServiceLabel ( service , "traefik.backend.maxconn.extractorfunc" ) ; err == nil {
return label
}
return "request.host"
}
func getServiceLabel ( service rancherData , label string ) ( string , error ) {
for key , value := range service . Labels {
if key == label {
return value , nil
}
}
return "" , errors . New ( "Label not found:" + label )
}
2017-04-17 12:50:02 +02:00
func ( p * Provider ) createClient ( ) ( * rancher . RancherClient , error ) {
2017-02-20 20:41:28 +01:00
2017-04-17 12:50:02 +02:00
rancherURL := getenv ( "CATTLE_URL" , p . Endpoint )
accessKey := getenv ( "CATTLE_ACCESS_KEY" , p . AccessKey )
secretKey := getenv ( "CATTLE_SECRET_KEY" , p . SecretKey )
2017-02-20 20:41:28 +01:00
2017-01-29 00:01:56 +01:00
return rancher . NewRancherClient ( & rancher . ClientOpts {
2017-02-20 20:41:28 +01:00
Url : rancherURL ,
AccessKey : accessKey ,
SecretKey : secretKey ,
2017-01-29 00:01:56 +01:00
} )
}
2017-02-20 20:41:28 +01:00
func getenv ( key , fallback string ) string {
value := os . Getenv ( key )
if len ( value ) == 0 {
return fallback
}
return value
}
2017-04-17 12:50:02 +02:00
// Provide allows the rancher provider to provide configurations to traefik
2017-02-05 22:54:24 +01:00
// using the given configuration channel.
2017-04-17 12:50:02 +02:00
func ( p * Provider ) Provide ( configurationChan chan <- types . ConfigMessage , pool * safe . Pool , constraints types . Constraints ) error {
2017-01-29 00:01:56 +01:00
safe . Go ( func ( ) {
operation := func ( ) error {
2017-04-17 12:50:02 +02:00
rancherClient , err := p . createClient ( )
2017-02-20 20:41:28 +01:00
if err != nil {
log . Errorf ( "Failed to create a client for rancher, error: %s" , err )
return err
}
2017-02-06 16:28:12 +01:00
ctx := context . Background ( )
2017-01-29 00:01:56 +01:00
var environments = listRancherEnvironments ( rancherClient )
var services = listRancherServices ( rancherClient )
var container = listRancherContainer ( rancherClient )
var rancherData = parseRancherData ( environments , services , container )
2017-04-17 12:50:02 +02:00
configuration := p . loadRancherConfig ( rancherData )
2017-01-29 00:01:56 +01:00
configurationChan <- types . ConfigMessage {
ProviderName : "rancher" ,
Configuration : configuration ,
}
2017-04-17 12:50:02 +02:00
if p . Watch {
2017-02-06 16:28:12 +01:00
_ , cancel := context . WithCancel ( ctx )
ticker := time . NewTicker ( RancherDefaultWatchTime )
pool . Go ( func ( stop chan bool ) {
for {
select {
case <- ticker . C :
2017-04-17 12:50:02 +02:00
log . Debugf ( "Refreshing new Data from Provider API" )
2017-02-06 16:28:12 +01:00
var environments = listRancherEnvironments ( rancherClient )
var services = listRancherServices ( rancherClient )
var container = listRancherContainer ( rancherClient )
rancherData := parseRancherData ( environments , services , container )
2017-04-17 12:50:02 +02:00
configuration := p . loadRancherConfig ( rancherData )
2017-02-06 16:28:12 +01:00
if configuration != nil {
configurationChan <- types . ConfigMessage {
ProviderName : "rancher" ,
Configuration : configuration ,
}
}
case <- stop :
ticker . Stop ( )
cancel ( )
return
}
}
} )
}
2017-01-29 00:01:56 +01:00
return nil
}
notify := func ( err error , time time . Duration ) {
2017-04-17 12:50:02 +02:00
log . Errorf ( "Provider connection error %+v, retrying in %s" , err , time )
2017-01-29 00:01:56 +01:00
}
err := backoff . RetryNotify ( operation , job . NewBackOff ( backoff . NewExponentialBackOff ( ) ) , notify )
if err != nil {
2017-04-17 12:50:02 +02:00
log . Errorf ( "Cannot connect to Provider Endpoint %+v" , err )
2017-01-29 00:01:56 +01:00
}
} )
return nil
}
2017-02-05 22:54:24 +01:00
func listRancherEnvironments ( client * rancher . RancherClient ) [ ] * rancher . Environment {
2017-01-29 00:01:56 +01:00
var environmentList = [ ] * rancher . Environment { }
2017-04-18 12:01:11 +10:00
environments , err := client . Environment . List ( withoutPagination )
2017-01-29 00:01:56 +01:00
if err != nil {
2017-04-17 12:50:02 +02:00
log . Errorf ( "Cannot get Provider Environments %+v" , err )
2017-01-29 00:01:56 +01:00
}
2017-02-06 16:30:21 +01:00
for k := range environments . Data {
2017-01-29 00:01:56 +01:00
environmentList = append ( environmentList , & environments . Data [ k ] )
}
return environmentList
}
2017-02-05 22:54:24 +01:00
func listRancherServices ( client * rancher . RancherClient ) [ ] * rancher . Service {
2017-01-29 00:01:56 +01:00
var servicesList = [ ] * rancher . Service { }
2017-04-18 12:01:11 +10:00
services , err := client . Service . List ( withoutPagination )
2017-01-29 00:01:56 +01:00
if err != nil {
2017-04-17 12:50:02 +02:00
log . Errorf ( "Cannot get Provider Services %+v" , err )
2017-01-29 00:01:56 +01:00
}
2017-02-06 16:30:21 +01:00
for k := range services . Data {
2017-01-29 00:01:56 +01:00
servicesList = append ( servicesList , & services . Data [ k ] )
}
return servicesList
}
2017-02-05 22:54:24 +01:00
func listRancherContainer ( client * rancher . RancherClient ) [ ] * rancher . Container {
2017-01-29 00:01:56 +01:00
2017-02-05 22:54:24 +01:00
containerList := [ ] * rancher . Container { }
2017-01-29 00:01:56 +01:00
2017-04-18 12:01:11 +10:00
container , err := client . Container . List ( withoutPagination )
2017-01-29 00:01:56 +01:00
2017-02-05 22:54:24 +01:00
log . Debugf ( "first container len: %i" , len ( container . Data ) )
2017-01-29 00:01:56 +01:00
if err != nil {
2017-04-17 12:50:02 +02:00
log . Errorf ( "Cannot get Provider Services %+v" , err )
2017-01-29 00:01:56 +01:00
}
2017-02-05 22:54:24 +01:00
valid := true
for valid {
2017-02-06 16:28:12 +01:00
for k := range container . Data {
2017-02-05 22:54:24 +01:00
containerList = append ( containerList , & container . Data [ k ] )
}
container , err = container . Next ( )
if err != nil {
break
}
if container == nil || len ( container . Data ) == 0 {
valid = false
}
2017-01-29 00:01:56 +01:00
}
return containerList
}
func parseRancherData ( environments [ ] * rancher . Environment , services [ ] * rancher . Service , containers [ ] * rancher . Container ) [ ] rancherData {
var rancherDataList [ ] rancherData
for _ , environment := range environments {
for _ , service := range services {
if service . EnvironmentId != environment . Id {
continue
}
rancherData := rancherData {
2017-02-05 22:54:24 +01:00
Name : environment . Name + "/" + service . Name ,
Health : service . HealthState ,
Labels : make ( map [ string ] string ) ,
Containers : [ ] string { } ,
2017-01-29 00:01:56 +01:00
}
for key , value := range service . LaunchConfig . Labels {
rancherData . Labels [ key ] = value . ( string )
}
for _ , container := range containers {
for key , value := range container . Labels {
if key == "io.rancher.stack_service.name" && value == rancherData . Name {
rancherData . Containers = append ( rancherData . Containers , container . PrimaryIpAddress )
}
}
}
rancherDataList = append ( rancherDataList , rancherData )
}
}
return rancherDataList
}
2017-04-17 12:50:02 +02:00
func ( p * Provider ) loadRancherConfig ( services [ ] rancherData ) * types . Configuration {
2017-01-29 00:01:56 +01:00
var RancherFuncMap = template . FuncMap {
2017-04-17 12:50:02 +02:00
"getPort" : p . getPort ,
"getBackend" : p . getBackend ,
"getWeight" : p . getWeight ,
"getDomain" : p . getDomain ,
"getProtocol" : p . getProtocol ,
"getPassHostHeader" : p . getPassHostHeader ,
"getPriority" : p . getPriority ,
"getEntryPoints" : p . getEntryPoints ,
2017-04-19 11:14:05 +02:00
"getBasicAuth" : p . getBasicAuth ,
2017-04-17 12:50:02 +02:00
"getFrontendRule" : p . getFrontendRule ,
"hasCircuitBreakerLabel" : p . hasCircuitBreakerLabel ,
"getCircuitBreakerExpression" : p . getCircuitBreakerExpression ,
"hasLoadBalancerLabel" : p . hasLoadBalancerLabel ,
"getLoadBalancerMethod" : p . getLoadBalancerMethod ,
"hasMaxConnLabels" : p . hasMaxConnLabels ,
"getMaxConnAmount" : p . getMaxConnAmount ,
"getMaxConnExtractorFunc" : p . getMaxConnExtractorFunc ,
"getSticky" : p . getSticky ,
2017-01-29 00:01:56 +01:00
}
// filter services
filteredServices := fun . Filter ( func ( service rancherData ) bool {
2017-04-17 12:50:02 +02:00
return p . serviceFilter ( service )
2017-01-29 00:01:56 +01:00
} , services ) . ( [ ] rancherData )
frontends := map [ string ] rancherData { }
backends := map [ string ] rancherData { }
for _ , service := range filteredServices {
2017-04-17 12:50:02 +02:00
frontendName := p . getFrontendName ( service )
2017-01-29 00:01:56 +01:00
frontends [ frontendName ] = service
2017-04-17 12:50:02 +02:00
backendName := p . getBackend ( service )
2017-01-29 00:01:56 +01:00
backends [ backendName ] = service
}
templateObjects := struct {
2017-02-05 22:54:24 +01:00
Frontends map [ string ] rancherData
Backends map [ string ] rancherData
Domain string
2017-01-29 00:01:56 +01:00
} {
frontends ,
backends ,
2017-04-17 12:50:02 +02:00
p . Domain ,
2017-01-29 00:01:56 +01:00
}
2017-04-17 12:50:02 +02:00
configuration , err := p . GetConfiguration ( "templates/rancher.tmpl" , RancherFuncMap , templateObjects )
2017-01-29 00:01:56 +01:00
if err != nil {
log . Error ( err )
}
return configuration
}
2017-04-17 12:50:02 +02:00
func ( p * Provider ) serviceFilter ( service rancherData ) bool {
2017-01-29 00:01:56 +01:00
if service . Labels [ "traefik.port" ] == "" {
log . Debugf ( "Filtering service %s without traefik.port label" , service . Name )
2017-02-05 22:54:24 +01:00
return false
2017-01-29 00:01:56 +01:00
}
2017-04-17 12:50:02 +02:00
if ! isServiceEnabled ( service , p . ExposedByDefault ) {
2017-01-29 00:01:56 +01:00
log . Debugf ( "Filtering disabled service %s" , service . Name )
return false
}
if service . Health != "" && service . Health != "healthy" {
log . Debugf ( "Filtering unhealthy or starting service %s" , service . Name )
return false
}
return true
}
func isServiceEnabled ( service rancherData , exposedByDefault bool ) bool {
2017-02-05 22:54:24 +01:00
if service . Labels [ "traefik.enable" ] != "" {
2017-01-29 00:01:56 +01:00
var v = service . Labels [ "traefik.enable" ]
return exposedByDefault && v != "false" || v == "true"
}
2017-02-06 00:58:05 +01:00
return exposedByDefault
2017-01-29 00:01:56 +01:00
}