Merge pull request #88 from vdemeester/refactor-package

Refactor traefik with package
This commit is contained in:
Emile Vauge 2015-11-02 21:08:29 +01:00
commit 7766d0ddaa
22 changed files with 236 additions and 208 deletions

0
autogen/.placeholder Normal file
View file

View file

@ -1,9 +1,10 @@
package main package main
import ( import (
"errors"
"strings"
"time" "time"
"github.com/emilevauge/traefik/provider"
"github.com/emilevauge/traefik/types"
) )
type GlobalConfiguration struct { type GlobalConfiguration struct {
@ -14,14 +15,14 @@ type GlobalConfiguration struct {
CertFile, KeyFile string CertFile, KeyFile string
LogLevel string LogLevel string
ProvidersThrottleDuration time.Duration ProvidersThrottleDuration time.Duration
Docker *DockerProvider Docker *provider.Docker
File *FileProvider File *provider.File
Web *WebProvider Web *WebProvider
Marathon *MarathonProvider Marathon *provider.Marathon
Consul *ConsulProvider Consul *provider.Consul
Etcd *EtcdProvider Etcd *provider.Etcd
Zookeeper *ZookepperProvider Zookeeper *provider.Zookepper
Boltdb *BoltDbProvider Boltdb *provider.BoltDb
} }
func NewGlobalConfiguration() *GlobalConfiguration { func NewGlobalConfiguration() *GlobalConfiguration {
@ -35,79 +36,4 @@ func NewGlobalConfiguration() *GlobalConfiguration {
return globalConfiguration return globalConfiguration
} }
// Backend configuration type configs map[string]*types.Configuration
type Backend struct {
Servers map[string]Server `json:"servers,omitempty"`
CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty"`
LoadBalancer *LoadBalancer `json:"loadBalancer,omitempty"`
}
// LoadBalancer configuration
type LoadBalancer struct {
Method string `json:"method,omitempty"`
}
// CircuitBreaker configuration
type CircuitBreaker struct {
Expression string `json:"expression,omitempty"`
}
// Server configuration
type Server struct {
URL string `json:"url,omitempty"`
Weight int `json:"weight,omitempty"`
}
// Route configuration
type Route struct {
Rule string `json:"rule,omitempty"`
Value string `json:"value,omitempty"`
}
// Frontend configuration
type Frontend struct {
PassHostHeader bool `json:"passHostHeader,omitempty"`
Backend string `json:"backend,omitempty"`
Routes map[string]Route `json:"routes,omitempty"`
}
// Configuration of a provider
type Configuration struct {
Backends map[string]*Backend `json:"backends,omitempty"`
Frontends map[string]*Frontend `json:"frontends,omitempty"`
}
// Load Balancer Method
type LoadBalancerMethod uint8
const (
// wrr (default) = Weighted Round Robin
wrr LoadBalancerMethod = iota
// drr = Dynamic Round Robin
drr
)
var loadBalancerMethodNames = []string{
"wrr",
"drr",
}
func NewLoadBalancerMethod(loadBalancer *LoadBalancer) (LoadBalancerMethod, error) {
if loadBalancer != nil {
for i, name := range loadBalancerMethodNames {
if strings.EqualFold(name, loadBalancer.Method) {
return LoadBalancerMethod(i), nil
}
}
}
return wrr, ErrInvalidLoadBalancerMethod
}
var ErrInvalidLoadBalancerMethod = errors.New("Invalid method, using default")
type configMessage struct {
providerName string
configuration *Configuration
}
type configs map[string]*Configuration

View file

@ -1 +0,0 @@
package main

View file

@ -2,8 +2,8 @@
Copyright Copyright
*/ */
//go:generate rm -vf gen.go //go:generate rm -vf autogen/gen.go
//go:generate go-bindata -o gen.go static/... templates/... //go:generate go-bindata -pkg autogen -o autogen/gen.go ./static/... ./templates/...
//go:generate mkdir -p vendor/github.com/docker/docker/autogen/dockerversion //go:generate mkdir -p vendor/github.com/docker/docker/autogen/dockerversion
//go:generate cp script/dockerversion vendor/github.com/docker/docker/autogen/dockerversion/dockerversion.go //go:generate cp script/dockerversion vendor/github.com/docker/docker/autogen/dockerversion/dockerversion.go

View file

@ -1,5 +0,0 @@
package main
type Provider interface {
Provide(configurationChan chan<- configMessage) error
}

View file

@ -1,14 +1,16 @@
package main package provider
type BoltDbProvider struct { import "github.com/emilevauge/traefik/types"
type BoltDb struct {
Watch bool Watch bool
Endpoint string Endpoint string
Prefix string Prefix string
Filename string Filename string
KvProvider *KvProvider KvProvider *Kv
} }
func (provider *BoltDbProvider) Provide(configurationChan chan<- configMessage) error { func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage) error {
provider.KvProvider = NewBoltDbProvider(provider) provider.KvProvider = NewBoltDbProvider(provider)
return provider.KvProvider.provide(configurationChan) return provider.KvProvider.provide(configurationChan)
} }

View file

@ -1,14 +1,16 @@
package main package provider
type ConsulProvider struct { import "github.com/emilevauge/traefik/types"
type Consul struct {
Watch bool Watch bool
Endpoint string Endpoint string
Prefix string Prefix string
Filename string Filename string
KvProvider *KvProvider KvProvider *Kv
} }
func (provider *ConsulProvider) Provide(configurationChan chan<- configMessage) error { func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage) error {
provider.KvProvider = NewConsulProvider(provider) provider.KvProvider = NewConsulProvider(provider)
return provider.KvProvider.provide(configurationChan) return provider.KvProvider.provide(configurationChan)
} }

View file

@ -1,29 +1,31 @@
package main package provider
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"strconv" "strconv"
"strings" "strings"
"text/template" "text/template"
"time" "time"
"fmt"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/BurntSushi/ty/fun" "github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/cenkalti/backoff" "github.com/cenkalti/backoff"
"github.com/emilevauge/traefik/autogen"
"github.com/emilevauge/traefik/types"
"github.com/fsouza/go-dockerclient" "github.com/fsouza/go-dockerclient"
) )
type DockerProvider struct { type Docker struct {
Watch bool Watch bool
Endpoint string Endpoint string
Filename string Filename string
Domain string Domain string
} }
func (provider *DockerProvider) Provide(configurationChan chan<- configMessage) error { func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage) error {
if dockerClient, err := docker.NewClient(provider.Endpoint); err != nil { if dockerClient, err := docker.NewClient(provider.Endpoint); err != nil {
log.Errorf("Failed to create a client for docker, error: %s", err) log.Errorf("Failed to create a client for docker, error: %s", err)
return err return err
@ -50,7 +52,7 @@ func (provider *DockerProvider) Provide(configurationChan chan<- configMessage)
log.Debugf("Docker event receveived %+v", event) log.Debugf("Docker event receveived %+v", event)
configuration := provider.loadDockerConfig(dockerClient) configuration := provider.loadDockerConfig(dockerClient)
if configuration != nil { if configuration != nil {
configurationChan <- configMessage{"docker", configuration} configurationChan <- types.ConfigMessage{"docker", configuration}
} }
} }
} }
@ -66,12 +68,12 @@ func (provider *DockerProvider) Provide(configurationChan chan<- configMessage)
} }
configuration := provider.loadDockerConfig(dockerClient) configuration := provider.loadDockerConfig(dockerClient)
configurationChan <- configMessage{"docker", configuration} configurationChan <- types.ConfigMessage{"docker", configuration}
} }
return nil return nil
} }
func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *Configuration { func (provider *Docker) loadDockerConfig(dockerClient *docker.Client) *types.Configuration {
var DockerFuncMap = template.FuncMap{ var DockerFuncMap = template.FuncMap{
"getBackend": func(container docker.Container) string { "getBackend": func(container docker.Container) string {
if label, err := provider.getLabel(container, "traefik.backend"); err == nil { if label, err := provider.getLabel(container, "traefik.backend"); err == nil {
@ -118,7 +120,7 @@ func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *C
return strings.Replace(s3, s1, s2, -1) return strings.Replace(s3, s1, s2, -1)
}, },
} }
configuration := new(Configuration) configuration := new(types.Configuration)
containerList, _ := dockerClient.ListContainers(docker.ListContainersOptions{}) containerList, _ := dockerClient.ListContainers(docker.ListContainersOptions{})
containersInspected := []docker.Container{} containersInspected := []docker.Container{}
frontends := map[string][]docker.Container{} frontends := map[string][]docker.Container{}
@ -174,7 +176,7 @@ func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *C
return nil return nil
} }
} else { } else {
buf, err := Asset("templates/docker.tmpl") buf, err := autogen.Asset("templates/docker.tmpl")
if err != nil { if err != nil {
log.Error("Error reading file", err) log.Error("Error reading file", err)
} }
@ -199,17 +201,17 @@ func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *C
return configuration return configuration
} }
func (provider *DockerProvider) getFrontendName(container docker.Container) string { func (provider *Docker) getFrontendName(container docker.Container) string {
// Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78 // Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78
frontendName := fmt.Sprintf("%s-%s", provider.GetFrontendRule(container), provider.GetFrontendValue(container)) frontendName := fmt.Sprintf("%s-%s", provider.GetFrontendRule(container), provider.GetFrontendValue(container))
return strings.Replace(frontendName, ".", "-", -1) return strings.Replace(frontendName, ".", "-", -1)
} }
func (provider *DockerProvider) getEscapedName(name string) string { func (provider *Docker) getEscapedName(name string) string {
return strings.Replace(name, "/", "", -1) return strings.Replace(name, "/", "", -1)
} }
func (provider *DockerProvider) getLabel(container docker.Container, label string) (string, error) { func (provider *Docker) getLabel(container docker.Container, label string) (string, error) {
for key, value := range container.Config.Labels { for key, value := range container.Config.Labels {
if key == label { if key == label {
return value, nil return value, nil
@ -218,7 +220,7 @@ func (provider *DockerProvider) getLabel(container docker.Container, label strin
return "", errors.New("Label not found:" + label) return "", errors.New("Label not found:" + label)
} }
func (provider *DockerProvider) getLabels(container docker.Container, labels []string) (map[string]string, error) { func (provider *Docker) getLabels(container docker.Container, labels []string) (map[string]string, error) {
foundLabels := map[string]string{} foundLabels := map[string]string{}
for _, label := range labels { for _, label := range labels {
if foundLabel, err := provider.getLabel(container, label); err != nil { if foundLabel, err := provider.getLabel(container, label); err != nil {
@ -230,14 +232,14 @@ func (provider *DockerProvider) getLabels(container docker.Container, labels []s
return foundLabels, nil return foundLabels, nil
} }
func (provider *DockerProvider) GetFrontendValue(container docker.Container) string { func (provider *Docker) GetFrontendValue(container docker.Container) string {
if label, err := provider.getLabel(container, "traefik.frontend.value"); err == nil { if label, err := provider.getLabel(container, "traefik.frontend.value"); err == nil {
return label return label
} }
return provider.getEscapedName(container.Name) + "." + provider.Domain return provider.getEscapedName(container.Name) + "." + provider.Domain
} }
func (provider *DockerProvider) GetFrontendRule(container docker.Container) string { func (provider *Docker) GetFrontendRule(container docker.Container) string {
if label, err := provider.getLabel(container, "traefik.frontend.rule"); err == nil { if label, err := provider.getLabel(container, "traefik.frontend.rule"); err == nil {
return label return label
} }

View file

@ -1,14 +1,16 @@
package main package provider
type EtcdProvider struct { import "github.com/emilevauge/traefik/types"
type Etcd struct {
Watch bool Watch bool
Endpoint string Endpoint string
Prefix string Prefix string
Filename string Filename string
KvProvider *KvProvider KvProvider *Kv
} }
func (provider *EtcdProvider) Provide(configurationChan chan<- configMessage) error { func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage) error {
provider.KvProvider = NewEtcdProvider(provider) provider.KvProvider = NewEtcdProvider(provider)
return provider.KvProvider.provide(configurationChan) return provider.KvProvider.provide(configurationChan)
} }

View file

@ -1,4 +1,4 @@
package main package provider
import ( import (
"os" "os"
@ -7,15 +7,16 @@ import (
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/emilevauge/traefik/types"
"gopkg.in/fsnotify.v1" "gopkg.in/fsnotify.v1"
) )
type FileProvider struct { type File struct {
Watch bool Watch bool
Filename string Filename string
} }
func (provider *FileProvider) Provide(configurationChan chan<- configMessage) error { func (provider *File) Provide(configurationChan chan<- types.ConfigMessage) error {
watcher, err := fsnotify.NewWatcher() watcher, err := fsnotify.NewWatcher()
if err != nil { if err != nil {
log.Error("Error creating file watcher", err) log.Error("Error creating file watcher", err)
@ -40,7 +41,7 @@ func (provider *FileProvider) Provide(configurationChan chan<- configMessage) er
log.Debug("File event:", event) log.Debug("File event:", event)
configuration := provider.LoadFileConfig(file.Name()) configuration := provider.LoadFileConfig(file.Name())
if configuration != nil { if configuration != nil {
configurationChan <- configMessage{"file", configuration} configurationChan <- types.ConfigMessage{"file", configuration}
} }
} }
case error := <-watcher.Errors: case error := <-watcher.Errors:
@ -56,12 +57,12 @@ func (provider *FileProvider) Provide(configurationChan chan<- configMessage) er
} }
configuration := provider.LoadFileConfig(file.Name()) configuration := provider.LoadFileConfig(file.Name())
configurationChan <- configMessage{"file", configuration} configurationChan <- types.ConfigMessage{"file", configuration}
return nil return nil
} }
func (provider *FileProvider) LoadFileConfig(filename string) *Configuration { func (provider *File) LoadFileConfig(filename string) *types.Configuration {
configuration := new(Configuration) configuration := new(types.Configuration)
if _, err := toml.DecodeFile(filename, configuration); err != nil { if _, err := toml.DecodeFile(filename, configuration); err != nil {
log.Error("Error reading file:", err) log.Error("Error reading file:", err)
return nil return nil

1
provider/file_test.go Normal file
View file

@ -0,0 +1 @@
package provider

View file

@ -1,27 +1,29 @@
/* /*
Copyright Copyright
*/ */
package main package provider
import ( import (
"bytes" "bytes"
"errors"
"strings"
"text/template"
"time"
"github.com/BurntSushi/toml"
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus"
"github.com/docker/libkv" "github.com/docker/libkv"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/boltdb" "github.com/docker/libkv/store/boltdb"
"github.com/docker/libkv/store/consul" "github.com/docker/libkv/store/consul"
"github.com/docker/libkv/store/etcd" "github.com/docker/libkv/store/etcd"
"github.com/docker/libkv/store/zookeeper" "github.com/docker/libkv/store/zookeeper"
"strings" "github.com/emilevauge/traefik/autogen"
"text/template" "github.com/emilevauge/traefik/types"
"errors"
"github.com/BurntSushi/toml"
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus"
"github.com/docker/libkv/store"
"time"
) )
type KvProvider struct { type Kv struct {
Watch bool Watch bool
Endpoint string Endpoint string
Prefix string Prefix string
@ -30,8 +32,8 @@ type KvProvider struct {
kvclient store.Store kvclient store.Store
} }
func NewConsulProvider(provider *ConsulProvider) *KvProvider { func NewConsulProvider(provider *Consul) *Kv {
kvProvider := new(KvProvider) kvProvider := new(Kv)
kvProvider.Watch = provider.Watch kvProvider.Watch = provider.Watch
kvProvider.Endpoint = provider.Endpoint kvProvider.Endpoint = provider.Endpoint
kvProvider.Prefix = provider.Prefix kvProvider.Prefix = provider.Prefix
@ -40,8 +42,8 @@ func NewConsulProvider(provider *ConsulProvider) *KvProvider {
return kvProvider return kvProvider
} }
func NewEtcdProvider(provider *EtcdProvider) *KvProvider { func NewEtcdProvider(provider *Etcd) *Kv {
kvProvider := new(KvProvider) kvProvider := new(Kv)
kvProvider.Watch = provider.Watch kvProvider.Watch = provider.Watch
kvProvider.Endpoint = provider.Endpoint kvProvider.Endpoint = provider.Endpoint
kvProvider.Prefix = provider.Prefix kvProvider.Prefix = provider.Prefix
@ -50,8 +52,8 @@ func NewEtcdProvider(provider *EtcdProvider) *KvProvider {
return kvProvider return kvProvider
} }
func NewZkProvider(provider *ZookepperProvider) *KvProvider { func NewZkProvider(provider *Zookepper) *Kv {
kvProvider := new(KvProvider) kvProvider := new(Kv)
kvProvider.Watch = provider.Watch kvProvider.Watch = provider.Watch
kvProvider.Endpoint = provider.Endpoint kvProvider.Endpoint = provider.Endpoint
kvProvider.Prefix = provider.Prefix kvProvider.Prefix = provider.Prefix
@ -60,8 +62,8 @@ func NewZkProvider(provider *ZookepperProvider) *KvProvider {
return kvProvider return kvProvider
} }
func NewBoltDbProvider(provider *BoltDbProvider) *KvProvider { func NewBoltDbProvider(provider *BoltDb) *Kv {
kvProvider := new(KvProvider) kvProvider := new(Kv)
kvProvider.Watch = provider.Watch kvProvider.Watch = provider.Watch
kvProvider.Endpoint = provider.Endpoint kvProvider.Endpoint = provider.Endpoint
kvProvider.Prefix = provider.Prefix kvProvider.Prefix = provider.Prefix
@ -70,7 +72,7 @@ func NewBoltDbProvider(provider *BoltDbProvider) *KvProvider {
return kvProvider return kvProvider
} }
func (provider *KvProvider) provide(configurationChan chan<- configMessage) error { func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage) error {
switch provider.StoreType { switch provider.StoreType {
case store.CONSUL: case store.CONSUL:
consul.Register() consul.Register()
@ -109,19 +111,19 @@ func (provider *KvProvider) provide(configurationChan chan<- configMessage) erro
<-chanKeys <-chanKeys
configuration := provider.loadConfig() configuration := provider.loadConfig()
if configuration != nil { if configuration != nil {
configurationChan <- configMessage{string(provider.StoreType), configuration} configurationChan <- types.ConfigMessage{string(provider.StoreType), configuration}
} }
defer close(stopCh) defer close(stopCh)
} }
}() }()
} }
configuration := provider.loadConfig() configuration := provider.loadConfig()
configurationChan <- configMessage{string(provider.StoreType), configuration} configurationChan <- types.ConfigMessage{string(provider.StoreType), configuration}
return nil return nil
} }
func (provider *KvProvider) loadConfig() *Configuration { func (provider *Kv) loadConfig() *types.Configuration {
configuration := new(Configuration) configuration := new(types.Configuration)
templateObjects := struct { templateObjects := struct {
Prefix string Prefix string
}{ }{
@ -167,7 +169,7 @@ func (provider *KvProvider) loadConfig() *Configuration {
return nil return nil
} }
} else { } else {
buf, err := Asset("templates/kv.tmpl") buf, err := autogen.Asset("templates/kv.tmpl")
if err != nil { if err != nil {
log.Error("Error reading file", err) log.Error("Error reading file", err)
} }

View file

@ -1,19 +1,21 @@
package main package provider
import ( import (
"bytes" "bytes"
"errors"
"strconv" "strconv"
"strings" "strings"
"text/template" "text/template"
"errors"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/BurntSushi/ty/fun" "github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/emilevauge/traefik/autogen"
"github.com/emilevauge/traefik/types"
"github.com/gambol99/go-marathon" "github.com/gambol99/go-marathon"
) )
type MarathonProvider struct { type Marathon struct {
Watch bool Watch bool
Endpoint string Endpoint string
marathonClient marathon.Marathon marathonClient marathon.Marathon
@ -22,7 +24,7 @@ type MarathonProvider struct {
NetworkInterface string NetworkInterface string
} }
func (provider *MarathonProvider) Provide(configurationChan chan<- configMessage) error { func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage) error {
config := marathon.NewDefaultConfig() config := marathon.NewDefaultConfig()
config.URL = provider.Endpoint config.URL = provider.Endpoint
config.EventsInterface = provider.NetworkInterface config.EventsInterface = provider.NetworkInterface
@ -43,7 +45,7 @@ func (provider *MarathonProvider) Provide(configurationChan chan<- configMessage
log.Debug("Marathon event receveived", event) log.Debug("Marathon event receveived", event)
configuration := provider.loadMarathonConfig() configuration := provider.loadMarathonConfig()
if configuration != nil { if configuration != nil {
configurationChan <- configMessage{"marathon", configuration} configurationChan <- types.ConfigMessage{"marathon", configuration}
} }
} }
}() }()
@ -51,11 +53,11 @@ func (provider *MarathonProvider) Provide(configurationChan chan<- configMessage
} }
configuration := provider.loadMarathonConfig() configuration := provider.loadMarathonConfig()
configurationChan <- configMessage{"marathon", configuration} configurationChan <- types.ConfigMessage{"marathon", configuration}
return nil return nil
} }
func (provider *MarathonProvider) loadMarathonConfig() *Configuration { func (provider *Marathon) loadMarathonConfig() *types.Configuration {
var MarathonFuncMap = template.FuncMap{ var MarathonFuncMap = template.FuncMap{
"getPort": func(task marathon.Task) string { "getPort": func(task marathon.Task) string {
for _, port := range task.Ports { for _, port := range task.Ports {
@ -103,7 +105,7 @@ func (provider *MarathonProvider) loadMarathonConfig() *Configuration {
"getFrontendValue": provider.GetFrontendValue, "getFrontendValue": provider.GetFrontendValue,
"getFrontendRule": provider.GetFrontendRule, "getFrontendRule": provider.GetFrontendRule,
} }
configuration := new(Configuration) configuration := new(types.Configuration)
applications, err := provider.marathonClient.Applications(nil) applications, err := provider.marathonClient.Applications(nil)
if err != nil { if err != nil {
@ -187,7 +189,7 @@ func (provider *MarathonProvider) loadMarathonConfig() *Configuration {
return nil return nil
} }
} else { } else {
buf, err := Asset("templates/marathon.tmpl") buf, err := autogen.Asset("templates/marathon.tmpl")
if err != nil { if err != nil {
log.Error("Error reading file", err) log.Error("Error reading file", err)
} }
@ -223,7 +225,7 @@ func getApplication(task marathon.Task, apps []marathon.Application) (marathon.A
return marathon.Application{}, errors.New("Application not found: " + task.AppID) return marathon.Application{}, errors.New("Application not found: " + task.AppID)
} }
func (provider *MarathonProvider) getLabel(application marathon.Application, label string) (string, error) { func (provider *Marathon) getLabel(application marathon.Application, label string) (string, error) {
for key, value := range application.Labels { for key, value := range application.Labels {
if key == label { if key == label {
return value, nil return value, nil
@ -232,18 +234,18 @@ func (provider *MarathonProvider) getLabel(application marathon.Application, lab
return "", errors.New("Label not found:" + label) return "", errors.New("Label not found:" + label)
} }
func (provider *MarathonProvider) getEscapedName(name string) string { func (provider *Marathon) getEscapedName(name string) string {
return strings.Replace(name, "/", "", -1) return strings.Replace(name, "/", "", -1)
} }
func (provider *MarathonProvider) GetFrontendValue(application marathon.Application) string { func (provider *Marathon) GetFrontendValue(application marathon.Application) string {
if label, err := provider.getLabel(application, "traefik.frontend.value"); err == nil { if label, err := provider.getLabel(application, "traefik.frontend.value"); err == nil {
return label return label
} }
return provider.getEscapedName(application.ID) + "." + provider.Domain return provider.getEscapedName(application.ID) + "." + provider.Domain
} }
func (provider *MarathonProvider) GetFrontendRule(application marathon.Application) string { func (provider *Marathon) GetFrontendRule(application marathon.Application) string {
if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil { if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil {
return label return label
} }

7
provider/provider.go Normal file
View file

@ -0,0 +1,7 @@
package provider
import "github.com/emilevauge/traefik/types"
type Provider interface {
Provide(configurationChan chan<- types.ConfigMessage) error
}

16
provider/zk.go Normal file
View file

@ -0,0 +1,16 @@
package provider
import "github.com/emilevauge/traefik/types"
type Zookepper struct {
Watch bool
Endpoint string
Prefix string
Filename string
KvProvider *Kv
}
func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage) error {
provider.KvProvider = NewZkProvider(provider)
return provider.KvProvider.provide(configurationChan)
}

View file

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
set -e set -e
if ! test -e gen.go; then if ! test -e autogen/gen.go; then
echo >&2 'error: generate must be run before binary' echo >&2 'error: generate must be run before binary'
false false
fi fi

View file

@ -1,8 +1,8 @@
#!/bin/bash #!/bin/bash
set -e set -e
if ! test -e gen.go; then if ! test -e autogen/gen.go; then
echo >&2 'error: generate must be run before binary' echo >&2 'error: generate must be run before crossbinary'
false false
fi fi

View file

@ -1,8 +1,8 @@
#!/bin/bash #!/bin/bash
set -e set -e
if ! test -e gen.go; then if ! test -e autogen/gen.go; then
echo >&2 'error: generate must be run before binary' echo >&2 'error: generate must be run before test-unit'
false false
fi fi

View file

@ -2,21 +2,24 @@ package main
import ( import (
"crypto/tls" "crypto/tls"
"errors"
fmtlog "log" fmtlog "log"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"os/signal" "os/signal"
"reflect" "reflect"
"runtime"
"strings" "strings"
"syscall" "syscall"
"time" "time"
"errors"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/codegangsta/negroni" "github.com/codegangsta/negroni"
"github.com/emilevauge/traefik/middlewares" "github.com/emilevauge/traefik/middlewares"
"github.com/emilevauge/traefik/provider"
"github.com/emilevauge/traefik/types"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mailgun/manners" "github.com/mailgun/manners"
"github.com/mailgun/oxy/cbreaker" "github.com/mailgun/oxy/cbreaker"
@ -24,7 +27,6 @@ import (
"github.com/mailgun/oxy/roundrobin" "github.com/mailgun/oxy/roundrobin"
"github.com/thoas/stats" "github.com/thoas/stats"
"gopkg.in/alecthomas/kingpin.v2" "gopkg.in/alecthomas/kingpin.v2"
"runtime"
) )
var ( var (
@ -42,15 +44,15 @@ func main() {
fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags) fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags)
var srv *manners.GracefulServer var srv *manners.GracefulServer
var configurationRouter *mux.Router var configurationRouter *mux.Router
var configurationChan = make(chan configMessage, 10) var configurationChan = make(chan types.ConfigMessage, 10)
defer close(configurationChan) defer close(configurationChan)
var configurationChanValidated = make(chan configMessage, 10) var configurationChanValidated = make(chan types.ConfigMessage, 10)
defer close(configurationChanValidated) defer close(configurationChanValidated)
var sigs = make(chan os.Signal, 1) var sigs = make(chan os.Signal, 1)
defer close(sigs) defer close(sigs)
var stopChan = make(chan bool) var stopChan = make(chan bool)
defer close(stopChan) defer close(stopChan)
var providers = []Provider{} var providers = []provider.Provider{}
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
// load global configuration // load global configuration
@ -84,22 +86,22 @@ func main() {
// listen new configurations from providers // listen new configurations from providers
go func() { go func() {
lastReceivedConfiguration := time.Unix(0, 0) lastReceivedConfiguration := time.Unix(0, 0)
lastConfigs := make(map[string]*configMessage) lastConfigs := make(map[string]*types.ConfigMessage)
for { for {
configMsg := <-configurationChan configMsg := <-configurationChan
log.Infof("Configuration receveived from provider %s: %#v", configMsg.providerName, configMsg.configuration) log.Infof("Configuration receveived from provider %s: %#v", configMsg.ProviderName, configMsg.Configuration)
lastConfigs[configMsg.providerName] = &configMsg lastConfigs[configMsg.ProviderName] = &configMsg
if time.Now().After(lastReceivedConfiguration.Add(time.Duration(globalConfiguration.ProvidersThrottleDuration))) { if time.Now().After(lastReceivedConfiguration.Add(time.Duration(globalConfiguration.ProvidersThrottleDuration))) {
log.Infof("Last %s config received more than %s, OK", configMsg.providerName, globalConfiguration.ProvidersThrottleDuration) log.Infof("Last %s config received more than %s, OK", configMsg.ProviderName, globalConfiguration.ProvidersThrottleDuration)
// last config received more than n s ago // last config received more than n s ago
configurationChanValidated <- configMsg configurationChanValidated <- configMsg
} else { } else {
log.Infof("Last %s config received less than %s, waiting...", configMsg.providerName, globalConfiguration.ProvidersThrottleDuration) log.Infof("Last %s config received less than %s, waiting...", configMsg.ProviderName, globalConfiguration.ProvidersThrottleDuration)
go func() { go func() {
<-time.After(globalConfiguration.ProvidersThrottleDuration) <-time.After(globalConfiguration.ProvidersThrottleDuration)
if time.Now().After(lastReceivedConfiguration.Add(time.Duration(globalConfiguration.ProvidersThrottleDuration))) { if time.Now().After(lastReceivedConfiguration.Add(time.Duration(globalConfiguration.ProvidersThrottleDuration))) {
log.Infof("Waited for %s config, OK", configMsg.providerName) log.Infof("Waited for %s config, OK", configMsg.ProviderName)
configurationChanValidated <- *lastConfigs[configMsg.providerName] configurationChanValidated <- *lastConfigs[configMsg.ProviderName]
} }
}() }()
} }
@ -109,9 +111,9 @@ func main() {
go func() { go func() {
for { for {
configMsg := <-configurationChanValidated configMsg := <-configurationChanValidated
if configMsg.configuration == nil { if configMsg.Configuration == nil {
log.Info("Skipping empty configuration") log.Info("Skipping empty Configuration")
} else if reflect.DeepEqual(currentConfigurations[configMsg.providerName], configMsg.configuration) { } else if reflect.DeepEqual(currentConfigurations[configMsg.ProviderName], configMsg.Configuration) {
log.Info("Skipping same configuration") log.Info("Skipping same configuration")
} else { } else {
// Copy configurations to new map so we don't change current if LoadConfig fails // Copy configurations to new map so we don't change current if LoadConfig fails
@ -119,7 +121,7 @@ func main() {
for k, v := range currentConfigurations { for k, v := range currentConfigurations {
newConfigurations[k] = v newConfigurations[k] = v
} }
newConfigurations[configMsg.providerName] = configMsg.configuration newConfigurations[configMsg.ProviderName] = configMsg.Configuration
newConfigurationRouter, err := LoadConfig(newConfigurations, globalConfiguration) newConfigurationRouter, err := LoadConfig(newConfigurations, globalConfiguration)
if err == nil { if err == nil {
@ -299,12 +301,12 @@ func LoadConfig(configurations configs, globalConfiguration *GlobalConfiguration
if configuration.Backends[frontend.Backend] == nil { if configuration.Backends[frontend.Backend] == nil {
return nil, errors.New("Backend not found: " + frontend.Backend) return nil, errors.New("Backend not found: " + frontend.Backend)
} }
lbMethod, err := NewLoadBalancerMethod(configuration.Backends[frontend.Backend].LoadBalancer) lbMethod, err := types.NewLoadBalancerMethod(configuration.Backends[frontend.Backend].LoadBalancer)
if err != nil { if err != nil {
configuration.Backends[frontend.Backend].LoadBalancer = &LoadBalancer{Method: "wrr"} configuration.Backends[frontend.Backend].LoadBalancer = &types.LoadBalancer{Method: "wrr"}
} }
switch lbMethod { switch lbMethod {
case drr: case types.Drr:
log.Infof("Creating load-balancer drr") log.Infof("Creating load-balancer drr")
rebalancer, _ := roundrobin.NewRebalancer(rr, roundrobin.RebalancerLogger(oxyLogger)) rebalancer, _ := roundrobin.NewRebalancer(rr, roundrobin.RebalancerLogger(oxyLogger))
lb = rebalancer lb = rebalancer
@ -316,7 +318,7 @@ func LoadConfig(configurations configs, globalConfiguration *GlobalConfiguration
log.Infof("Creating server %s %s", serverName, url.String()) log.Infof("Creating server %s %s", serverName, url.String())
rebalancer.UpsertServer(url, roundrobin.Weight(server.Weight)) rebalancer.UpsertServer(url, roundrobin.Weight(server.Weight))
} }
case wrr: case types.Wrr:
log.Infof("Creating load-balancer wrr") log.Infof("Creating load-balancer wrr")
lb = middlewares.NewWebsocketUpgrader(rr) lb = middlewares.NewWebsocketUpgrader(rr)
for serverName, server := range configuration.Backends[frontend.Backend].Servers { for serverName, server := range configuration.Backends[frontend.Backend].Servers {

81
types/types.go Normal file
View file

@ -0,0 +1,81 @@
package types
import (
"errors"
"strings"
)
// Backend configuration
type Backend struct {
Servers map[string]Server `json:"servers,omitempty"`
CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty"`
LoadBalancer *LoadBalancer `json:"loadBalancer,omitempty"`
}
// LoadBalancer configuration
type LoadBalancer struct {
Method string `json:"method,omitempty"`
}
// CircuitBreaker configuration
type CircuitBreaker struct {
Expression string `json:"expression,omitempty"`
}
// Server configuration
type Server struct {
URL string `json:"url,omitempty"`
Weight int `json:"weight,omitempty"`
}
// Route configuration
type Route struct {
Rule string `json:"rule,omitempty"`
Value string `json:"value,omitempty"`
}
// Frontend configuration
type Frontend struct {
Backend string `json:"backend,omitempty"`
Routes map[string]Route `json:"routes,omitempty"`
PassHostHeader bool `json:"passHostHeader,omitempty"`
}
// Load Balancer Method
type LoadBalancerMethod uint8
const (
// Wrr (default) = Weighted Round Robin
Wrr LoadBalancerMethod = iota
// Drr = Dynamic Round Robin
Drr
)
var loadBalancerMethodNames = []string{
"Wrr",
"Drr",
}
func NewLoadBalancerMethod(loadBalancer *LoadBalancer) (LoadBalancerMethod, error) {
if loadBalancer != nil {
for i, name := range loadBalancerMethodNames {
if strings.EqualFold(name, loadBalancer.Method) {
return LoadBalancerMethod(i), nil
}
}
}
return Wrr, ErrInvalidLoadBalancerMethod
}
var ErrInvalidLoadBalancerMethod = errors.New("Invalid method, using default")
// Configuration of a provider
type Configuration struct {
Backends map[string]*Backend `json:"backends,omitempty"`
Frontends map[string]*Frontend `json:"frontends,omitempty"`
}
type ConfigMessage struct {
ProviderName string
Configuration *Configuration
}

10
web.go
View file

@ -8,6 +8,8 @@ import (
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/elazarl/go-bindata-assetfs" "github.com/elazarl/go-bindata-assetfs"
"github.com/emilevauge/traefik/autogen"
"github.com/emilevauge/traefik/types"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/unrolled/render" "github.com/unrolled/render"
) )
@ -23,7 +25,7 @@ var (
}) })
) )
func (provider *WebProvider) Provide(configurationChan chan<- configMessage) error { func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage) error {
systemRouter := mux.NewRouter() systemRouter := mux.NewRouter()
// health route // health route
@ -41,11 +43,11 @@ func (provider *WebProvider) Provide(configurationChan chan<- configMessage) err
return return
} }
configuration := new(Configuration) configuration := new(types.Configuration)
body, _ := ioutil.ReadAll(request.Body) body, _ := ioutil.ReadAll(request.Body)
err := json.Unmarshal(body, configuration) err := json.Unmarshal(body, configuration)
if err == nil { if err == nil {
configurationChan <- configMessage{"web", configuration} configurationChan <- types.ConfigMessage{"web", configuration}
getConfigHandler(response, request) getConfigHandler(response, request)
} else { } else {
log.Errorf("Error parsing configuration %+v", err) log.Errorf("Error parsing configuration %+v", err)
@ -65,7 +67,7 @@ func (provider *WebProvider) Provide(configurationChan chan<- configMessage) err
systemRouter.Methods("GET").Path("/").HandlerFunc(func(response http.ResponseWriter, request *http.Request) { systemRouter.Methods("GET").Path("/").HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
http.Redirect(response, request, "/dashboard/", 302) http.Redirect(response, request, "/dashboard/", 302)
}) })
systemRouter.Methods("GET").PathPrefix("/dashboard/").Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "static"}))) systemRouter.Methods("GET").PathPrefix("/dashboard/").Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: autogen.Asset, AssetDir: autogen.AssetDir, Prefix: "static"})))
go func() { go func() {
if len(provider.CertFile) > 0 && len(provider.KeyFile) > 0 { if len(provider.CertFile) > 0 && len(provider.KeyFile) > 0 {

14
zk.go
View file

@ -1,14 +0,0 @@
package main
type ZookepperProvider struct {
Watch bool
Endpoint string
Prefix string
Filename string
KvProvider *KvProvider
}
func (provider *ZookepperProvider) Provide(configurationChan chan<- configMessage) error {
provider.KvProvider = NewZkProvider(provider)
return provider.KvProvider.provide(configurationChan)
}