Merge pull request #1120 from SantoDE/feature-rancher-integration
Feature rancher integration
This commit is contained in:
commit
bff654b843
7 changed files with 974 additions and 6 deletions
|
@ -51,6 +51,7 @@ type GlobalConfiguration struct {
|
||||||
Mesos *provider.Mesos `description:"Enable Mesos backend"`
|
Mesos *provider.Mesos `description:"Enable Mesos backend"`
|
||||||
Eureka *provider.Eureka `description:"Enable Eureka backend"`
|
Eureka *provider.Eureka `description:"Enable Eureka backend"`
|
||||||
ECS *provider.ECS `description:"Enable ECS backend"`
|
ECS *provider.ECS `description:"Enable ECS backend"`
|
||||||
|
Rancher *provider.Rancher `description:"Enable Rancher backend"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultEntryPoints holds default entry points
|
// DefaultEntryPoints holds default entry points
|
||||||
|
@ -400,6 +401,11 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||||
defaultECS.Cluster = "default"
|
defaultECS.Cluster = "default"
|
||||||
defaultECS.Constraints = types.Constraints{}
|
defaultECS.Constraints = types.Constraints{}
|
||||||
|
|
||||||
|
//default Rancher
|
||||||
|
var defaultRancher provider.Rancher
|
||||||
|
defaultRancher.Watch = true
|
||||||
|
defaultRancher.ExposedByDefault = true
|
||||||
|
|
||||||
defaultConfiguration := GlobalConfiguration{
|
defaultConfiguration := GlobalConfiguration{
|
||||||
Docker: &defaultDocker,
|
Docker: &defaultDocker,
|
||||||
File: &defaultFile,
|
File: &defaultFile,
|
||||||
|
@ -413,8 +419,13 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||||
Kubernetes: &defaultKubernetes,
|
Kubernetes: &defaultKubernetes,
|
||||||
Mesos: &defaultMesos,
|
Mesos: &defaultMesos,
|
||||||
ECS: &defaultECS,
|
ECS: &defaultECS,
|
||||||
|
Rancher: &defaultRancher,
|
||||||
Retry: &Retry{},
|
Retry: &Retry{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//default Rancher
|
||||||
|
//@TODO: ADD
|
||||||
|
|
||||||
return &TraefikConfiguration{
|
return &TraefikConfiguration{
|
||||||
GlobalConfiguration: defaultConfiguration,
|
GlobalConfiguration: defaultConfiguration,
|
||||||
}
|
}
|
||||||
|
|
18
glide.lock
generated
18
glide.lock
generated
|
@ -1,5 +1,5 @@
|
||||||
hash: a0b0abed2162e490cbe75a6a36ebaaf39e748ee80e419e879e7253679a0bc134
|
hash: b2ac93355c3f551a75216a800337cee9321f6c9a04a18ab1fa8d8152e89b7595
|
||||||
updated: 2017-02-05T18:09:09.856588042Z
|
updated: 2017-02-05T23:00:24.927243212+01:00
|
||||||
imports:
|
imports:
|
||||||
- name: bitbucket.org/ww/goautoneg
|
- name: bitbucket.org/ww/goautoneg
|
||||||
version: 75cd24fc2f2c2a2088577d12123ddee5f54e0675
|
version: 75cd24fc2f2c2a2088577d12123ddee5f54e0675
|
||||||
|
@ -300,6 +300,8 @@ imports:
|
||||||
version: 44d81051d367757e1c7c6a5a86423ece9afcf63c
|
version: 44d81051d367757e1c7c6a5a86423ece9afcf63c
|
||||||
- name: github.com/gorilla/context
|
- name: github.com/gorilla/context
|
||||||
version: 1ea25387ff6f684839d82767c1733ff4d4d15d0a
|
version: 1ea25387ff6f684839d82767c1733ff4d4d15d0a
|
||||||
|
- name: github.com/gorilla/websocket
|
||||||
|
version: c36f2fe5c330f0ac404b616b96c438b8616b1aaf
|
||||||
- name: github.com/hashicorp/consul
|
- name: github.com/hashicorp/consul
|
||||||
version: fce7d75609a04eeb9d4bf41c8dc592aac18fc97d
|
version: fce7d75609a04eeb9d4bf41c8dc592aac18fc97d
|
||||||
subpackages:
|
subpackages:
|
||||||
|
@ -370,7 +372,7 @@ imports:
|
||||||
- name: github.com/mvdan/xurls
|
- name: github.com/mvdan/xurls
|
||||||
version: fa08908f19eca8c491d68c6bd8b4b44faea6daf8
|
version: fa08908f19eca8c491d68c6bd8b4b44faea6daf8
|
||||||
- name: github.com/NYTimes/gziphandler
|
- name: github.com/NYTimes/gziphandler
|
||||||
version: f6438dbf4a82c56684964b03956aa727b0d7816b
|
version: 6710af535839f57c687b62c4c23d649f9545d885
|
||||||
- name: github.com/ogier/pflag
|
- name: github.com/ogier/pflag
|
||||||
version: 45c278ab3607870051a2ea9040bb85fcb8557481
|
version: 45c278ab3607870051a2ea9040bb85fcb8557481
|
||||||
- name: github.com/opencontainers/runc
|
- name: github.com/opencontainers/runc
|
||||||
|
@ -386,6 +388,8 @@ imports:
|
||||||
- ovh
|
- ovh
|
||||||
- name: github.com/pborman/uuid
|
- name: github.com/pborman/uuid
|
||||||
version: 5007efa264d92316c43112bc573e754bc889b7b1
|
version: 5007efa264d92316c43112bc573e754bc889b7b1
|
||||||
|
- name: github.com/pkg/errors
|
||||||
|
version: 248dadf4e9068a0b3e79f02ed0a610d935de5302
|
||||||
- name: github.com/pmezard/go-difflib
|
- name: github.com/pmezard/go-difflib
|
||||||
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
||||||
subpackages:
|
subpackages:
|
||||||
|
@ -414,6 +418,12 @@ imports:
|
||||||
version: ab4b0d7ff424c462da486aef27f354cdeb29a319
|
version: ab4b0d7ff424c462da486aef27f354cdeb29a319
|
||||||
subpackages:
|
subpackages:
|
||||||
- src/egoscale
|
- src/egoscale
|
||||||
|
- name: github.com/rancher/go-rancher
|
||||||
|
version: 2c43ff300f3eafcbd7d0b89b10427fc630efdc1e
|
||||||
|
subpackages:
|
||||||
|
- client
|
||||||
|
- name: github.com/rcrowley/go-metrics
|
||||||
|
version: 1f30fe9094a513ce4c700b9a54458bbb0c96996c
|
||||||
- name: github.com/ryanuber/go-glob
|
- name: github.com/ryanuber/go-glob
|
||||||
version: 572520ed46dbddaed19ea3d9541bdd0494163693
|
version: 572520ed46dbddaed19ea3d9541bdd0494163693
|
||||||
- name: github.com/samuel/go-zookeeper
|
- name: github.com/samuel/go-zookeeper
|
||||||
|
@ -739,8 +749,6 @@ testImports:
|
||||||
version: 06479209bdc0d4135911688c18157bd39bd99c22
|
version: 06479209bdc0d4135911688c18157bd39bd99c22
|
||||||
subpackages:
|
subpackages:
|
||||||
- specs-go
|
- specs-go
|
||||||
- name: github.com/pkg/errors
|
|
||||||
version: 01fa4104b9c248c8945d14d9f128454d5b28d595
|
|
||||||
- name: github.com/vbatts/tar-split
|
- name: github.com/vbatts/tar-split
|
||||||
version: 6810cedb21b2c3d0b9bb8f9af12ff2dc7a2f14df
|
version: 6810cedb21b2c3d0b9bb8f9af12ff2dc7a2f14df
|
||||||
subpackages:
|
subpackages:
|
||||||
|
|
|
@ -136,3 +136,5 @@ import:
|
||||||
version: v0.3
|
version: v0.3
|
||||||
subpackages:
|
subpackages:
|
||||||
- proto
|
- proto
|
||||||
|
- package: github.com/rancher/go-rancher
|
||||||
|
version: 2c43ff300f3eafcbd7d0b89b10427fc630efdc1e
|
||||||
|
|
458
provider/rancher.go
Normal file
458
provider/rancher.go
Normal file
|
@ -0,0 +1,458 @@
|
||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/BurntSushi/ty/fun"
|
||||||
|
"github.com/cenk/backoff"
|
||||||
|
"github.com/containous/traefik/job"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
|
rancher "github.com/rancher/go-rancher/client"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RancherDefaultWatchTime is the duration of the interval when polling rancher
|
||||||
|
RancherDefaultWatchTime = 15 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Provider = (*Rancher)(nil)
|
||||||
|
|
||||||
|
// Rancher holds configurations of the Rancher provider.
|
||||||
|
type Rancher struct {
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type rancherData struct {
|
||||||
|
Name string
|
||||||
|
Labels map[string]string // List of labels set to container or service
|
||||||
|
Containers []string
|
||||||
|
Health string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r rancherData) String() string {
|
||||||
|
return fmt.Sprintf("{name:%s, labels:%v, containers: %v, health: %s}", r.Name, r.Labels, r.Containers, r.Health)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frontend Labels
|
||||||
|
func (provider *Rancher) getPassHostHeader(service rancherData) string {
|
||||||
|
if passHostHeader, err := getServiceLabel(service, "traefik.frontend.passHostHeader"); err == nil {
|
||||||
|
return passHostHeader
|
||||||
|
}
|
||||||
|
return "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Rancher) getPriority(service rancherData) string {
|
||||||
|
if priority, err := getServiceLabel(service, "traefik.frontend.priority"); err == nil {
|
||||||
|
return priority
|
||||||
|
}
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Rancher) getEntryPoints(service rancherData) []string {
|
||||||
|
if entryPoints, err := getServiceLabel(service, "traefik.frontend.entryPoints"); err == nil {
|
||||||
|
return strings.Split(entryPoints, ",")
|
||||||
|
}
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Rancher) getFrontendRule(service rancherData) string {
|
||||||
|
if label, err := getServiceLabel(service, "traefik.frontend.rule"); err == nil {
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
return "Host:" + strings.ToLower(strings.Replace(service.Name, "/", "_", -1)) + "." + provider.Domain
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Rancher) getFrontendName(service rancherData) string {
|
||||||
|
// Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78
|
||||||
|
return normalize(provider.getFrontendRule(service))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backend Labels
|
||||||
|
func (provider *Rancher) getLoadBalancerMethod(service rancherData) string {
|
||||||
|
if label, err := getServiceLabel(service, "traefik.backend.loadbalancer.method"); err == nil {
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
return "wrr"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Rancher) hasLoadBalancerLabel(service rancherData) bool {
|
||||||
|
_, errMethod := getServiceLabel(service, "traefik.backend.loadbalancer.method")
|
||||||
|
_, errSticky := getServiceLabel(service, "traefik.backend.loadbalancer.sticky")
|
||||||
|
if errMethod != nil && errSticky != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Rancher) hasCircuitBreakerLabel(service rancherData) bool {
|
||||||
|
if _, err := getServiceLabel(service, "traefik.backend.circuitbreaker.expression"); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Rancher) getCircuitBreakerExpression(service rancherData) string {
|
||||||
|
if label, err := getServiceLabel(service, "traefik.backend.circuitbreaker.expression"); err == nil {
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
return "NetworkErrorRatio() > 1"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Rancher) getSticky(service rancherData) string {
|
||||||
|
if _, err := getServiceLabel(service, "traefik.backend.loadbalancer.sticky"); err == nil {
|
||||||
|
return "true"
|
||||||
|
}
|
||||||
|
return "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Rancher) getBackend(service rancherData) string {
|
||||||
|
if label, err := getServiceLabel(service, "traefik.backend"); err == nil {
|
||||||
|
return normalize(label)
|
||||||
|
}
|
||||||
|
return normalize(service.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generall Application Stuff
|
||||||
|
func (provider *Rancher) getPort(service rancherData) string {
|
||||||
|
if label, err := getServiceLabel(service, "traefik.port"); err == nil {
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Rancher) getProtocol(service rancherData) string {
|
||||||
|
if label, err := getServiceLabel(service, "traefik.protocol"); err == nil {
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
return "http"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Rancher) getWeight(service rancherData) string {
|
||||||
|
if label, err := getServiceLabel(service, "traefik.weight"); err == nil {
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Rancher) getDomain(service rancherData) string {
|
||||||
|
if label, err := getServiceLabel(service, "traefik.domain"); err == nil {
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
return provider.Domain
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Rancher) hasMaxConnLabels(service rancherData) bool {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Rancher) getMaxConnAmount(service rancherData) int64 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Rancher) getMaxConnExtractorFunc(service rancherData) string {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Rancher) createClient() (*rancher.RancherClient, error) {
|
||||||
|
return rancher.NewRancherClient(&rancher.ClientOpts{
|
||||||
|
Url: provider.Endpoint,
|
||||||
|
AccessKey: provider.AccessKey,
|
||||||
|
SecretKey: provider.SecretKey,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide allows the provider to provide configurations to traefik
|
||||||
|
// using the given configuration channel.
|
||||||
|
func (provider *Rancher) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||||
|
|
||||||
|
safe.Go(func() {
|
||||||
|
operation := func() error {
|
||||||
|
rancherClient, err := provider.createClient()
|
||||||
|
ctx := context.Background()
|
||||||
|
var environments = listRancherEnvironments(rancherClient)
|
||||||
|
var services = listRancherServices(rancherClient)
|
||||||
|
var container = listRancherContainer(rancherClient)
|
||||||
|
|
||||||
|
var rancherData = parseRancherData(environments, services, container)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to create a client for rancher, error: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration := provider.loadRancherConfig(rancherData)
|
||||||
|
configurationChan <- types.ConfigMessage{
|
||||||
|
ProviderName: "rancher",
|
||||||
|
Configuration: configuration,
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider.Watch {
|
||||||
|
_, cancel := context.WithCancel(ctx)
|
||||||
|
ticker := time.NewTicker(RancherDefaultWatchTime)
|
||||||
|
pool.Go(func(stop chan bool) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
|
||||||
|
log.Debugf("Refreshing new Data from Rancher API")
|
||||||
|
var environments = listRancherEnvironments(rancherClient)
|
||||||
|
var services = listRancherServices(rancherClient)
|
||||||
|
var container = listRancherContainer(rancherClient)
|
||||||
|
|
||||||
|
rancherData := parseRancherData(environments, services, container)
|
||||||
|
|
||||||
|
configuration := provider.loadRancherConfig(rancherData)
|
||||||
|
if configuration != nil {
|
||||||
|
configurationChan <- types.ConfigMessage{
|
||||||
|
ProviderName: "rancher",
|
||||||
|
Configuration: configuration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case <-stop:
|
||||||
|
ticker.Stop()
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
notify := func(err error, time time.Duration) {
|
||||||
|
log.Errorf("Rancher 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 Rancher Endpoint %+v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func listRancherEnvironments(client *rancher.RancherClient) []*rancher.Environment {
|
||||||
|
|
||||||
|
var environmentList = []*rancher.Environment{}
|
||||||
|
|
||||||
|
environments, err := client.Environment.List(nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Cannot get Rancher Environments %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range environments.Data {
|
||||||
|
environmentList = append(environmentList, &environments.Data[k])
|
||||||
|
}
|
||||||
|
|
||||||
|
return environmentList
|
||||||
|
}
|
||||||
|
|
||||||
|
func listRancherServices(client *rancher.RancherClient) []*rancher.Service {
|
||||||
|
|
||||||
|
var servicesList = []*rancher.Service{}
|
||||||
|
|
||||||
|
services, err := client.Service.List(nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Cannot get Rancher Services %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range services.Data {
|
||||||
|
servicesList = append(servicesList, &services.Data[k])
|
||||||
|
}
|
||||||
|
|
||||||
|
return servicesList
|
||||||
|
}
|
||||||
|
|
||||||
|
func listRancherContainer(client *rancher.RancherClient) []*rancher.Container {
|
||||||
|
|
||||||
|
containerList := []*rancher.Container{}
|
||||||
|
|
||||||
|
container, err := client.Container.List(nil)
|
||||||
|
|
||||||
|
log.Debugf("first container len: %i", len(container.Data))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Cannot get Rancher 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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{
|
||||||
|
Name: environment.Name + "/" + service.Name,
|
||||||
|
Health: service.HealthState,
|
||||||
|
Labels: make(map[string]string),
|
||||||
|
Containers: []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Rancher) loadRancherConfig(services []rancherData) *types.Configuration {
|
||||||
|
|
||||||
|
var RancherFuncMap = template.FuncMap{
|
||||||
|
"getPort": provider.getPort,
|
||||||
|
"getBackend": provider.getBackend,
|
||||||
|
"getWeight": provider.getWeight,
|
||||||
|
"getDomain": provider.getDomain,
|
||||||
|
"getProtocol": provider.getProtocol,
|
||||||
|
"getPassHostHeader": provider.getPassHostHeader,
|
||||||
|
"getPriority": provider.getPriority,
|
||||||
|
"getEntryPoints": provider.getEntryPoints,
|
||||||
|
"getFrontendRule": provider.getFrontendRule,
|
||||||
|
"hasCircuitBreakerLabel": provider.hasCircuitBreakerLabel,
|
||||||
|
"getCircuitBreakerExpression": provider.getCircuitBreakerExpression,
|
||||||
|
"hasLoadBalancerLabel": provider.hasLoadBalancerLabel,
|
||||||
|
"getLoadBalancerMethod": provider.getLoadBalancerMethod,
|
||||||
|
"hasMaxConnLabels": provider.hasMaxConnLabels,
|
||||||
|
"getMaxConnAmount": provider.getMaxConnAmount,
|
||||||
|
"getMaxConnExtractorFunc": provider.getMaxConnExtractorFunc,
|
||||||
|
"getSticky": provider.getSticky,
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter services
|
||||||
|
filteredServices := fun.Filter(func(service rancherData) bool {
|
||||||
|
return provider.serviceFilter(service)
|
||||||
|
}, services).([]rancherData)
|
||||||
|
|
||||||
|
frontends := map[string]rancherData{}
|
||||||
|
backends := map[string]rancherData{}
|
||||||
|
|
||||||
|
for _, service := range filteredServices {
|
||||||
|
frontendName := provider.getFrontendName(service)
|
||||||
|
frontends[frontendName] = service
|
||||||
|
backendName := provider.getBackend(service)
|
||||||
|
backends[backendName] = service
|
||||||
|
}
|
||||||
|
|
||||||
|
templateObjects := struct {
|
||||||
|
Frontends map[string]rancherData
|
||||||
|
Backends map[string]rancherData
|
||||||
|
Domain string
|
||||||
|
}{
|
||||||
|
frontends,
|
||||||
|
backends,
|
||||||
|
provider.Domain,
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration, err := provider.getConfiguration("templates/rancher.tmpl", RancherFuncMap, templateObjects)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
return configuration
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Rancher) serviceFilter(service rancherData) bool {
|
||||||
|
|
||||||
|
if service.Labels["traefik.port"] == "" {
|
||||||
|
log.Debugf("Filtering service %s without traefik.port label", service.Name)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isServiceEnabled(service, provider.ExposedByDefault) {
|
||||||
|
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 {
|
||||||
|
|
||||||
|
if service.Labels["traefik.enable"] != "" {
|
||||||
|
var v = service.Labels["traefik.enable"]
|
||||||
|
return exposedByDefault && v != "false" || v == "true"
|
||||||
|
}
|
||||||
|
return exposedByDefault
|
||||||
|
}
|
448
provider/rancher_test.go
Normal file
448
provider/rancher_test.go
Normal file
|
@ -0,0 +1,448 @@
|
||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRancherGetFrontendName(t *testing.T) {
|
||||||
|
provider := &Rancher{
|
||||||
|
Domain: "rancher.localhost",
|
||||||
|
}
|
||||||
|
|
||||||
|
services := []struct {
|
||||||
|
service rancherData
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
expected: "Host-foo-rancher-localhost",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"traefik.frontend.rule": "Headers:User-Agent,bat/0.1.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: "Headers-User-Agent-bat-0-1-0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"traefik.frontend.rule": "Host:foo.bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: "Host-foo-bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"traefik.frontend.rule": "Path:/test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: "Path-test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"traefik.frontend.rule": "PathPrefix:/test2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: "PathPrefix-test2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range services {
|
||||||
|
actual := provider.getFrontendName(e.service)
|
||||||
|
if actual != e.expected {
|
||||||
|
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRancherGetFrontendRule(t *testing.T) {
|
||||||
|
provider := &Rancher{
|
||||||
|
Domain: "rancher.localhost",
|
||||||
|
}
|
||||||
|
|
||||||
|
services := []struct {
|
||||||
|
service rancherData
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
expected: "Host:foo.rancher.localhost",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"traefik.frontend.rule": "Host:foo.bar.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: "Host:foo.bar.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"traefik.frontend.rule": "Path:/test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: "Path:/test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"traefik.frontend.rule": "PathPrefix:/test2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: "PathPrefix:/test2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range services {
|
||||||
|
actual := provider.getFrontendRule(e.service)
|
||||||
|
if actual != e.expected {
|
||||||
|
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRancherGetBackend(t *testing.T) {
|
||||||
|
provider := &Rancher{
|
||||||
|
Domain: "rancher.localhost",
|
||||||
|
}
|
||||||
|
|
||||||
|
services := []struct {
|
||||||
|
service rancherData
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
},
|
||||||
|
expected: "test-service",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"traefik.backend": "foobar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: "foobar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range services {
|
||||||
|
actual := provider.getBackend(e.service)
|
||||||
|
if actual != e.expected {
|
||||||
|
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRancherGetWeight(t *testing.T) {
|
||||||
|
provider := &Rancher{
|
||||||
|
Domain: "rancher.localhost",
|
||||||
|
}
|
||||||
|
|
||||||
|
services := []struct {
|
||||||
|
service rancherData
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
},
|
||||||
|
expected: "0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"traefik.weight": "5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: "5",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range services {
|
||||||
|
actual := provider.getWeight(e.service)
|
||||||
|
if actual != e.expected {
|
||||||
|
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRancherGetPort(t *testing.T) {
|
||||||
|
provider := &Rancher{
|
||||||
|
Domain: "rancher.localhost",
|
||||||
|
}
|
||||||
|
|
||||||
|
services := []struct {
|
||||||
|
service rancherData
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
},
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"traefik.port": "1337",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: "1337",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range services {
|
||||||
|
actual := provider.getPort(e.service)
|
||||||
|
if actual != e.expected {
|
||||||
|
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRancherGetDomain(t *testing.T) {
|
||||||
|
provider := &Rancher{
|
||||||
|
Domain: "rancher.localhost",
|
||||||
|
}
|
||||||
|
|
||||||
|
services := []struct {
|
||||||
|
service rancherData
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
},
|
||||||
|
expected: "rancher.localhost",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"traefik.domain": "foo.bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: "foo.bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range services {
|
||||||
|
actual := provider.getDomain(e.service)
|
||||||
|
if actual != e.expected {
|
||||||
|
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRancherGetProtocol(t *testing.T) {
|
||||||
|
provider := &Rancher{
|
||||||
|
Domain: "rancher.localhost",
|
||||||
|
}
|
||||||
|
|
||||||
|
services := []struct {
|
||||||
|
service rancherData
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
},
|
||||||
|
expected: "http",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"traefik.protocol": "https",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: "https",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range services {
|
||||||
|
actual := provider.getProtocol(e.service)
|
||||||
|
if actual != e.expected {
|
||||||
|
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRancherGetPassHostHeader(t *testing.T) {
|
||||||
|
provider := &Rancher{
|
||||||
|
Domain: "rancher.localhost",
|
||||||
|
}
|
||||||
|
|
||||||
|
services := []struct {
|
||||||
|
service rancherData
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
},
|
||||||
|
expected: "true",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"traefik.frontend.passHostHeader": "false",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: "false",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range services {
|
||||||
|
actual := provider.getPassHostHeader(e.service)
|
||||||
|
if actual != e.expected {
|
||||||
|
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRancherGetLabel(t *testing.T) {
|
||||||
|
services := []struct {
|
||||||
|
service rancherData
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
},
|
||||||
|
expected: "Label not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: rancherData{
|
||||||
|
Name: "test-service",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range services {
|
||||||
|
label, err := getServiceLabel(e.service, "foo")
|
||||||
|
if e.expected != "" {
|
||||||
|
if err == nil || !strings.Contains(err.Error(), e.expected) {
|
||||||
|
t.Fatalf("expected an error with %q, got %v", e.expected, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if label != "bar" {
|
||||||
|
t.Fatalf("expected label 'bar', got %s", label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRancherLoadRancherConfig(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
services []rancherData
|
||||||
|
expectedFrontends map[string]*types.Frontend
|
||||||
|
expectedBackends map[string]*types.Backend
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
services: []rancherData{},
|
||||||
|
expectedFrontends: map[string]*types.Frontend{},
|
||||||
|
expectedBackends: map[string]*types.Backend{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
services: []rancherData{
|
||||||
|
{
|
||||||
|
Name: "test-service",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"traefik.port": "80",
|
||||||
|
},
|
||||||
|
Health: "healthy",
|
||||||
|
Containers: []string{"127.0.0.1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedFrontends: map[string]*types.Frontend{
|
||||||
|
"frontend-Host-test-service-rancher-localhost": {
|
||||||
|
Backend: "backend-test-service",
|
||||||
|
PassHostHeader: true,
|
||||||
|
EntryPoints: []string{},
|
||||||
|
Priority: 0,
|
||||||
|
|
||||||
|
Routes: map[string]types.Route{
|
||||||
|
"route-frontend-Host-test-service-rancher-localhost": {
|
||||||
|
Rule: "Host:test-service.rancher.localhost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedBackends: map[string]*types.Backend{
|
||||||
|
"backend-test-service": {
|
||||||
|
Servers: map[string]types.Server{
|
||||||
|
"server-0": {
|
||||||
|
URL: "http://127.0.0.1:80",
|
||||||
|
Weight: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CircuitBreaker: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := &Rancher{
|
||||||
|
Domain: "rancher.localhost",
|
||||||
|
ExposedByDefault: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
var rancherDataList []rancherData
|
||||||
|
for _, service := range c.services {
|
||||||
|
rancherDataList = append(rancherDataList, service)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualConfig := provider.loadRancherConfig(rancherDataList)
|
||||||
|
|
||||||
|
// Compare backends
|
||||||
|
if !reflect.DeepEqual(actualConfig.Backends, c.expectedBackends) {
|
||||||
|
t.Fatalf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) {
|
||||||
|
t.Fatalf("expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -377,6 +377,9 @@ func (server *Server) configureProviders() {
|
||||||
if server.globalConfiguration.ECS != nil {
|
if server.globalConfiguration.ECS != nil {
|
||||||
server.providers = append(server.providers, server.globalConfiguration.ECS)
|
server.providers = append(server.providers, server.globalConfiguration.ECS)
|
||||||
}
|
}
|
||||||
|
if server.globalConfiguration.Rancher != nil {
|
||||||
|
server.providers = append(server.providers, server.globalConfiguration.Rancher)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) startProviders() {
|
func (server *Server) startProviders() {
|
||||||
|
|
38
templates/rancher.tmpl
Normal file
38
templates/rancher.tmpl
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
{{$backendServers := .Backends}}
|
||||||
|
[backends]{{range $backendName, $backend := .Backends}}
|
||||||
|
{{if hasCircuitBreakerLabel $backend}}
|
||||||
|
[backends.backend-{{$backendName}}.circuitbreaker]
|
||||||
|
expression = "{{getCircuitBreakerExpression $backend}}"
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if hasLoadBalancerLabel $backend}}
|
||||||
|
[backends.backend-{{$backendName}}.loadbalancer]
|
||||||
|
method = "{{getLoadBalancerMethod $backend}}"
|
||||||
|
sticky = {{getSticky $backend}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if hasMaxConnLabels $backend}}
|
||||||
|
[backends.backend-{{$backendName}}.maxconn]
|
||||||
|
amount = {{getMaxConnAmount $backend}}
|
||||||
|
extractorfunc = "{{getMaxConnExtractorFunc $backend}}"
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{range $index, $ip := $backend.Containers}}
|
||||||
|
[backends.backend-{{$backendName}}.servers.server-{{$index}}]
|
||||||
|
url = "{{getProtocol $backend}}://{{$ip}}:{{getPort $backend}}"
|
||||||
|
weight = {{getWeight $backend}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
[frontends]{{range $frontendName, $service := .Frontends}}
|
||||||
|
[frontends."frontend-{{$frontendName}}"]
|
||||||
|
backend = "backend-{{getBackend $service}}"
|
||||||
|
passHostHeader = {{getPassHostHeader $service}}
|
||||||
|
priority = {{getPriority $service}}
|
||||||
|
entryPoints = [{{range getEntryPoints $service}}
|
||||||
|
"{{.}}",
|
||||||
|
{{end}}]
|
||||||
|
[frontends."frontend-{{$frontendName}}".routes."route-frontend-{{$frontendName}}"]
|
||||||
|
rule = "{{getFrontendRule $service}}"
|
||||||
|
{{end}}
|
Loading…
Add table
Reference in a new issue