mirror of
https://github.com/containous/traefik.git
synced 2025-09-26 01:44:23 +03:00
Compare commits
35 Commits
v1.0.alpha
...
v1.0.alpha
Author | SHA1 | Date | |
---|---|---|---|
|
7be566ef7c | ||
|
3c9ec55f0a | ||
|
5ee6981410 | ||
|
c32f82baee | ||
|
89bb1ae835 | ||
|
9387235a04 | ||
|
7766d0ddaa | ||
|
cdade5f649 | ||
|
de0a57ec76 | ||
|
6e1a0554c0 | ||
|
ae73d08d67 | ||
|
ddceefa4e1 | ||
|
80cd6c3699 | ||
|
9cfd0a6b26 | ||
|
1e99ecf583 | ||
|
aae7941689 | ||
|
d888b4fcb5 | ||
|
b029e7eded | ||
|
6f3afe8213 | ||
|
b4c019afb6 | ||
|
143ea86ab9 | ||
|
287d5c59da | ||
|
ae6bda3220 | ||
|
0a6be92290 | ||
|
b71b5dd0d4 | ||
|
b12c4ac55a | ||
|
9f736f4235 | ||
|
33d912290b | ||
|
d390f86de2 | ||
|
aaeb7cdffd | ||
|
32bfecff83 | ||
|
d671cc3821 | ||
|
5dea2e7902 | ||
|
1fdff9dae4 | ||
|
46d7cc83c9 |
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
||||
dist/
|
||||
vendor/
|
||||
!dist/traefik
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,5 +5,5 @@ log
|
||||
*.iml
|
||||
traefik
|
||||
traefik.toml
|
||||
|
||||
*.test
|
||||
vendor/
|
||||
|
3
Makefile
3
Makefile
@@ -20,6 +20,9 @@ print-%: ; @echo $*=$($*)
|
||||
|
||||
default: binary
|
||||
|
||||
all: build
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh
|
||||
|
||||
binary: build
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate binary
|
||||
|
||||
|
@@ -1,9 +1,9 @@
|
||||

|
||||
___
|
||||
|
||||
[](https://circleci.com/gh/EmileVauge/traefik)
|
||||
[](https://circleci.com/gh/emilevauge/traefik)
|
||||
[](https://github.com/EmileVauge/traefik/blob/master/LICENSE.md)
|
||||
[](https://gitter.im/EmileVauge/traefik?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://traefik.herokuapp.com)
|
||||
|
||||
|
||||
Træfɪk is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||
@@ -85,6 +85,7 @@ You need either [Docker](https://github.com/docker/docker) and `make`, or `go` a
|
||||
#### Setting up your `go` environment
|
||||
- You need `go` v1.5
|
||||
- You need to set `export GO15VENDOREXPERIMENT=1` environment variable
|
||||
- You need `go-bindata` to be able to use `go generate` command (needed to build) : `go get github.com/jteeuwen/go-bindata/...`.
|
||||
- If you clone Træfɪk into something like `~/go/src/github.com/traefik`, your `GOPATH` variable will have to be set to `~/go`: export `GOPATH=~/go`.
|
||||
|
||||
#### Using `Docker` and `Makefile`
|
||||
|
0
autogen/.placeholder
Normal file
0
autogen/.placeholder
Normal file
14
boltdb.go
14
boltdb.go
@@ -1,14 +0,0 @@
|
||||
package main
|
||||
|
||||
type BoltDbProvider struct {
|
||||
Watch bool
|
||||
Endpoint string
|
||||
Prefix string
|
||||
Filename string
|
||||
KvProvider *KvProvider
|
||||
}
|
||||
|
||||
func (provider *BoltDbProvider) Provide(configurationChan chan<- configMessage) error {
|
||||
provider.KvProvider = NewBoltDbProvider(provider)
|
||||
return provider.KvProvider.provide(configurationChan)
|
||||
}
|
@@ -3,6 +3,7 @@ FROM golang:1.5
|
||||
RUN go get github.com/Masterminds/glide
|
||||
RUN go get github.com/mitchellh/gox
|
||||
RUN go get github.com/tcnksm/ghr
|
||||
RUN go get github.com/jteeuwen/go-bindata/...
|
||||
|
||||
# Which docker version to test on
|
||||
ENV DOCKER_VERSION 1.6.2
|
||||
|
@@ -1,9 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/emilevauge/traefik/provider"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
)
|
||||
|
||||
type GlobalConfiguration struct {
|
||||
@@ -14,14 +15,14 @@ type GlobalConfiguration struct {
|
||||
CertFile, KeyFile string
|
||||
LogLevel string
|
||||
ProvidersThrottleDuration time.Duration
|
||||
Docker *DockerProvider
|
||||
File *FileProvider
|
||||
Docker *provider.Docker
|
||||
File *provider.File
|
||||
Web *WebProvider
|
||||
Marathon *MarathonProvider
|
||||
Consul *ConsulProvider
|
||||
Etcd *EtcdProvider
|
||||
Zookeeper *ZookepperProvider
|
||||
Boltdb *BoltDbProvider
|
||||
Marathon *provider.Marathon
|
||||
Consul *provider.Consul
|
||||
Etcd *provider.Etcd
|
||||
Zookeeper *provider.Zookepper
|
||||
Boltdb *provider.BoltDb
|
||||
}
|
||||
|
||||
func NewGlobalConfiguration() *GlobalConfiguration {
|
||||
@@ -35,71 +36,4 @@ func NewGlobalConfiguration() *GlobalConfiguration {
|
||||
return globalConfiguration
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// 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 configs map[string]*types.Configuration
|
||||
|
14
consul.go
14
consul.go
@@ -1,14 +0,0 @@
|
||||
package main
|
||||
|
||||
type ConsulProvider struct {
|
||||
Watch bool
|
||||
Endpoint string
|
||||
Prefix string
|
||||
Filename string
|
||||
KvProvider *KvProvider
|
||||
}
|
||||
|
||||
func (provider *ConsulProvider) Provide(configurationChan chan<- configMessage) error {
|
||||
provider.KvProvider = NewConsulProvider(provider)
|
||||
return provider.KvProvider.provide(configurationChan)
|
||||
}
|
197
docker.go
197
docker.go
@@ -1,197 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
type DockerProvider struct {
|
||||
Watch bool
|
||||
Endpoint string
|
||||
Filename string
|
||||
Domain string
|
||||
}
|
||||
|
||||
func (provider *DockerProvider) Provide(configurationChan chan<- configMessage) error {
|
||||
if dockerClient, err := docker.NewClient(provider.Endpoint); err != nil {
|
||||
log.Errorf("Failed to create a client for docker, error: %s", err)
|
||||
return err
|
||||
} else {
|
||||
err := dockerClient.Ping()
|
||||
if err != nil {
|
||||
log.Errorf("Docker connection error %+v", err)
|
||||
return err
|
||||
}
|
||||
log.Debug("Docker connection established")
|
||||
if provider.Watch {
|
||||
dockerEvents := make(chan *docker.APIEvents)
|
||||
dockerClient.AddEventListener(dockerEvents)
|
||||
log.Debug("Docker listening")
|
||||
go func() {
|
||||
operation := func() error {
|
||||
for {
|
||||
event := <-dockerEvents
|
||||
if event == nil {
|
||||
return errors.New("Docker event nil")
|
||||
// log.Fatalf("Docker connection error")
|
||||
}
|
||||
if event.Status == "start" || event.Status == "die" {
|
||||
log.Debugf("Docker event receveived %+v", event)
|
||||
configuration := provider.loadDockerConfig(dockerClient)
|
||||
if configuration != nil {
|
||||
configurationChan <- configMessage{"docker", configuration}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Docker connection error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, backoff.NewExponentialBackOff(), notify)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot connect to docker server %+v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
configuration := provider.loadDockerConfig(dockerClient)
|
||||
configurationChan <- configMessage{"docker", configuration}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *Configuration {
|
||||
var DockerFuncMap = template.FuncMap{
|
||||
"getBackend": func(container docker.Container) string {
|
||||
for key, value := range container.Config.Labels {
|
||||
if key == "traefik.backend" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return getHost(container)
|
||||
},
|
||||
"getPort": func(container docker.Container) string {
|
||||
for key, value := range container.Config.Labels {
|
||||
if key == "traefik.port" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
for key := range container.NetworkSettings.Ports {
|
||||
return key.Port()
|
||||
}
|
||||
return ""
|
||||
},
|
||||
"getWeight": func(container docker.Container) string {
|
||||
for key, value := range container.Config.Labels {
|
||||
if key == "traefik.weight" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return "0"
|
||||
},
|
||||
"getDomain": func(container docker.Container) string {
|
||||
for key, value := range container.Config.Labels {
|
||||
if key == "traefik.domain" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return provider.Domain
|
||||
},
|
||||
"replace": func(s1 string, s2 string, s3 string) string {
|
||||
return strings.Replace(s3, s1, s2, -1)
|
||||
},
|
||||
"getHost": getHost,
|
||||
}
|
||||
configuration := new(Configuration)
|
||||
containerList, _ := dockerClient.ListContainers(docker.ListContainersOptions{})
|
||||
containersInspected := []docker.Container{}
|
||||
hosts := map[string][]docker.Container{}
|
||||
|
||||
// get inspect containers
|
||||
for _, container := range containerList {
|
||||
containerInspected, _ := dockerClient.InspectContainer(container.ID)
|
||||
containersInspected = append(containersInspected, *containerInspected)
|
||||
}
|
||||
|
||||
// filter containers
|
||||
filteredContainers := fun.Filter(func(container docker.Container) bool {
|
||||
if len(container.NetworkSettings.Ports) == 0 {
|
||||
log.Debugf("Filtering container without port %s", container.Name)
|
||||
return false
|
||||
}
|
||||
_, err := strconv.Atoi(container.Config.Labels["traefik.port"])
|
||||
if len(container.NetworkSettings.Ports) > 1 && err != nil {
|
||||
log.Debugf("Filtering container with more than 1 port and no traefik.port label %s", container.Name)
|
||||
return false
|
||||
}
|
||||
if container.Config.Labels["traefik.enable"] == "false" {
|
||||
log.Debugf("Filtering disabled container %s", container.Name)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, containersInspected).([]docker.Container)
|
||||
|
||||
for _, container := range filteredContainers {
|
||||
hosts[getHost(container)] = append(hosts[getHost(container)], container)
|
||||
}
|
||||
|
||||
templateObjects := struct {
|
||||
Containers []docker.Container
|
||||
Hosts map[string][]docker.Container
|
||||
Domain string
|
||||
}{
|
||||
filteredContainers,
|
||||
hosts,
|
||||
provider.Domain,
|
||||
}
|
||||
tmpl := template.New(provider.Filename).Funcs(DockerFuncMap)
|
||||
if len(provider.Filename) > 0 {
|
||||
_, err := tmpl.ParseFiles(provider.Filename)
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
buf, err := Asset("providerTemplates/docker.tmpl")
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
}
|
||||
_, err = tmpl.Parse(string(buf))
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
err := tmpl.Execute(&buffer, templateObjects)
|
||||
if err != nil {
|
||||
log.Error("Error with docker template", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := toml.Decode(buffer.String(), configuration); err != nil {
|
||||
log.Error("Error creating docker configuration", err)
|
||||
return nil
|
||||
}
|
||||
return configuration
|
||||
}
|
||||
|
||||
func getHost(container docker.Container) string {
|
||||
for key, value := range container.Config.Labels {
|
||||
if key == "traefik.host" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return strings.Replace(strings.Replace(container.Name, "/", "", -1), ".", "-", -1)
|
||||
}
|
@@ -25,7 +25,7 @@ It supports several backends ([Docker :whale:](https://www.docker.com/), [Mesos/
|
||||
|
||||
Basically, Træfɪk is a http router, which sends traffic from frontends to http backends, following rules you have configured.
|
||||
|
||||
### Frontends
|
||||
### <a id="frontends"></a> Frontends
|
||||
|
||||
Frontends can be defined using the following rules:
|
||||
|
||||
@@ -38,6 +38,7 @@ Frontends can be defined using the following rules:
|
||||
|
||||
|
||||
A frontend is a set of rules that forwards the incoming http traffic to a backend.
|
||||
You can optionally enable `passHostHeader` to forward client `Host` header to the backend.
|
||||
|
||||
### HTTP Backends
|
||||
|
||||
@@ -163,6 +164,7 @@ logLevel = "DEBUG"
|
||||
value = "test.localhost"
|
||||
[frontends.frontend2]
|
||||
backend = "backend1"
|
||||
passHostHeader = true
|
||||
[frontends.frontend2.routes.test_2]
|
||||
rule = "Path"
|
||||
value = "/test"
|
||||
@@ -210,6 +212,7 @@ filename = "rules.toml"
|
||||
value = "test.localhost"
|
||||
[frontends.frontend2]
|
||||
backend = "backend1"
|
||||
passHostHeader = true
|
||||
[frontends.frontend2.routes.test_2]
|
||||
rule = "Path"
|
||||
value = "/test"
|
||||
@@ -407,11 +410,15 @@ Labels can be used on containers to override default behaviour:
|
||||
|
||||
- `traefik.backend=foo`: assign the container to `foo` backend
|
||||
- `traefik.port=80`: register this port. Useful when the container exposes multiples ports.
|
||||
- `traefik.protocol=https`: override the default `http` protocol
|
||||
- `traefik.weight=10`: assign this weight to the container
|
||||
- `traefik.enable=false`: disable this container in Træfɪk
|
||||
- `traefik.host=bar`: override the default routing from `{containerName}.{domain}` to `bar.{domain}`
|
||||
- `traefik.frontend.rule=Host`: override the default frontend rule (Default: Host). See [frontends](#frontends).
|
||||
- `traefik.frontend.value=test.example.com`: override the default frontend value (Default: `{containerName}.{domain}`) See [frontends](#frontends).
|
||||
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
||||
* `traefik.domain=traefik.localhost`: override the default domain
|
||||
|
||||
|
||||
## <a id="marathon"></a> Marathon backend
|
||||
|
||||
Træfɪk can be configured to use Marathon as a backend configuration:
|
||||
@@ -466,10 +473,12 @@ Labels can be used on containers to override default behaviour:
|
||||
|
||||
- `traefik.backend=foo`: assign the application to `foo` backend
|
||||
- `traefik.port=80`: register this port. Useful when the application exposes multiples ports.
|
||||
- `traefik.protocol=https`: override the default `http` protocol
|
||||
- `traefik.weight=10`: assign this weight to the application
|
||||
- `traefik.enable=false`: disable this application in Træfɪk
|
||||
- `traefik.host=bar`: override the default routing from `{appName}.{domain}` to `bar.{domain}`
|
||||
- `traefik.prefixes=pf1,pf2`: use `PathPrefix(es)` instead of hostname for routing, use `filename="providerTemplates/marathon-prefix.tmpl"` with this option
|
||||
- `traefik.frontend.rule=Host`: override the default frontend rule (Default: Host). See [frontends](#frontends).
|
||||
- `traefik.frontend.value=test.example.com`: override the default frontend value (Default: `{appName}.{domain}`) See [frontends](#frontends).
|
||||
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
||||
* `traefik.domain=traefik.localhost`: override the default domain
|
||||
|
||||
## <a id="consul"></a> Consul backend
|
||||
@@ -547,6 +556,7 @@ The Keys-Values structure should look (using `prefix = "/traefik"`):
|
||||
| Key | Value |
|
||||
|----------------------------------------------------|------------|
|
||||
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
||||
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
||||
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path` |
|
||||
| `/traefik/frontends/frontend2/routes/test_2/value` | `/test` |
|
||||
|
||||
@@ -626,6 +636,7 @@ The Keys-Values structure should look (using `prefix = "/traefik"`):
|
||||
| Key | Value |
|
||||
|----------------------------------------------------|------------|
|
||||
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
||||
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
||||
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path` |
|
||||
| `/traefik/frontends/frontend2/routes/test_2/value` | `/test` |
|
||||
|
||||
@@ -704,6 +715,7 @@ The Keys-Values structure should look (using `prefix = "/traefik"`):
|
||||
| Key | Value |
|
||||
|----------------------------------------------------|------------|
|
||||
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
||||
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
||||
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path` |
|
||||
| `/traefik/frontends/frontend2/routes/test_2/value` | `/test` |
|
||||
|
||||
|
14
etcd.go
14
etcd.go
@@ -1,14 +0,0 @@
|
||||
package main
|
||||
|
||||
type EtcdProvider struct {
|
||||
Watch bool
|
||||
Endpoint string
|
||||
Prefix string
|
||||
Filename string
|
||||
KvProvider *KvProvider
|
||||
}
|
||||
|
||||
func (provider *EtcdProvider) Provide(configurationChan chan<- configMessage) error {
|
||||
provider.KvProvider = NewEtcdProvider(provider)
|
||||
return provider.KvProvider.provide(configurationChan)
|
||||
}
|
@@ -1 +0,0 @@
|
||||
package main
|
@@ -2,8 +2,10 @@
|
||||
Copyright
|
||||
*/
|
||||
|
||||
//go:generate go get github.com/jteeuwen/go-bindata/...
|
||||
//go:generate rm -vf gen.go
|
||||
//go:generate go-bindata -o gen.go static/... templates/... providerTemplates/...
|
||||
//go:generate rm -vf autogen/gen.go
|
||||
//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 cp script/dockerversion vendor/github.com/docker/docker/autogen/dockerversion/dockerversion.go
|
||||
|
||||
package main
|
||||
|
@@ -10,6 +10,9 @@ import (
|
||||
check "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// SimpleSuite
|
||||
type SimpleSuite struct{ BaseSuite }
|
||||
|
||||
func (s *SimpleSuite) TestNoOrInexistentConfigShouldFail(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary)
|
||||
output, err := cmd.CombinedOutput()
|
||||
@@ -37,6 +40,7 @@ func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "fixtures/simple_default.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
// TODO validate : run on 80
|
||||
@@ -45,7 +49,18 @@ func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) {
|
||||
// Expected a 404 as we did not comfigure anything
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
|
||||
killErr := cmd.Process.Kill()
|
||||
c.Assert(killErr, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestWithWebConfig(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "fixtures/simple_web.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
resp, err := http.Get("http://127.0.0.1:8080/api")
|
||||
// Expected a 200
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "fixtures/consul/simple.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
// TODO validate : run on 80
|
||||
@@ -21,7 +22,4 @@ func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
|
||||
// Expected a 404 as we did not comfigure anything
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
|
||||
killErr := cmd.Process.Kill()
|
||||
c.Assert(killErr, checker.IsNil)
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ func (s *FileSuite) TestSimpleConfiguration(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "fixtures/file/simple.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
resp, err := http.Get("http://127.0.0.1/")
|
||||
@@ -20,9 +21,6 @@ func (s *FileSuite) TestSimpleConfiguration(c *check.C) {
|
||||
// Expected a 404 as we did not configure anything
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
|
||||
killErr := cmd.Process.Kill()
|
||||
c.Assert(killErr, checker.IsNil)
|
||||
}
|
||||
|
||||
// #56 regression test, make sure it does not fail
|
||||
@@ -30,6 +28,7 @@ func (s *FileSuite) TestSimpleConfigurationNoPanic(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "fixtures/file/56-simple-panic.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
resp, err := http.Get("http://127.0.0.1/")
|
||||
@@ -37,7 +36,4 @@ func (s *FileSuite) TestSimpleConfigurationNoPanic(c *check.C) {
|
||||
// Expected a 404 as we did not configure anything
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
|
||||
killErr := cmd.Process.Kill()
|
||||
c.Assert(killErr, checker.IsNil)
|
||||
}
|
||||
|
5
integration/fixtures/simple_web.toml
Normal file
5
integration/fixtures/simple_web.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[web]
|
||||
|
||||
address = ":8080"
|
@@ -32,9 +32,6 @@ func init() {
|
||||
|
||||
var traefikBinary = "../dist/traefik"
|
||||
|
||||
// SimpleSuite
|
||||
type SimpleSuite struct{ BaseSuite }
|
||||
|
||||
// File test suites
|
||||
type FileSuite struct{ BaseSuite }
|
||||
|
||||
|
@@ -13,6 +13,7 @@ func (s *MarathonSuite) TestSimpleConfiguration(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "fixtures/marathon/simple.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
// TODO validate : run on 80
|
||||
@@ -21,7 +22,4 @@ func (s *MarathonSuite) TestSimpleConfiguration(c *check.C) {
|
||||
// Expected a 404 as we did not configure anything
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
|
||||
killErr := cmd.Process.Kill()
|
||||
c.Assert(killErr, checker.IsNil)
|
||||
}
|
||||
|
204
marathon.go
204
marathon.go
@@ -1,204 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gambol99/go-marathon"
|
||||
)
|
||||
|
||||
type MarathonProvider struct {
|
||||
Watch bool
|
||||
Endpoint string
|
||||
marathonClient marathon.Marathon
|
||||
Domain string
|
||||
Filename string
|
||||
NetworkInterface string
|
||||
}
|
||||
|
||||
func (provider *MarathonProvider) Provide(configurationChan chan<- configMessage) error {
|
||||
config := marathon.NewDefaultConfig()
|
||||
config.URL = provider.Endpoint
|
||||
config.EventsInterface = provider.NetworkInterface
|
||||
client, err := marathon.NewClient(config)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for marathon, error: %s", err)
|
||||
return err
|
||||
}
|
||||
provider.marathonClient = client
|
||||
update := make(marathon.EventsChannel, 5)
|
||||
if provider.Watch {
|
||||
if err := client.AddEventsListener(update, marathon.EVENTS_APPLICATIONS); err != nil {
|
||||
log.Errorf("Failed to register for subscriptions, %s", err)
|
||||
} else {
|
||||
go func() {
|
||||
for {
|
||||
event := <-update
|
||||
log.Debug("Marathon event receveived", event)
|
||||
configuration := provider.loadMarathonConfig()
|
||||
if configuration != nil {
|
||||
configurationChan <- configMessage{"marathon", configuration}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
configuration := provider.loadMarathonConfig()
|
||||
configurationChan <- configMessage{"marathon", configuration}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *MarathonProvider) loadMarathonConfig() *Configuration {
|
||||
var MarathonFuncMap = template.FuncMap{
|
||||
"getPort": func(task marathon.Task) string {
|
||||
for _, port := range task.Ports {
|
||||
return strconv.Itoa(port)
|
||||
}
|
||||
return ""
|
||||
},
|
||||
"getHost": func(application marathon.Application) string {
|
||||
for key, value := range application.Labels {
|
||||
if key == "traefik.host" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return strings.Replace(application.ID, "/", "", 1)
|
||||
},
|
||||
"getWeight": func(application marathon.Application) string {
|
||||
for key, value := range application.Labels {
|
||||
if key == "traefik.weight" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return "0"
|
||||
},
|
||||
"getDomain": func(application marathon.Application) string {
|
||||
for key, value := range application.Labels {
|
||||
if key == "traefik.domain" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return provider.Domain
|
||||
},
|
||||
"getPrefixes": func(application marathon.Application) ([]string, error) {
|
||||
for key, value := range application.Labels {
|
||||
if key == "traefik.prefixes" {
|
||||
return strings.Split(value, ","), nil
|
||||
}
|
||||
}
|
||||
return []string{}, nil
|
||||
},
|
||||
"replace": func(s1 string, s2 string, s3 string) string {
|
||||
return strings.Replace(s3, s1, s2, -1)
|
||||
},
|
||||
}
|
||||
configuration := new(Configuration)
|
||||
|
||||
applications, err := provider.marathonClient.Applications(nil)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for marathon, error: %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
tasks, err := provider.marathonClient.AllTasks()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for marathon, error: %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
//filter tasks
|
||||
filteredTasks := fun.Filter(func(task marathon.Task) bool {
|
||||
if len(task.Ports) == 0 {
|
||||
log.Debug("Filtering marathon task without port", task.AppID)
|
||||
return false
|
||||
}
|
||||
application := getApplication(task, applications.Apps)
|
||||
if application == nil {
|
||||
log.Errorf("Unable to get marathon application from task %s", task.AppID)
|
||||
return false
|
||||
}
|
||||
_, err := strconv.Atoi(application.Labels["traefik.port"])
|
||||
if len(application.Ports) > 1 && err != nil {
|
||||
log.Debug("Filtering marathon task with more than 1 port and no traefik.port label", task.AppID)
|
||||
return false
|
||||
}
|
||||
if application.Labels["traefik.enable"] == "false" {
|
||||
log.Debug("Filtering disabled marathon task", task.AppID)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, tasks.Tasks).([]marathon.Task)
|
||||
|
||||
//filter apps
|
||||
filteredApps := fun.Filter(func(app marathon.Application) bool {
|
||||
//get ports from app tasks
|
||||
if !fun.Exists(func(task marathon.Task) bool {
|
||||
if task.AppID == app.ID {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, filteredTasks) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, applications.Apps).([]marathon.Application)
|
||||
|
||||
templateObjects := struct {
|
||||
Applications []marathon.Application
|
||||
Tasks []marathon.Task
|
||||
Domain string
|
||||
}{
|
||||
filteredApps,
|
||||
filteredTasks,
|
||||
provider.Domain,
|
||||
}
|
||||
|
||||
tmpl := template.New(provider.Filename).Funcs(MarathonFuncMap)
|
||||
if len(provider.Filename) > 0 {
|
||||
_, err := tmpl.ParseFiles(provider.Filename)
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
buf, err := Asset("providerTemplates/marathon.tmpl")
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
}
|
||||
_, err = tmpl.Parse(string(buf))
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
||||
err = tmpl.Execute(&buffer, templateObjects)
|
||||
if err != nil {
|
||||
log.Error("Error with marathon template:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := toml.Decode(buffer.String(), configuration); err != nil {
|
||||
log.Error("Error creating marathon configuration:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return configuration
|
||||
}
|
||||
|
||||
func getApplication(task marathon.Task, apps []marathon.Application) *marathon.Application {
|
||||
for _, application := range apps {
|
||||
if application.ID == task.AppID {
|
||||
return &application
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@@ -1,6 +1,3 @@
|
||||
/*
|
||||
Copyright
|
||||
*/
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
@@ -9,10 +6,12 @@ import (
|
||||
"github.com/mailgun/oxy/cbreaker"
|
||||
)
|
||||
|
||||
// CircuitBreaker holds the oxy circuit breaker.
|
||||
type CircuitBreaker struct {
|
||||
circuitBreaker *cbreaker.CircuitBreaker
|
||||
}
|
||||
|
||||
// NewCircuitBreaker returns a new CircuitBreaker.
|
||||
func NewCircuitBreaker(next http.Handler, expression string, options ...cbreaker.CircuitBreakerOption) *CircuitBreaker {
|
||||
circuitBreaker, _ := cbreaker.New(next, expression, options...)
|
||||
return &CircuitBreaker{circuitBreaker}
|
||||
|
@@ -1,6 +1,3 @@
|
||||
/*
|
||||
Copyright
|
||||
*/
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
@@ -16,7 +13,7 @@ type Logger struct {
|
||||
file *os.File
|
||||
}
|
||||
|
||||
// NewLogger returns a new Logger instance
|
||||
// NewLogger returns a new Logger instance.
|
||||
func NewLogger(file string) *Logger {
|
||||
if len(file) > 0 {
|
||||
fi, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
@@ -36,6 +33,7 @@ func (l *Logger) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.Ha
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the logger (i.e. the file).
|
||||
func (l *Logger) Close() {
|
||||
l.file.Close()
|
||||
}
|
||||
|
@@ -1,6 +1,3 @@
|
||||
/*
|
||||
Copyright
|
||||
*/
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
@@ -11,10 +8,12 @@ import (
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// Routes holds the gorilla mux routes (for the API & co).
|
||||
type Routes struct {
|
||||
router *mux.Router
|
||||
}
|
||||
|
||||
// NewRoutes return a Routes based on the given router.
|
||||
func NewRoutes(router *mux.Router) *Routes {
|
||||
return &Routes{router}
|
||||
}
|
||||
|
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
Copyright
|
||||
*/
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/mailgun/oxy/roundrobin"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/mailgun/oxy/roundrobin"
|
||||
)
|
||||
|
||||
// WebsocketUpgrader holds Websocket configuration.
|
||||
type WebsocketUpgrader struct {
|
||||
rr *roundrobin.RoundRobin
|
||||
}
|
||||
|
||||
// NewWebsocketUpgrader returns a new WebsocketUpgrader.
|
||||
func NewWebsocketUpgrader(rr *roundrobin.RoundRobin) *WebsocketUpgrader {
|
||||
wu := WebsocketUpgrader{
|
||||
rr: rr,
|
||||
|
@@ -73,15 +73,16 @@ func NewProxy(target *url.URL) *WebsocketProxy {
|
||||
// ServeHTTP implements the http.Handler that proxies WebSocket connections.
|
||||
func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if w.Backend == nil {
|
||||
log.Println("websocketproxy: backend function is not defined")
|
||||
http.Error(rw, "internal server error (code: 1)", http.StatusInternalServerError)
|
||||
log.Errorf("Websocketproxy: backend function is not defined")
|
||||
http.Error(rw, "Backend not found", http.StatusInternalServerError)
|
||||
http.NotFound(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
backendURL := w.Backend(req)
|
||||
if backendURL == nil {
|
||||
log.Println("websocketproxy: backend URL is nil")
|
||||
http.Error(rw, "internal server error (code: 2)", http.StatusInternalServerError)
|
||||
log.Errorf("Websocketproxy: backend URL is nil")
|
||||
http.Error(rw, "Backend URL is nil", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -100,6 +101,9 @@ func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
for _, cookie := range req.Header[http.CanonicalHeaderKey("Cookie")] {
|
||||
requestHeader.Add("Cookie", cookie)
|
||||
}
|
||||
for _, auth := range req.Header[http.CanonicalHeaderKey("Authorization")] {
|
||||
requestHeader.Add("Authorization", auth)
|
||||
}
|
||||
|
||||
// Pass X-Forwarded-For headers too, code below is a part of
|
||||
// httputil.ReverseProxy. See http://en.wikipedia.org/wiki/X-Forwarded-For
|
||||
@@ -123,15 +127,19 @@ func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
requestHeader.Set("X-Forwarded-Proto", "https")
|
||||
}
|
||||
|
||||
//frontend Origin != backend Origin
|
||||
requestHeader.Del("Origin")
|
||||
|
||||
// Connect to the backend URL, also pass the headers we get from the requst
|
||||
// together with the Forwarded headers we prepared above.
|
||||
// TODO: support multiplexing on the same backend connection instead of
|
||||
// opening a new TCP connection time for each request. This should be
|
||||
// optional:
|
||||
// http://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-01
|
||||
connBackend, resp, err := dialer.Dial(backendURL.String(), nil)
|
||||
connBackend, resp, err := dialer.Dial(backendURL.String(), requestHeader)
|
||||
if err != nil {
|
||||
log.Printf("websocketproxy: couldn't dial to remote backend url %s, %s, %+v", backendURL.String(), err, resp)
|
||||
log.Errorf("Websocketproxy: couldn't dial to remote backend url %s, %s, %+v", backendURL.String(), err, resp)
|
||||
http.Error(rw, "Remote backend unreachable", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
defer connBackend.Close()
|
||||
@@ -152,7 +160,8 @@ func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
// Also pass the header that we gathered from the Dial handshake.
|
||||
connPub, err := upgrader.Upgrade(rw, req, upgradeHeader)
|
||||
if err != nil {
|
||||
log.Printf("websocketproxy: couldn't upgrade %s\n", err)
|
||||
log.Errorf("Websocketproxy: couldn't upgrade %s", err)
|
||||
http.NotFound(rw, req)
|
||||
return
|
||||
}
|
||||
defer connPub.Close()
|
||||
|
@@ -1,5 +0,0 @@
|
||||
package main
|
||||
|
||||
type Provider interface {
|
||||
Provide(configurationChan chan<- configMessage) error
|
||||
}
|
19
provider/boltdb.go
Normal file
19
provider/boltdb.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package provider
|
||||
|
||||
import "github.com/emilevauge/traefik/types"
|
||||
|
||||
// BoltDb holds configurations of the BoltDb provider.
|
||||
type BoltDb struct {
|
||||
Watch bool
|
||||
Endpoint string
|
||||
Prefix string
|
||||
Filename string
|
||||
KvProvider *Kv
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
provider.KvProvider = NewBoltDbProvider(provider)
|
||||
return provider.KvProvider.provide(configurationChan)
|
||||
}
|
19
provider/consul.go
Normal file
19
provider/consul.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package provider
|
||||
|
||||
import "github.com/emilevauge/traefik/types"
|
||||
|
||||
// Consul holds configurations of the Consul provider.
|
||||
type Consul struct {
|
||||
Watch bool
|
||||
Endpoint string
|
||||
Prefix string
|
||||
Filename string
|
||||
KvProvider *Kv
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
provider.KvProvider = NewConsulProvider(provider)
|
||||
return provider.KvProvider.provide(configurationChan)
|
||||
}
|
255
provider/docker.go
Normal file
255
provider/docker.go
Normal file
@@ -0,0 +1,255 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/emilevauge/traefik/autogen"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
// Docker holds configurations of the Docker provider.
|
||||
type Docker struct {
|
||||
Watch bool
|
||||
Endpoint string
|
||||
Filename string
|
||||
Domain string
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
|
||||
dockerClient, err := docker.NewClient(provider.Endpoint)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for docker, error: %s", err)
|
||||
return err
|
||||
}
|
||||
err = dockerClient.Ping()
|
||||
if err != nil {
|
||||
log.Errorf("Docker connection error %+v", err)
|
||||
return err
|
||||
}
|
||||
log.Debug("Docker connection established")
|
||||
if provider.Watch {
|
||||
dockerEvents := make(chan *docker.APIEvents)
|
||||
dockerClient.AddEventListener(dockerEvents)
|
||||
log.Debug("Docker listening")
|
||||
go func() {
|
||||
operation := func() error {
|
||||
for {
|
||||
event := <-dockerEvents
|
||||
if event == nil {
|
||||
return errors.New("Docker event nil")
|
||||
// log.Fatalf("Docker connection error")
|
||||
}
|
||||
if event.Status == "start" || event.Status == "die" {
|
||||
log.Debugf("Docker event receveived %+v", event)
|
||||
configuration := provider.loadDockerConfig(dockerClient)
|
||||
if configuration != nil {
|
||||
configurationChan <- types.ConfigMessage{"docker", configuration}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Docker connection error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, backoff.NewExponentialBackOff(), notify)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot connect to docker server %+v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
configuration := provider.loadDockerConfig(dockerClient)
|
||||
configurationChan <- types.ConfigMessage{"docker", configuration}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Docker) loadDockerConfig(dockerClient *docker.Client) *types.Configuration {
|
||||
var DockerFuncMap = template.FuncMap{
|
||||
"getBackend": func(container docker.Container) string {
|
||||
if label, err := provider.getLabel(container, "traefik.backend"); err == nil {
|
||||
return label
|
||||
}
|
||||
return provider.getEscapedName(container.Name)
|
||||
},
|
||||
"getPort": func(container docker.Container) string {
|
||||
if label, err := provider.getLabel(container, "traefik.port"); err == nil {
|
||||
return label
|
||||
}
|
||||
for key := range container.NetworkSettings.Ports {
|
||||
return key.Port()
|
||||
}
|
||||
return ""
|
||||
},
|
||||
"getWeight": func(container docker.Container) string {
|
||||
if label, err := provider.getLabel(container, "traefik.weight"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "0"
|
||||
},
|
||||
"getDomain": func(container docker.Container) string {
|
||||
if label, err := provider.getLabel(container, "traefik.domain"); err == nil {
|
||||
return label
|
||||
}
|
||||
return provider.Domain
|
||||
},
|
||||
"getProtocol": func(container docker.Container) string {
|
||||
if label, err := provider.getLabel(container, "traefik.protocol"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "http"
|
||||
},
|
||||
"getPassHostHeader": func(container docker.Container) string {
|
||||
if passHostHeader, err := provider.getLabel(container, "traefik.frontend.passHostHeader"); err == nil {
|
||||
return passHostHeader
|
||||
}
|
||||
return "false"
|
||||
},
|
||||
"getFrontendValue": provider.GetFrontendValue,
|
||||
"getFrontendRule": provider.GetFrontendRule,
|
||||
"replace": func(s1 string, s2 string, s3 string) string {
|
||||
return strings.Replace(s3, s1, s2, -1)
|
||||
},
|
||||
}
|
||||
configuration := new(types.Configuration)
|
||||
containerList, _ := dockerClient.ListContainers(docker.ListContainersOptions{})
|
||||
containersInspected := []docker.Container{}
|
||||
frontends := map[string][]docker.Container{}
|
||||
|
||||
// get inspect containers
|
||||
for _, container := range containerList {
|
||||
containerInspected, _ := dockerClient.InspectContainer(container.ID)
|
||||
containersInspected = append(containersInspected, *containerInspected)
|
||||
}
|
||||
|
||||
// filter containers
|
||||
filteredContainers := fun.Filter(func(container docker.Container) bool {
|
||||
if len(container.NetworkSettings.Ports) == 0 {
|
||||
log.Debugf("Filtering container without port %s", container.Name)
|
||||
return false
|
||||
}
|
||||
_, err := strconv.Atoi(container.Config.Labels["traefik.port"])
|
||||
if len(container.NetworkSettings.Ports) > 1 && err != nil {
|
||||
log.Debugf("Filtering container with more than 1 port and no traefik.port label %s", container.Name)
|
||||
return false
|
||||
}
|
||||
if container.Config.Labels["traefik.enable"] == "false" {
|
||||
log.Debugf("Filtering disabled container %s", container.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
if _, err := provider.getLabels(container, []string{"traefik.frontend.rule", "traefik.frontend.value"}); err != nil {
|
||||
log.Debugf("Filtering bad labeled container %s", container.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}, containersInspected).([]docker.Container)
|
||||
|
||||
for _, container := range filteredContainers {
|
||||
frontends[provider.getFrontendName(container)] = append(frontends[provider.getFrontendName(container)], container)
|
||||
}
|
||||
|
||||
templateObjects := struct {
|
||||
Containers []docker.Container
|
||||
Frontends map[string][]docker.Container
|
||||
Domain string
|
||||
}{
|
||||
filteredContainers,
|
||||
frontends,
|
||||
provider.Domain,
|
||||
}
|
||||
tmpl := template.New(provider.Filename).Funcs(DockerFuncMap)
|
||||
if len(provider.Filename) > 0 {
|
||||
_, err := tmpl.ParseFiles(provider.Filename)
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
buf, err := autogen.Asset("templates/docker.tmpl")
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
}
|
||||
_, err = tmpl.Parse(string(buf))
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
err := tmpl.Execute(&buffer, templateObjects)
|
||||
if err != nil {
|
||||
log.Error("Error with docker template", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := toml.Decode(buffer.String(), configuration); err != nil {
|
||||
log.Error("Error creating docker configuration ", err)
|
||||
return nil
|
||||
}
|
||||
return configuration
|
||||
}
|
||||
|
||||
func (provider *Docker) getFrontendName(container docker.Container) string {
|
||||
// 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))
|
||||
return strings.Replace(frontendName, ".", "-", -1)
|
||||
}
|
||||
|
||||
func (provider *Docker) getEscapedName(name string) string {
|
||||
return strings.Replace(name, "/", "", -1)
|
||||
}
|
||||
|
||||
func (provider *Docker) getLabel(container docker.Container, label string) (string, error) {
|
||||
for key, value := range container.Config.Labels {
|
||||
if key == label {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("Label not found:" + label)
|
||||
}
|
||||
|
||||
func (provider *Docker) getLabels(container docker.Container, labels []string) (map[string]string, error) {
|
||||
foundLabels := map[string]string{}
|
||||
for _, label := range labels {
|
||||
foundLabel, err := provider.getLabel(container, label)
|
||||
if err != nil {
|
||||
return nil, errors.New("Label not found: " + label)
|
||||
}
|
||||
foundLabels[label] = foundLabel
|
||||
}
|
||||
return foundLabels, nil
|
||||
}
|
||||
|
||||
// GetFrontendValue returns the frontend value for the specified container, using
|
||||
// it's label. It returns a default one if the label is not present.
|
||||
func (provider *Docker) GetFrontendValue(container docker.Container) string {
|
||||
if label, err := provider.getLabel(container, "traefik.frontend.value"); err == nil {
|
||||
return label
|
||||
}
|
||||
return provider.getEscapedName(container.Name) + "." + provider.Domain
|
||||
}
|
||||
|
||||
// GetFrontendRule returns the frontend rule for the specified container, using
|
||||
// it's label. It returns a default one (Host) if the label is not present.
|
||||
func (provider *Docker) GetFrontendRule(container docker.Container) string {
|
||||
if label, err := provider.getLabel(container, "traefik.frontend.rule"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "Host"
|
||||
}
|
19
provider/etcd.go
Normal file
19
provider/etcd.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package provider
|
||||
|
||||
import "github.com/emilevauge/traefik/types"
|
||||
|
||||
// Etcd holds configurations of the Etcd provider.
|
||||
type Etcd struct {
|
||||
Watch bool
|
||||
Endpoint string
|
||||
Prefix string
|
||||
Filename string
|
||||
KvProvider *Kv
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
provider.KvProvider = NewEtcdProvider(provider)
|
||||
return provider.KvProvider.provide(configurationChan)
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package provider
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -7,15 +7,19 @@ import (
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
"gopkg.in/fsnotify.v1"
|
||||
)
|
||||
|
||||
type FileProvider struct {
|
||||
// File holds configurations of the File provider.
|
||||
type File struct {
|
||||
Watch bool
|
||||
Filename string
|
||||
}
|
||||
|
||||
func (provider *FileProvider) Provide(configurationChan chan<- configMessage) error {
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *File) Provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Error("Error creating file watcher", err)
|
||||
@@ -38,9 +42,9 @@ func (provider *FileProvider) Provide(configurationChan chan<- configMessage) er
|
||||
case event := <-watcher.Events:
|
||||
if strings.Contains(event.Name, file.Name()) {
|
||||
log.Debug("File event:", event)
|
||||
configuration := provider.LoadFileConfig(file.Name())
|
||||
configuration := provider.loadFileConfig(file.Name())
|
||||
if configuration != nil {
|
||||
configurationChan <- configMessage{"file", configuration}
|
||||
configurationChan <- types.ConfigMessage{"file", configuration}
|
||||
}
|
||||
}
|
||||
case error := <-watcher.Errors:
|
||||
@@ -55,13 +59,13 @@ func (provider *FileProvider) Provide(configurationChan chan<- configMessage) er
|
||||
}
|
||||
}
|
||||
|
||||
configuration := provider.LoadFileConfig(file.Name())
|
||||
configurationChan <- configMessage{"file", configuration}
|
||||
configuration := provider.loadFileConfig(file.Name())
|
||||
configurationChan <- types.ConfigMessage{"file", configuration}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *FileProvider) LoadFileConfig(filename string) *Configuration {
|
||||
configuration := new(Configuration)
|
||||
func (provider *File) loadFileConfig(filename string) *types.Configuration {
|
||||
configuration := new(types.Configuration)
|
||||
if _, err := toml.DecodeFile(filename, configuration); err != nil {
|
||||
log.Error("Error reading file:", err)
|
||||
return nil
|
1
provider/file_test.go
Normal file
1
provider/file_test.go
Normal file
@@ -0,0 +1 @@
|
||||
package provider
|
@@ -1,27 +1,28 @@
|
||||
/*
|
||||
Copyright
|
||||
*/
|
||||
package main
|
||||
// Package provider holds the different provider implementation.
|
||||
package provider
|
||||
|
||||
import (
|
||||
"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/store"
|
||||
"github.com/docker/libkv/store/boltdb"
|
||||
"github.com/docker/libkv/store/consul"
|
||||
"github.com/docker/libkv/store/etcd"
|
||||
"github.com/docker/libkv/store/zookeeper"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"errors"
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/libkv/store"
|
||||
"time"
|
||||
"github.com/emilevauge/traefik/autogen"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
)
|
||||
|
||||
type KvProvider struct {
|
||||
// Kv holds common configurations of key-value providers.
|
||||
type Kv struct {
|
||||
Watch bool
|
||||
Endpoint string
|
||||
Prefix string
|
||||
@@ -30,8 +31,9 @@ type KvProvider struct {
|
||||
kvclient store.Store
|
||||
}
|
||||
|
||||
func NewConsulProvider(provider *ConsulProvider) *KvProvider {
|
||||
kvProvider := new(KvProvider)
|
||||
// NewConsulProvider returns a Consul provider.
|
||||
func NewConsulProvider(provider *Consul) *Kv {
|
||||
kvProvider := new(Kv)
|
||||
kvProvider.Watch = provider.Watch
|
||||
kvProvider.Endpoint = provider.Endpoint
|
||||
kvProvider.Prefix = provider.Prefix
|
||||
@@ -40,8 +42,9 @@ func NewConsulProvider(provider *ConsulProvider) *KvProvider {
|
||||
return kvProvider
|
||||
}
|
||||
|
||||
func NewEtcdProvider(provider *EtcdProvider) *KvProvider {
|
||||
kvProvider := new(KvProvider)
|
||||
// NewEtcdProvider returns a Etcd provider.
|
||||
func NewEtcdProvider(provider *Etcd) *Kv {
|
||||
kvProvider := new(Kv)
|
||||
kvProvider.Watch = provider.Watch
|
||||
kvProvider.Endpoint = provider.Endpoint
|
||||
kvProvider.Prefix = provider.Prefix
|
||||
@@ -50,8 +53,9 @@ func NewEtcdProvider(provider *EtcdProvider) *KvProvider {
|
||||
return kvProvider
|
||||
}
|
||||
|
||||
func NewZkProvider(provider *ZookepperProvider) *KvProvider {
|
||||
kvProvider := new(KvProvider)
|
||||
// NewZkProvider returns a Zookepper provider.
|
||||
func NewZkProvider(provider *Zookepper) *Kv {
|
||||
kvProvider := new(Kv)
|
||||
kvProvider.Watch = provider.Watch
|
||||
kvProvider.Endpoint = provider.Endpoint
|
||||
kvProvider.Prefix = provider.Prefix
|
||||
@@ -60,8 +64,9 @@ func NewZkProvider(provider *ZookepperProvider) *KvProvider {
|
||||
return kvProvider
|
||||
}
|
||||
|
||||
func NewBoltDbProvider(provider *BoltDbProvider) *KvProvider {
|
||||
kvProvider := new(KvProvider)
|
||||
// NewBoltDbProvider returns a BoldDb provider.
|
||||
func NewBoltDbProvider(provider *BoltDb) *Kv {
|
||||
kvProvider := new(Kv)
|
||||
kvProvider.Watch = provider.Watch
|
||||
kvProvider.Endpoint = provider.Endpoint
|
||||
kvProvider.Prefix = provider.Prefix
|
||||
@@ -70,7 +75,7 @@ func NewBoltDbProvider(provider *BoltDbProvider) *KvProvider {
|
||||
return kvProvider
|
||||
}
|
||||
|
||||
func (provider *KvProvider) provide(configurationChan chan<- configMessage) error {
|
||||
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
switch provider.StoreType {
|
||||
case store.CONSUL:
|
||||
consul.Register()
|
||||
@@ -88,6 +93,7 @@ func (provider *KvProvider) provide(configurationChan chan<- configMessage) erro
|
||||
[]string{provider.Endpoint},
|
||||
&store.Config{
|
||||
ConnectionTimeout: 30 * time.Second,
|
||||
Bucket: "traefik",
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -108,19 +114,19 @@ func (provider *KvProvider) provide(configurationChan chan<- configMessage) erro
|
||||
<-chanKeys
|
||||
configuration := provider.loadConfig()
|
||||
if configuration != nil {
|
||||
configurationChan <- configMessage{string(provider.StoreType), configuration}
|
||||
configurationChan <- types.ConfigMessage{string(provider.StoreType), configuration}
|
||||
}
|
||||
defer close(stopCh)
|
||||
}
|
||||
}()
|
||||
}
|
||||
configuration := provider.loadConfig()
|
||||
configurationChan <- configMessage{string(provider.StoreType), configuration}
|
||||
configurationChan <- types.ConfigMessage{string(provider.StoreType), configuration}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *KvProvider) loadConfig() *Configuration {
|
||||
configuration := new(Configuration)
|
||||
func (provider *Kv) loadConfig() *types.Configuration {
|
||||
configuration := new(types.Configuration)
|
||||
templateObjects := struct {
|
||||
Prefix string
|
||||
}{
|
||||
@@ -166,7 +172,7 @@ func (provider *KvProvider) loadConfig() *Configuration {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
buf, err := Asset("providerTemplates/kv.tmpl")
|
||||
buf, err := autogen.Asset("templates/kv.tmpl")
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
}
|
260
provider/marathon.go
Normal file
260
provider/marathon.go
Normal file
@@ -0,0 +1,260 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/emilevauge/traefik/autogen"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
"github.com/gambol99/go-marathon"
|
||||
)
|
||||
|
||||
// Marathon holds configuration of the Marathon provider.
|
||||
type Marathon struct {
|
||||
Watch bool
|
||||
Endpoint string
|
||||
Domain string
|
||||
Filename string
|
||||
NetworkInterface string
|
||||
marathonClient marathon.Marathon
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
config := marathon.NewDefaultConfig()
|
||||
config.URL = provider.Endpoint
|
||||
config.EventsInterface = provider.NetworkInterface
|
||||
client, err := marathon.NewClient(config)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for marathon, error: %s", err)
|
||||
return err
|
||||
}
|
||||
provider.marathonClient = client
|
||||
update := make(marathon.EventsChannel, 5)
|
||||
if provider.Watch {
|
||||
if err := client.AddEventsListener(update, marathon.EVENTS_APPLICATIONS); err != nil {
|
||||
log.Errorf("Failed to register for subscriptions, %s", err)
|
||||
} else {
|
||||
go func() {
|
||||
for {
|
||||
event := <-update
|
||||
log.Debug("Marathon event receveived", event)
|
||||
configuration := provider.loadMarathonConfig()
|
||||
if configuration != nil {
|
||||
configurationChan <- types.ConfigMessage{"marathon", configuration}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
configuration := provider.loadMarathonConfig()
|
||||
configurationChan <- types.ConfigMessage{"marathon", configuration}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Marathon) loadMarathonConfig() *types.Configuration {
|
||||
var MarathonFuncMap = template.FuncMap{
|
||||
"getPort": func(task marathon.Task) string {
|
||||
for _, port := range task.Ports {
|
||||
return strconv.Itoa(port)
|
||||
}
|
||||
return ""
|
||||
},
|
||||
"getWeight": func(task marathon.Task, applications []marathon.Application) string {
|
||||
application, errApp := getApplication(task, applications)
|
||||
if errApp != nil {
|
||||
log.Errorf("Unable to get marathon application from task %s", task.AppID)
|
||||
return "0"
|
||||
}
|
||||
if label, err := provider.getLabel(application, "traefik.weight"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "0"
|
||||
},
|
||||
"getDomain": func(application marathon.Application) string {
|
||||
if label, err := provider.getLabel(application, "traefik.domain"); err == nil {
|
||||
return label
|
||||
}
|
||||
return provider.Domain
|
||||
},
|
||||
"replace": func(s1 string, s2 string, s3 string) string {
|
||||
return strings.Replace(s3, s1, s2, -1)
|
||||
},
|
||||
"getProtocol": func(task marathon.Task, applications []marathon.Application) string {
|
||||
application, errApp := getApplication(task, applications)
|
||||
if errApp != nil {
|
||||
log.Errorf("Unable to get marathon application from task %s", task.AppID)
|
||||
return "http"
|
||||
}
|
||||
if label, err := provider.getLabel(application, "traefik.protocol"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "http"
|
||||
},
|
||||
"getPassHostHeader": func(application marathon.Application) string {
|
||||
if passHostHeader, err := provider.getLabel(application, "traefik.frontend.passHostHeader"); err == nil {
|
||||
return passHostHeader
|
||||
}
|
||||
return "false"
|
||||
},
|
||||
"getFrontendValue": provider.GetFrontendValue,
|
||||
"getFrontendRule": provider.GetFrontendRule,
|
||||
}
|
||||
configuration := new(types.Configuration)
|
||||
|
||||
applications, err := provider.marathonClient.Applications(nil)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for marathon, error: %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
tasks, err := provider.marathonClient.AllTasks()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for marathon, error: %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
//filter tasks
|
||||
filteredTasks := fun.Filter(func(task marathon.Task) bool {
|
||||
if len(task.Ports) == 0 {
|
||||
log.Debug("Filtering marathon task without port %s", task.AppID)
|
||||
return false
|
||||
}
|
||||
application, errApp := getApplication(task, applications.Apps)
|
||||
if errApp != nil {
|
||||
log.Errorf("Unable to get marathon application from task %s", task.AppID)
|
||||
return false
|
||||
}
|
||||
_, err := strconv.Atoi(application.Labels["traefik.port"])
|
||||
if len(application.Ports) > 1 && err != nil {
|
||||
log.Debugf("Filtering marathon task %s with more than 1 port and no traefik.port label", task.AppID)
|
||||
return false
|
||||
}
|
||||
if application.Labels["traefik.enable"] == "false" {
|
||||
log.Debugf("Filtering disabled marathon task %s", task.AppID)
|
||||
return false
|
||||
}
|
||||
//filter healthchecks
|
||||
if application.HasHealthChecks() {
|
||||
if task.HasHealthCheckResults() {
|
||||
for _, healthcheck := range task.HealthCheckResult {
|
||||
// found one bad healthcheck, return false
|
||||
if !healthcheck.Alive {
|
||||
log.Debugf("Filtering marathon task %s with bad healthcheck", task.AppID)
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Debugf("Filtering marathon task %s with bad healthcheck", task.AppID)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}, tasks.Tasks).([]marathon.Task)
|
||||
|
||||
//filter apps
|
||||
filteredApps := fun.Filter(func(app marathon.Application) bool {
|
||||
//get ports from app tasks
|
||||
if !fun.Exists(func(task marathon.Task) bool {
|
||||
if task.AppID == app.ID {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, filteredTasks) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, applications.Apps).([]marathon.Application)
|
||||
|
||||
templateObjects := struct {
|
||||
Applications []marathon.Application
|
||||
Tasks []marathon.Task
|
||||
Domain string
|
||||
}{
|
||||
filteredApps,
|
||||
filteredTasks,
|
||||
provider.Domain,
|
||||
}
|
||||
|
||||
tmpl := template.New(provider.Filename).Funcs(MarathonFuncMap)
|
||||
if len(provider.Filename) > 0 {
|
||||
_, err := tmpl.ParseFiles(provider.Filename)
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
buf, err := autogen.Asset("templates/marathon.tmpl")
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
}
|
||||
_, err = tmpl.Parse(string(buf))
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
||||
err = tmpl.Execute(&buffer, templateObjects)
|
||||
if err != nil {
|
||||
log.Error("Error with marathon template:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := toml.Decode(buffer.String(), configuration); err != nil {
|
||||
log.Error("Error creating marathon configuration:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return configuration
|
||||
}
|
||||
|
||||
func getApplication(task marathon.Task, apps []marathon.Application) (marathon.Application, error) {
|
||||
for _, application := range apps {
|
||||
if application.ID == task.AppID {
|
||||
return application, nil
|
||||
}
|
||||
}
|
||||
return marathon.Application{}, errors.New("Application not found: " + task.AppID)
|
||||
}
|
||||
|
||||
func (provider *Marathon) getLabel(application marathon.Application, label string) (string, error) {
|
||||
for key, value := range application.Labels {
|
||||
if key == label {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("Label not found:" + label)
|
||||
}
|
||||
|
||||
func (provider *Marathon) getEscapedName(name string) string {
|
||||
return strings.Replace(name, "/", "", -1)
|
||||
}
|
||||
|
||||
// GetFrontendValue returns the frontend value for the specified application, using
|
||||
// it's label. It returns a default one if the label is not present.
|
||||
func (provider *Marathon) GetFrontendValue(application marathon.Application) string {
|
||||
if label, err := provider.getLabel(application, "traefik.frontend.value"); err == nil {
|
||||
return label
|
||||
}
|
||||
return provider.getEscapedName(application.ID) + "." + provider.Domain
|
||||
}
|
||||
|
||||
// GetFrontendRule returns the frontend rule for the specified application, using
|
||||
// it's label. It returns a default one (Host) if the label is not present.
|
||||
func (provider *Marathon) GetFrontendRule(application marathon.Application) string {
|
||||
if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "Host"
|
||||
}
|
10
provider/provider.go
Normal file
10
provider/provider.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package provider
|
||||
|
||||
import "github.com/emilevauge/traefik/types"
|
||||
|
||||
// Provider defines methods of a provider.
|
||||
type Provider interface {
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
Provide(configurationChan chan<- types.ConfigMessage) error
|
||||
}
|
19
provider/zk.go
Normal file
19
provider/zk.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package provider
|
||||
|
||||
import "github.com/emilevauge/traefik/types"
|
||||
|
||||
// Zookepper holds configurations of the Zookepper provider.
|
||||
type Zookepper struct {
|
||||
Watch bool
|
||||
Endpoint string
|
||||
Prefix string
|
||||
Filename string
|
||||
KvProvider *Kv
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
provider.KvProvider = NewZkProvider(provider)
|
||||
return provider.KvProvider.provide(configurationChan)
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
[backends]{{range .Containers}}
|
||||
[backends.backend-{{getBackend .}}.servers.server-{{.Name | replace "/" "" | replace "." "-"}}]
|
||||
url = "http://{{.NetworkSettings.IPAddress}}:{{getPort .}}"
|
||||
weight = {{getWeight .}}
|
||||
{{end}}
|
||||
|
||||
[frontends]{{range $host, $containers := .Hosts}}
|
||||
[frontends.frontend-{{$host}}]
|
||||
{{$container := index $containers 0}}
|
||||
backend = "backend-{{getBackend $container}}"
|
||||
[frontends.frontend-{{$host}}.routes.route-host-{{$host}}]
|
||||
rule = "Host"
|
||||
value = "{{$host}}.{{getDomain $container}}"
|
||||
{{end}}
|
@@ -1,27 +0,0 @@
|
||||
{{$apps := .Applications}}
|
||||
[backends]{{range .Tasks}}
|
||||
[backends.backend{{.AppID | replace "/" "-"}}.servers.server-{{.ID | replace "." "-"}}]
|
||||
url = "http://{{.Host}}:{{getPort .}}"
|
||||
{{$appID := .AppID}}
|
||||
{{range $apps}}
|
||||
{{if eq $appID .ID}}
|
||||
weight = {{getWeight .}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
[frontends]{{ range $app := .Applications}}
|
||||
{{range $prefix := getPrefixes .}}
|
||||
[frontends.frontend{{$app.ID | replace "/" "-"}}{{$prefix | replace "/" "-"}}]
|
||||
backend = "backend{{$app.ID | replace "/" "-"}}"
|
||||
[frontends.frontend-{{getHost $app | replace "/" "-"}}{{$prefix | replace "/" "-"}}.routes.route-prefix{{$prefix | replace "/" "-"}}]
|
||||
rule = "PathPrefix"
|
||||
value = "{{.}}"
|
||||
{{else}}
|
||||
[frontends.frontend{{.ID | replace "/" "-"}}]
|
||||
backend = "backend{{.ID | replace "/" "-"}}"
|
||||
[frontends.frontend-{{getHost $app | replace "/" "-"}}.routes.route-host-{{getHost $app | replace "/" "-"}}]
|
||||
rule = "Host"
|
||||
value = "{{getHost $app | replace "/" "-"}}.{{getDomain .}}"
|
||||
{{end}}
|
||||
{{end}}
|
@@ -1,19 +0,0 @@
|
||||
{{$apps := .Applications}}
|
||||
[backends]{{range .Tasks}}
|
||||
[backends.backend{{.AppID | replace "/" "-"}}.servers.server-{{.ID | replace "." "-"}}]
|
||||
url = "http://{{.Host}}:{{getPort .}}"
|
||||
{{$appID := .AppID}}
|
||||
{{range $apps}}
|
||||
{{if eq $appID .ID}}
|
||||
weight = {{getWeight .}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
[frontends]{{range .Applications}}
|
||||
[frontends.frontend{{.ID | replace "/" "-"}}]
|
||||
backend = "backend{{.ID | replace "/" "-"}}"
|
||||
[frontends.frontend-{{getHost . | replace "/" "-"}}.routes.route-host-{{getHost . | replace "/" "-"}}]
|
||||
rule = "Host"
|
||||
value = "{{getHost . | replace "/" "-"}}.{{getDomain .}}"
|
||||
{{end}}
|
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if ! test -e gen.go; then
|
||||
if ! test -e autogen/gen.go; then
|
||||
echo >&2 'error: generate must be run before binary'
|
||||
false
|
||||
fi
|
||||
|
@@ -1,8 +1,8 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if ! test -e gen.go; then
|
||||
echo >&2 'error: generate must be run before binary'
|
||||
if ! test -e autogen/gen.go; then
|
||||
echo >&2 'error: generate must be run before crossbinary'
|
||||
false
|
||||
fi
|
||||
|
||||
|
12
script/dockerversion
Normal file
12
script/dockerversion
Normal file
@@ -0,0 +1,12 @@
|
||||
// AUTOGENERATED FILE; see /go/src/github.com/docker/docker/hack/make/.go-autogen
|
||||
package dockerversion
|
||||
|
||||
var (
|
||||
GITCOMMIT string = "traefik-import"
|
||||
VERSION string = "traefik-import"
|
||||
BUILDTIME string = "traefik-import"
|
||||
|
||||
IAMSTATIC string = "traefik-import"
|
||||
INITSHA1 string = "traefik-import"
|
||||
INITPATH string = "traefik-import"
|
||||
)
|
@@ -5,6 +5,7 @@ set -e
|
||||
DEFAULT_BUNDLES=(
|
||||
validate-gofmt
|
||||
validate-govet
|
||||
generate
|
||||
binary
|
||||
|
||||
test-unit
|
||||
|
@@ -1,8 +1,8 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if ! test -e gen.go; then
|
||||
echo >&2 'error: generate must be run before binary'
|
||||
if ! test -e autogen/gen.go; then
|
||||
echo >&2 'error: generate must be run before test-unit'
|
||||
false
|
||||
fi
|
||||
|
||||
|
@@ -18,5 +18,6 @@
|
||||
</div>
|
||||
<div data-bg-show="frontendCtrl.frontend.backend" class="panel-footer">
|
||||
<span class="label label-warning" role="button" data-toggle="collapse" href="#{{frontendCtrl.frontend.backend}}" aria-expanded="false">{{frontendCtrl.frontend.backend}}</span>
|
||||
<span data-ng-show="frontendCtrl.frontend.passHostHeader" class="label label-warning">Pass Host Header</span>
|
||||
</div>
|
||||
</div>
|
||||
|
14
templates/docker.tmpl
Normal file
14
templates/docker.tmpl
Normal file
@@ -0,0 +1,14 @@
|
||||
[backends]{{range .Containers}}
|
||||
[backends.backend-{{getBackend .}}.servers.server-{{.Name | replace "/" "" | replace "." "-"}}]
|
||||
url = "{{getProtocol .}}://{{.NetworkSettings.IPAddress}}:{{getPort .}}"
|
||||
weight = {{getWeight .}}
|
||||
{{end}}
|
||||
|
||||
[frontends]{{range $frontend, $containers := .Frontends}}
|
||||
[frontends."frontend-{{$frontend}}"]{{$container := index $containers 0}}
|
||||
backend = "backend-{{getBackend $container}}"
|
||||
passHostHeader = {{getPassHostHeader $container}}
|
||||
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
|
||||
rule = "{{getFrontendRule $container}}"
|
||||
value = "{{getFrontendValue $container}}"
|
||||
{{end}}
|
15
templates/marathon.tmpl
Normal file
15
templates/marathon.tmpl
Normal file
@@ -0,0 +1,15 @@
|
||||
{{$apps := .Applications}}
|
||||
[backends]{{range .Tasks}}
|
||||
[backends.backend{{.AppID | replace "/" "-"}}.servers.server-{{.ID | replace "." "-"}}]
|
||||
url = "{{getProtocol . $apps}}://{{.Host}}:{{getPort .}}"
|
||||
weight = {{getWeight . $apps}}
|
||||
{{end}}
|
||||
|
||||
[frontends]{{range .Applications}}
|
||||
[frontends.frontend{{.ID | replace "/" "-"}}]
|
||||
backend = "backend{{.ID | replace "/" "-"}}"
|
||||
passHostHeader = {{getPassHostHeader .}}
|
||||
[frontends.frontend-{{.ID | replace "/" ""}}.routes.route-host-{{.ID | replace "/" ""}}]
|
||||
rule = "{{getFrontendRule .}}"
|
||||
value = "{{getFrontendValue .}}"
|
||||
{{end}}
|
39
tests/whoami.json
Normal file
39
tests/whoami.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"id": "whoami",
|
||||
"cpus": 0.1,
|
||||
"mem": 64.0,
|
||||
"instances": 3,
|
||||
"container": {
|
||||
"type": "DOCKER",
|
||||
"docker": {
|
||||
"image": "emilevauge/whoami",
|
||||
"network": "BRIDGE",
|
||||
"portMappings": [
|
||||
{ "containerPort": 80, "hostPort": 0, "protocol": "tcp" }
|
||||
],
|
||||
"parameters": [{
|
||||
"key": "log-driver",
|
||||
"value": "gelf"
|
||||
}, {
|
||||
"key": "log-opt",
|
||||
"value": "gelf-address=udp://172.17.42.1:12201"
|
||||
}]
|
||||
}
|
||||
},
|
||||
"healthChecks": [
|
||||
{
|
||||
"protocol": "HTTP",
|
||||
"portIndex": 0,
|
||||
"path": "/",
|
||||
"gracePeriodSeconds": 5,
|
||||
"intervalSeconds": 20,
|
||||
"maxConsecutiveFailures": 3
|
||||
}
|
||||
],
|
||||
"labels": {
|
||||
"traefik.weight": "1",
|
||||
"traefik.protocole": "https",
|
||||
"traefik.frontend.rule": "Path",
|
||||
"traefik.frontend.value": "/test"
|
||||
}
|
||||
}
|
59
traefik.go
59
traefik.go
@@ -2,54 +2,41 @@ package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
fmtlog "log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"errors"
|
||||
"github.com/BurntSushi/toml"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/negroni"
|
||||
"github.com/emilevauge/traefik/middlewares"
|
||||
"github.com/emilevauge/traefik/provider"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mailgun/manners"
|
||||
"github.com/mailgun/oxy/cbreaker"
|
||||
"github.com/mailgun/oxy/forward"
|
||||
"github.com/mailgun/oxy/roundrobin"
|
||||
"github.com/thoas/stats"
|
||||
"github.com/unrolled/render"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
Version = ""
|
||||
BuildDate = ""
|
||||
globalConfigFile = kingpin.Arg("conf", "Main configration file.").Default("traefik.toml").String()
|
||||
version = kingpin.Flag("version", "Get Version.").Short('v').Bool()
|
||||
currentConfigurations = make(configs)
|
||||
metrics = stats.New()
|
||||
oxyLogger = &OxyLogger{}
|
||||
templatesRenderer = render.New(render.Options{
|
||||
Directory: "templates",
|
||||
Asset: Asset,
|
||||
AssetNames: AssetNames,
|
||||
})
|
||||
)
|
||||
|
||||
type configMessage struct {
|
||||
providerName string
|
||||
configuration *Configuration
|
||||
}
|
||||
|
||||
type configs map[string]*Configuration
|
||||
|
||||
func main() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
kingpin.Version(Version + " built on the " + BuildDate)
|
||||
@@ -57,15 +44,15 @@ func main() {
|
||||
fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags)
|
||||
var srv *manners.GracefulServer
|
||||
var configurationRouter *mux.Router
|
||||
var configurationChan = make(chan configMessage, 10)
|
||||
var configurationChan = make(chan types.ConfigMessage, 10)
|
||||
defer close(configurationChan)
|
||||
var configurationChanValidated = make(chan configMessage, 10)
|
||||
var configurationChanValidated = make(chan types.ConfigMessage, 10)
|
||||
defer close(configurationChanValidated)
|
||||
var sigs = make(chan os.Signal, 1)
|
||||
defer close(sigs)
|
||||
var stopChan = make(chan bool)
|
||||
defer close(stopChan)
|
||||
var providers = []Provider{}
|
||||
var providers = []provider.Provider{}
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
// load global configuration
|
||||
@@ -99,22 +86,22 @@ func main() {
|
||||
// listen new configurations from providers
|
||||
go func() {
|
||||
lastReceivedConfiguration := time.Unix(0, 0)
|
||||
lastConfigs := make(map[string]*configMessage)
|
||||
lastConfigs := make(map[string]*types.ConfigMessage)
|
||||
for {
|
||||
configMsg := <-configurationChan
|
||||
log.Infof("Configuration receveived from provider %s: %#v", configMsg.providerName, configMsg.configuration)
|
||||
lastConfigs[configMsg.providerName] = &configMsg
|
||||
log.Infof("Configuration receveived from provider %s: %#v", configMsg.ProviderName, configMsg.Configuration)
|
||||
lastConfigs[configMsg.ProviderName] = &configMsg
|
||||
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
|
||||
configurationChanValidated <- configMsg
|
||||
} 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() {
|
||||
<-time.After(globalConfiguration.ProvidersThrottleDuration)
|
||||
if time.Now().After(lastReceivedConfiguration.Add(time.Duration(globalConfiguration.ProvidersThrottleDuration))) {
|
||||
log.Infof("Waited for %s config, OK", configMsg.providerName)
|
||||
configurationChanValidated <- *lastConfigs[configMsg.providerName]
|
||||
log.Infof("Waited for %s config, OK", configMsg.ProviderName)
|
||||
configurationChanValidated <- *lastConfigs[configMsg.ProviderName]
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -124,9 +111,9 @@ func main() {
|
||||
go func() {
|
||||
for {
|
||||
configMsg := <-configurationChanValidated
|
||||
if configMsg.configuration == nil {
|
||||
log.Info("Skipping empty configuration")
|
||||
} else if reflect.DeepEqual(currentConfigurations[configMsg.providerName], configMsg.configuration) {
|
||||
if configMsg.Configuration == nil {
|
||||
log.Info("Skipping empty Configuration")
|
||||
} else if reflect.DeepEqual(currentConfigurations[configMsg.ProviderName], configMsg.Configuration) {
|
||||
log.Info("Skipping same configuration")
|
||||
} else {
|
||||
// Copy configurations to new map so we don't change current if LoadConfig fails
|
||||
@@ -134,7 +121,7 @@ func main() {
|
||||
for k, v := range currentConfigurations {
|
||||
newConfigurations[k] = v
|
||||
}
|
||||
newConfigurations[configMsg.providerName] = configMsg.configuration
|
||||
newConfigurations[configMsg.ProviderName] = configMsg.Configuration
|
||||
|
||||
newConfigurationRouter, err := LoadConfig(newConfigurations, globalConfiguration)
|
||||
if err == nil {
|
||||
@@ -300,7 +287,7 @@ func LoadConfig(configurations configs, globalConfiguration *GlobalConfiguration
|
||||
for _, configuration := range configurations {
|
||||
for frontendName, frontend := range configuration.Frontends {
|
||||
log.Debugf("Creating frontend %s", frontendName)
|
||||
fwd, _ := forward.New(forward.Logger(oxyLogger))
|
||||
fwd, _ := forward.New(forward.Logger(oxyLogger), forward.PassHostHeader(frontend.PassHostHeader))
|
||||
newRoute := router.NewRoute().Name(frontendName)
|
||||
for routeName, route := range frontend.Routes {
|
||||
log.Debugf("Creating route %s %s:%s", routeName, route.Rule, route.Value)
|
||||
@@ -314,12 +301,12 @@ func LoadConfig(configurations configs, globalConfiguration *GlobalConfiguration
|
||||
if configuration.Backends[frontend.Backend] == nil {
|
||||
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 {
|
||||
configuration.Backends[frontend.Backend].LoadBalancer = &LoadBalancer{Method: "wrr"}
|
||||
configuration.Backends[frontend.Backend].LoadBalancer = &types.LoadBalancer{Method: "wrr"}
|
||||
}
|
||||
switch lbMethod {
|
||||
case drr:
|
||||
case types.Drr:
|
||||
log.Infof("Creating load-balancer drr")
|
||||
rebalancer, _ := roundrobin.NewRebalancer(rr, roundrobin.RebalancerLogger(oxyLogger))
|
||||
lb = rebalancer
|
||||
@@ -331,7 +318,7 @@ func LoadConfig(configurations configs, globalConfiguration *GlobalConfiguration
|
||||
log.Infof("Creating server %s %s", serverName, url.String())
|
||||
rebalancer.UpsertServer(url, roundrobin.Weight(server.Weight))
|
||||
}
|
||||
case wrr:
|
||||
case types.Wrr:
|
||||
log.Infof("Creating load-balancer wrr")
|
||||
lb = middlewares.NewWebsocketUpgrader(rr)
|
||||
for serverName, server := range configuration.Backends[frontend.Backend].Servers {
|
||||
|
@@ -356,6 +356,7 @@
|
||||
# value = "test.localhost"
|
||||
# [frontends.frontend2]
|
||||
# backend = "backend1"
|
||||
# passHostHeader = true
|
||||
# [frontends.frontend2.routes.test_2]
|
||||
# rule = "Path"
|
||||
# value = "/test"
|
||||
|
84
types/types.go
Normal file
84
types/types.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Backend holds backend configuration.
|
||||
type Backend struct {
|
||||
Servers map[string]Server `json:"servers,omitempty"`
|
||||
CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty"`
|
||||
LoadBalancer *LoadBalancer `json:"loadBalancer,omitempty"`
|
||||
}
|
||||
|
||||
// LoadBalancer holds load balancing configuration.
|
||||
type LoadBalancer struct {
|
||||
Method string `json:"method,omitempty"`
|
||||
}
|
||||
|
||||
// CircuitBreaker holds circuit breaker configuration.
|
||||
type CircuitBreaker struct {
|
||||
Expression string `json:"expression,omitempty"`
|
||||
}
|
||||
|
||||
// Server holds server configuration.
|
||||
type Server struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
Weight int `json:"weight,omitempty"`
|
||||
}
|
||||
|
||||
// Route holds route configuration.
|
||||
type Route struct {
|
||||
Rule string `json:"rule,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// Frontend holds frontend configuration.
|
||||
type Frontend struct {
|
||||
Backend string `json:"backend,omitempty"`
|
||||
Routes map[string]Route `json:"routes,omitempty"`
|
||||
PassHostHeader bool `json:"passHostHeader,omitempty"`
|
||||
}
|
||||
|
||||
// LoadBalancerMethod holds the method of load balancing to use.
|
||||
type LoadBalancerMethod uint8
|
||||
|
||||
const (
|
||||
// Wrr (default) = Weighted Round Robin
|
||||
Wrr LoadBalancerMethod = iota
|
||||
// Drr = Dynamic Round Robin
|
||||
Drr
|
||||
)
|
||||
|
||||
var loadBalancerMethodNames = []string{
|
||||
"Wrr",
|
||||
"Drr",
|
||||
}
|
||||
|
||||
// NewLoadBalancerMethod create a new LoadBalancerMethod from a given LoadBalancer.
|
||||
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
|
||||
}
|
||||
|
||||
// ErrInvalidLoadBalancerMethod is thrown when the specified load balancing method is invalid.
|
||||
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"`
|
||||
}
|
||||
|
||||
// ConfigMessage hold configuration information exchanged between parts of traefik.
|
||||
type ConfigMessage struct {
|
||||
ProviderName string
|
||||
Configuration *Configuration
|
||||
}
|
12
vendor/github.com/docker/docker/autogen/dockerversion/dockerversion.go
generated
vendored
12
vendor/github.com/docker/docker/autogen/dockerversion/dockerversion.go
generated
vendored
@@ -1,12 +0,0 @@
|
||||
// AUTOGENERATED FILE; see ./hack/make/.go-autogen
|
||||
package dockerversion
|
||||
|
||||
var (
|
||||
GITCOMMIT string = ""
|
||||
VERSION string = ""
|
||||
BUILDTIME string = ""
|
||||
|
||||
IAMSTATIC string = "true"
|
||||
INITSHA1 string = ""
|
||||
INITPATH string = ""
|
||||
)
|
6
version.go
Normal file
6
version.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package main
|
||||
|
||||
var (
|
||||
Version = ""
|
||||
BuildDate = ""
|
||||
)
|
17
web.go
17
web.go
@@ -8,7 +8,10 @@ import (
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/elazarl/go-bindata-assetfs"
|
||||
"github.com/emilevauge/traefik/autogen"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/unrolled/render"
|
||||
)
|
||||
|
||||
type WebProvider struct {
|
||||
@@ -16,7 +19,13 @@ type WebProvider struct {
|
||||
CertFile, KeyFile string
|
||||
}
|
||||
|
||||
func (provider *WebProvider) Provide(configurationChan chan<- configMessage) error {
|
||||
var (
|
||||
templatesRenderer = render.New(render.Options{
|
||||
Directory: "nowhere",
|
||||
})
|
||||
)
|
||||
|
||||
func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
systemRouter := mux.NewRouter()
|
||||
|
||||
// health route
|
||||
@@ -34,11 +43,11 @@ func (provider *WebProvider) Provide(configurationChan chan<- configMessage) err
|
||||
return
|
||||
}
|
||||
|
||||
configuration := new(Configuration)
|
||||
configuration := new(types.Configuration)
|
||||
body, _ := ioutil.ReadAll(request.Body)
|
||||
err := json.Unmarshal(body, configuration)
|
||||
if err == nil {
|
||||
configurationChan <- configMessage{"web", configuration}
|
||||
configurationChan <- types.ConfigMessage{"web", configuration}
|
||||
getConfigHandler(response, request)
|
||||
} else {
|
||||
log.Errorf("Error parsing configuration %+v", err)
|
||||
@@ -58,7 +67,7 @@ func (provider *WebProvider) Provide(configurationChan chan<- configMessage) err
|
||||
systemRouter.Methods("GET").Path("/").HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||
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() {
|
||||
if len(provider.CertFile) > 0 && len(provider.KeyFile) > 0 {
|
||||
|
14
zk.go
14
zk.go
@@ -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)
|
||||
}
|
Reference in New Issue
Block a user