add dependency, start provider and fetch data

add tons of labels

Provide - WIP

add rancher data over rancher types

first version of direct fetch - pagination still an issue
This commit is contained in:
Manuel Laufenberg 2017-01-29 00:01:56 +01:00
parent 28054a0be3
commit 38bd49b97e
6 changed files with 533 additions and 6 deletions

View file

@ -51,6 +51,7 @@ type GlobalConfiguration struct {
Mesos *provider.Mesos `description:"Enable Mesos backend"`
Eureka *provider.Eureka `description:"Enable Eureka backend"`
ECS *provider.ECS `description:"Enable ECS backend"`
Rancher *provider.Rancher `description:"Enable Rancher backend"`
}
// DefaultEntryPoints holds default entry points
@ -415,6 +416,10 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
ECS: &defaultECS,
Retry: &Retry{},
}
//default Rancher
//@TODO: ADD
return &TraefikConfiguration{
GlobalConfiguration: defaultConfiguration,
}

18
glide.lock generated
View file

@ -1,5 +1,5 @@
hash: a0b0abed2162e490cbe75a6a36ebaaf39e748ee80e419e879e7253679a0bc134
updated: 2017-02-05T18:09:09.856588042Z
hash: b2ac93355c3f551a75216a800337cee9321f6c9a04a18ab1fa8d8152e89b7595
updated: 2017-02-05T23:00:24.927243212+01:00
imports:
- name: bitbucket.org/ww/goautoneg
version: 75cd24fc2f2c2a2088577d12123ddee5f54e0675
@ -300,6 +300,8 @@ imports:
version: 44d81051d367757e1c7c6a5a86423ece9afcf63c
- name: github.com/gorilla/context
version: 1ea25387ff6f684839d82767c1733ff4d4d15d0a
- name: github.com/gorilla/websocket
version: c36f2fe5c330f0ac404b616b96c438b8616b1aaf
- name: github.com/hashicorp/consul
version: fce7d75609a04eeb9d4bf41c8dc592aac18fc97d
subpackages:
@ -370,7 +372,7 @@ imports:
- name: github.com/mvdan/xurls
version: fa08908f19eca8c491d68c6bd8b4b44faea6daf8
- name: github.com/NYTimes/gziphandler
version: f6438dbf4a82c56684964b03956aa727b0d7816b
version: 6710af535839f57c687b62c4c23d649f9545d885
- name: github.com/ogier/pflag
version: 45c278ab3607870051a2ea9040bb85fcb8557481
- name: github.com/opencontainers/runc
@ -386,6 +388,8 @@ imports:
- ovh
- name: github.com/pborman/uuid
version: 5007efa264d92316c43112bc573e754bc889b7b1
- name: github.com/pkg/errors
version: 248dadf4e9068a0b3e79f02ed0a610d935de5302
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
@ -414,6 +418,12 @@ imports:
version: ab4b0d7ff424c462da486aef27f354cdeb29a319
subpackages:
- 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
version: 572520ed46dbddaed19ea3d9541bdd0494163693
- name: github.com/samuel/go-zookeeper
@ -739,8 +749,6 @@ testImports:
version: 06479209bdc0d4135911688c18157bd39bd99c22
subpackages:
- specs-go
- name: github.com/pkg/errors
version: 01fa4104b9c248c8945d14d9f128454d5b28d595
- name: github.com/vbatts/tar-split
version: 6810cedb21b2c3d0b9bb8f9af12ff2dc7a2f14df
subpackages:

View file

@ -135,4 +135,6 @@ import:
- package: github.com/gogo/protobuf
version: v0.3
subpackages:
- proto
- proto
- package: github.com/rancher/go-rancher
version: 2c43ff300f3eafcbd7d0b89b10427fc630efdc1e

471
provider/rancher.go Normal file
View file

@ -0,0 +1,471 @@
package provider
import (
rancher "github.com/rancher/go-rancher/client"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/containous/traefik/log"
"github.com/cenk/backoff"
"github.com/containous/traefik/job"
"time"
"github.com/BurntSushi/ty/fun"
//"context"
"errors"
"strings"
"strconv"
"math"
"fmt"
"text/template"
)
var _ Provider = (*Rancher)(nil)
// Rancher holds configurations of the Docker 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 ""
}
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"
}
// Container Stuff
func (provider *Rancher) getIPAddress(container *rancher.Container) string {
ipAdress := container.PrimaryIpAddress;
if ipAdress != ""{
return ipAdress
}
return ""
}
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,
})
}
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)
fmt.Printf("Rancher Data #2 %s", &rancherData)
if err != nil {
log.Errorf("Failed to create a client for docker, error: %s", err)
return err
}
configuration := provider.loadRancherConfig(rancherData)
configurationChan <- types.ConfigMessage{
ProviderName: "rancher",
Configuration: configuration,
}
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, environment := range environments.Data {
log.Debugf("Adding environment with id %s", environment.Id)
environmentList = append(environmentList, &environments.Data[k])
}
return environmentList
}
/*
"io.rancher.stack.name"
*/
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, service := range services.Data {
log.Debugf("Adding service with id %s", service.Id)
servicesList = append(servicesList, &services.Data[k])
}
return servicesList
}
func listRancherContainer(client *rancher.RancherClient)([]*rancher.Container){
var containerList = []*rancher.Container{}
container, err := client.Container.List(nil)
if err != nil {
log.Errorf("Cannot get Rancher Services %+v", err)
}
for k, singleContainer := range container.Data {
log.Debugf("Adding container with id %s", singleContainer.Id)
containerList = append(containerList, &container.Data[k])
}
return containerList
}
func parseRancherData(environments []*rancher.Environment, services []*rancher.Service, containers []*rancher.Container) []rancherData {
log.Debugf("Starting to parse Rancher Data")
var rancherDataList []rancherData
for _, environment := range environments {
log.Debugf("Iterating trough environment %s", environment.Name)
for _, service := range services {
log.Debugf("Iterating trough service %s with id %s for environment %s", service.Name, service.AccountId, environment.Id)
if service.EnvironmentId != environment.Id {
log.Debugf("NO MATCH")
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{
"getIPAddress": provider.getIPAddress,
"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
}
fmt.Printf("Frontends %v", frontends)
fmt.Printf("Backends %v", backends)
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
}
/*
constraintTags := strings.Split(container.Labels["traefik.tags"], ",")
if ok, failingConstraint := provider.MatchConstraints(constraintTags); !ok {
if failingConstraint != nil {
log.Debugf("Container %v pruned by '%v' constraint", container.Name, failingConstraint.String())
}
return false
}
*/
if service.Health != "" && service.Health != "healthy" {
log.Debugf("Filtering unhealthy or starting service %s", service.Name)
return false
}
log.Debugf("Service %s is enabled!", service.Name)
return true
}
func (provider *Rancher) containerFilter(container *rancher.Container, instanceIds []string) bool {
//log.Debugf("Filtering Containers for InstanceIds %v ", instanceIds)
for _, instanceId := range instanceIds {
//log.Debugf("Looking for instanceId %s on on container %s", instanceId, container.Id)
if container.Id == instanceId {
//log.Debugf("Found container with id %s", instanceId)
return true
}
}
return false
}
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 false
}

View file

@ -377,6 +377,9 @@ func (server *Server) configureProviders() {
if server.globalConfiguration.ECS != nil {
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() {

38
templates/rancher.tmpl Normal file
View 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}}