2017-05-08 04:20:38 +03:00
package rancher
import (
"context"
"os"
"time"
"github.com/cenk/backoff"
"github.com/containous/traefik/job"
"github.com/containous/traefik/log"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
2017-09-15 13:30:03 +03:00
"github.com/mitchellh/mapstructure"
2017-11-05 15:02:03 +03:00
rancher "github.com/rancher/go-rancher/v2"
2017-05-08 04:20:38 +03:00
)
2017-09-15 13:30:03 +03:00
const (
labelRancherStackServiceName = "io.rancher.stack_service.name"
hostNetwork = "host"
)
2017-07-10 17:58:12 +03:00
var withoutPagination * rancher . ListOpts
2017-05-08 04:20:38 +03:00
// APIConfiguration contains configuration properties specific to the Rancher
// API provider.
type APIConfiguration struct {
Endpoint string ` description:"Rancher server API HTTP(S) endpoint" `
AccessKey string ` description:"Rancher server API access key" `
SecretKey string ` description:"Rancher server API secret key" `
}
func init ( ) {
withoutPagination = & rancher . ListOpts {
Filters : map [ string ] interface { } { "limit" : 0 } ,
}
}
func ( p * Provider ) createClient ( ) ( * rancher . RancherClient , error ) {
rancherURL := getenv ( "CATTLE_URL" , p . API . Endpoint )
accessKey := getenv ( "CATTLE_ACCESS_KEY" , p . API . AccessKey )
secretKey := getenv ( "CATTLE_SECRET_KEY" , p . API . SecretKey )
return rancher . NewRancherClient ( & rancher . ClientOpts {
Url : rancherURL ,
AccessKey : accessKey ,
SecretKey : secretKey ,
} )
}
func getenv ( key , fallback string ) string {
value := os . Getenv ( key )
if len ( value ) == 0 {
return fallback
}
return value
}
func ( p * Provider ) apiProvide ( configurationChan chan <- types . ConfigMessage , pool * safe . Pool , constraints types . Constraints ) error {
p . Constraints = append ( p . Constraints , constraints ... )
if p . API == nil {
p . API = & APIConfiguration { }
}
safe . Go ( func ( ) {
operation := func ( ) error {
rancherClient , err := p . createClient ( )
if err != nil {
log . Errorf ( "Failed to create a client for rancher, error: %s" , err )
return err
}
ctx := context . Background ( )
2017-11-05 15:02:03 +03:00
var stacks = listRancherStacks ( rancherClient )
2017-05-08 04:20:38 +03:00
var services = listRancherServices ( rancherClient )
var container = listRancherContainer ( rancherClient )
2017-11-05 15:02:03 +03:00
var rancherData = parseAPISourcedRancherData ( stacks , services , container )
2017-05-08 04:20:38 +03:00
2017-12-02 21:29:09 +03:00
configuration := p . buildConfiguration ( rancherData )
2017-05-08 04:20:38 +03:00
configurationChan <- types . ConfigMessage {
ProviderName : "rancher" ,
Configuration : configuration ,
}
if p . Watch {
_ , cancel := context . WithCancel ( ctx )
ticker := time . NewTicker ( time . Second * time . Duration ( p . RefreshSeconds ) )
pool . Go ( func ( stop chan bool ) {
for {
select {
case <- ticker . C :
2018-01-22 13:00:07 +03:00
checkAPI , errAPI := rancherClient . ApiKey . List ( withoutPagination )
if errAPI != nil {
log . Errorf ( "Cannot establish connection: %+v, Rancher API return: %+v; Skipping refresh Data from Rancher API." , errAPI , checkAPI )
} else {
log . Debugf ( "Refreshing new Data from Rancher API" )
stacks := listRancherStacks ( rancherClient )
services := listRancherServices ( rancherClient )
container := listRancherContainer ( rancherClient )
rancherData := parseAPISourcedRancherData ( stacks , services , container )
2018-01-24 13:57:06 +03:00
configuration := p . buildConfiguration ( rancherData )
2018-01-22 13:00:07 +03:00
if configuration != nil {
configurationChan <- types . ConfigMessage {
ProviderName : "rancher" ,
Configuration : configuration ,
}
2017-05-08 04:20:38 +03:00
}
}
case <- stop :
ticker . Stop ( )
cancel ( )
return
}
}
} )
}
return nil
}
notify := func ( err error , time time . Duration ) {
log . Errorf ( "Provider connection error %+v, retrying in %s" , err , time )
}
err := backoff . RetryNotify ( operation , job . NewBackOff ( backoff . NewExponentialBackOff ( ) ) , notify )
if err != nil {
log . Errorf ( "Cannot connect to Provider Endpoint %+v" , err )
}
} )
return nil
}
2017-11-05 15:02:03 +03:00
func listRancherStacks ( client * rancher . RancherClient ) [ ] * rancher . Stack {
2017-05-08 04:20:38 +03:00
2017-12-02 21:29:09 +03:00
var stackList [ ] * rancher . Stack
2017-05-08 04:20:38 +03:00
2017-11-05 15:02:03 +03:00
stacks , err := client . Stack . List ( withoutPagination )
2017-05-08 04:20:38 +03:00
if err != nil {
2017-11-05 15:02:03 +03:00
log . Errorf ( "Cannot get Provider Stacks %+v" , err )
2017-05-08 04:20:38 +03:00
}
2017-11-05 15:02:03 +03:00
for k := range stacks . Data {
stackList = append ( stackList , & stacks . Data [ k ] )
2017-05-08 04:20:38 +03:00
}
2017-11-05 15:02:03 +03:00
return stackList
2017-05-08 04:20:38 +03:00
}
func listRancherServices ( client * rancher . RancherClient ) [ ] * rancher . Service {
2017-12-02 21:29:09 +03:00
var servicesList [ ] * rancher . Service
2017-05-08 04:20:38 +03:00
services , err := client . Service . List ( withoutPagination )
if err != nil {
log . Errorf ( "Cannot get Provider Services %+v" , err )
}
for k := range services . Data {
servicesList = append ( servicesList , & services . Data [ k ] )
}
return servicesList
}
func listRancherContainer ( client * rancher . RancherClient ) [ ] * rancher . Container {
2017-12-02 21:29:09 +03:00
var containerList [ ] * rancher . Container
2017-05-08 04:20:38 +03:00
container , err := client . Container . List ( withoutPagination )
if err != nil {
log . Errorf ( "Cannot get Provider Services %+v" , err )
}
valid := true
for valid {
for k := range container . Data {
containerList = append ( containerList , & container . Data [ k ] )
}
container , err = container . Next ( )
if err != nil {
break
}
if container == nil || len ( container . Data ) == 0 {
valid = false
}
}
return containerList
}
2017-11-05 15:02:03 +03:00
func parseAPISourcedRancherData ( stacks [ ] * rancher . Stack , services [ ] * rancher . Service , containers [ ] * rancher . Container ) [ ] rancherData {
2017-05-08 04:20:38 +03:00
var rancherDataList [ ] rancherData
2017-11-05 15:02:03 +03:00
for _ , stack := range stacks {
2017-05-08 04:20:38 +03:00
for _ , service := range services {
2017-09-06 11:50:04 +03:00
2017-11-05 15:02:03 +03:00
if service . StackId != stack . Id {
2017-05-08 04:20:38 +03:00
continue
}
2017-12-02 21:29:09 +03:00
rData := rancherData {
2017-11-05 15:02:03 +03:00
Name : service . Name + "/" + stack . Name ,
2017-05-08 04:20:38 +03:00
Health : service . HealthState ,
State : service . State ,
Labels : make ( map [ string ] string ) ,
Containers : [ ] string { } ,
}
if service . LaunchConfig == nil || service . LaunchConfig . Labels == nil {
2017-11-05 15:02:03 +03:00
log . Warnf ( "Rancher Service Labels are missing. Stack: %s, service: %s" , stack . Name , service . Name )
2017-05-08 04:20:38 +03:00
} else {
for key , value := range service . LaunchConfig . Labels {
2017-12-02 21:29:09 +03:00
rData . Labels [ key ] = value . ( string )
2017-05-08 04:20:38 +03:00
}
}
for _ , container := range containers {
2017-11-05 15:02:03 +03:00
if container . Labels [ labelRancherStackServiceName ] == stack . Name + "/" + service . Name &&
2017-05-08 04:20:38 +03:00
containerFilter ( container . Name , container . HealthState , container . State ) {
2017-09-15 13:30:03 +03:00
if container . NetworkMode == hostNetwork {
var endpoints [ ] * rancher . PublicEndpoint
err := mapstructure . Decode ( service . PublicEndpoints , & endpoints )
if err != nil {
log . Errorf ( "Failed to decode PublicEndpoint: %v" , err )
continue
}
if len ( endpoints ) > 0 {
2017-12-02 21:29:09 +03:00
rData . Containers = append ( rData . Containers , endpoints [ 0 ] . IpAddress )
2017-09-15 13:30:03 +03:00
}
} else {
2017-12-02 21:29:09 +03:00
rData . Containers = append ( rData . Containers , container . PrimaryIpAddress )
2017-09-15 13:30:03 +03:00
}
2017-05-08 04:20:38 +03:00
}
}
2017-12-02 21:29:09 +03:00
rancherDataList = append ( rancherDataList , rData )
2017-05-08 04:20:38 +03:00
}
}
return rancherDataList
}