Compare commits

...

13 Commits

Author SHA1 Message Date
Emile Vauge
85bbd49798 Merge pull request #96 from vdemeester/validate-golint
Add validate-golint target and script …
2015-11-08 20:05:01 +01:00
Vincent Demeester
40391c57c2 Add validate-golint target and script …
… and *lint* the latest piece of code.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-11-06 18:11:57 +01:00
Emile Vauge
7607eb173b Merge pull request #94 from vdemeester/fix-labels-docker
Fix docker labels (frontend.*) non-presence
2015-11-05 15:50:30 +01:00
Vincent Demeester
15318c4631 Fix docker labels (frontend.*)
Using Docker provider, you can specify `traefik.frontend.rule` and
`traefik.frontend.value` labels. If they are not both provided, there is
a default behavior. On the current master, if they are not defined, the
container is filtered (and thus the default behavior is broken).

Fixes that.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-11-05 15:14:25 +01:00
Emile Vauge
7be566ef7c Merge pull request #93 from vdemeester/integration-test-simple
Updates and additions on some integration tests
2015-11-04 09:12:41 +01:00
Vincent Demeester
3c9ec55f0a Updates and additions on some integration tests
- Use defer to kill traefik process (to fix the still running traefik
  binaries if the given tests is failing before the kill)
- Add TestWithWebConfig
- Add *.test to gitignore to ignore the test binaries generated by go.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-11-03 23:06:31 +01:00
Emile Vauge
5ee6981410 Merge pull request #92 from vdemeester/linting-some-packages
Linting some packages
2015-11-02 22:47:45 +01:00
Vincent Demeester
c32f82baee Linting types package
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-11-02 21:15:10 +01:00
Vincent Demeester
89bb1ae835 Linting provider package
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-11-02 21:15:03 +01:00
Vincent Demeester
9387235a04 Linting middlewares package
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-11-02 21:14:54 +01:00
Emile Vauge
7766d0ddaa Merge pull request #88 from vdemeester/refactor-package
Refactor traefik with package
2015-11-02 21:08:29 +01:00
Vincent Demeester
cdade5f649 Rename NameProvider to Name
Because golint is gonna cry at some point otherwise.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-11-02 19:48:34 +01:00
Vincent Demeester
de0a57ec76 Refactor traefik with package
Split a bit traefik into package. The idea behind this refactor is to
start move inter-dependencies away and do some DRY or SRP.

- Adds a `provider` package, with providers except `web.go`
- Adds a `types` package with common struct.
- Move `gen.go` to an `autogen` package

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-11-02 18:35:55 +01:00
42 changed files with 697 additions and 365 deletions

2
.gitignore vendored
View File

@@ -5,5 +5,5 @@ log
*.iml
traefik
traefik.toml
*.test
vendor/

View File

@@ -3,7 +3,8 @@
TRAEFIK_ENVS := \
-e OS_ARCH_ARG \
-e OS_PLATFORM_ARG \
-e TESTFLAGS
-e TESTFLAGS \
-e CIRCLECI
BIND_DIR := "dist"
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/emilevauge/traefik/$(BIND_DIR)"
@@ -39,7 +40,7 @@ test-integration: build
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-integration
validate: build
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint
validate-gofmt: build
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt
@@ -47,6 +48,9 @@ validate-gofmt: build
validate-govet: build
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-govet
validate-golint: build
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-golint
build: dist
docker build -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .

View File

@@ -4,40 +4,37 @@ Copyright
package main
import (
"net/http"
log "github.com/Sirupsen/logrus"
"github.com/gorilla/mux"
"github.com/mailgun/oxy/utils"
"net/http"
)
// OxyLogger implements oxy Logger interface with logrus.
type OxyLogger struct {
}
// Infof logs specified string as Debug level in logrus.
func (oxylogger *OxyLogger) Infof(format string, args ...interface{}) {
log.Debugf(format, args...)
}
// Warningf logs specified string as Warning level in logrus.
func (oxylogger *OxyLogger) Warningf(format string, args ...interface{}) {
log.Warningf(format, args...)
}
// Errorf logs specified string as Error level in logrus.
func (oxylogger *OxyLogger) Errorf(format string, args ...interface{}) {
log.Errorf(format, args...)
}
type ErrorHandler struct {
}
func (e *ErrorHandler) ServeHTTP(w http.ResponseWriter, req *http.Request, err error) {
log.Error("server error ", err.Error())
utils.DefaultHandler.ServeHTTP(w, req, err)
}
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
//templatesRenderer.HTML(w, http.StatusNotFound, "notFound", nil)
}
// LoadDefaultConfig returns a default gorrilla.mux router from the specified configuration.
func LoadDefaultConfig(globalConfiguration *GlobalConfiguration) *mux.Router {
router := mux.NewRouter()
router.NotFoundHandler = http.HandlerFunc(notFoundHandler)

0
autogen/.placeholder Normal file
View File

View File

@@ -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)
}

View File

@@ -4,6 +4,7 @@ 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/...
RUN go get github.com/golang/lint/golint
# Which docker version to test on
ENV DOCKER_VERSION 1.6.2

View File

@@ -1,11 +1,16 @@
package main
import (
"errors"
"strings"
fmtlog "log"
"time"
"github.com/BurntSushi/toml"
"github.com/emilevauge/traefik/provider"
"github.com/emilevauge/traefik/types"
)
// GlobalConfiguration holds global configuration (with providers, etc.).
// It's populated from the traefik configuration file passed as an argument to the binary.
type GlobalConfiguration struct {
Port string
GraceTimeOut int64
@@ -14,16 +19,17 @@ 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
}
// NewGlobalConfiguration returns a GlobalConfiguration with default values.
func NewGlobalConfiguration() *GlobalConfiguration {
globalConfiguration := new(GlobalConfiguration)
// default values
@@ -35,79 +41,13 @@ 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 {
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
}
}
// LoadFileConfig returns a GlobalConfiguration from reading the specified file (a toml file).
func LoadFileConfig(file string) *GlobalConfiguration {
configuration := NewGlobalConfiguration()
if _, err := toml.DecodeFile(file, configuration); err != nil {
fmtlog.Fatalf("Error reading file: %s", err)
}
return wrr, ErrInvalidLoadBalancerMethod
return configuration
}
var ErrInvalidLoadBalancerMethod = errors.New("Invalid method, using default")
type configMessage struct {
providerName string
configuration *Configuration
}
type configs map[string]*Configuration
type configs map[string]*types.Configuration

View File

@@ -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)
}

14
etcd.go
View File

@@ -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)
}

View File

@@ -1 +0,0 @@
package main

View File

@@ -2,8 +2,8 @@
Copyright
*/
//go:generate rm -vf gen.go
//go:generate go-bindata -o gen.go static/... templates/...
//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

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -1,15 +1,140 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"time"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/namesgenerator"
"github.com/fsouza/go-dockerclient"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
var (
// Label added to started container to identify them as part of the integration test
TestLabel = "io.traefik.test"
// Images to have or pull before the build in order to make it work
// FIXME handle this offline but loading them before build
RequiredImages = map[string]string{
"swarm": "1.0.0",
"nginx": "1",
}
)
// Docker test suites
type DockerSuite struct {
BaseSuite
client *docker.Client
}
func (s *DockerSuite) startContainer(c *check.C, image string, args ...string) string {
return s.startContainerWithConfig(c, docker.CreateContainerOptions{
Config: &docker.Config{
Image: image,
Cmd: args,
},
})
}
func (s *DockerSuite) startContainerWithLabels(c *check.C, image string, labels map[string]string, args ...string) string {
return s.startContainerWithConfig(c, docker.CreateContainerOptions{
Config: &docker.Config{
Image: image,
Cmd: args,
Labels: labels,
},
})
}
func (s *DockerSuite) startContainerWithConfig(c *check.C, config docker.CreateContainerOptions) string {
if config.Name == "" {
config.Name = namesgenerator.GetRandomName(10)
}
if config.Config.Labels == nil {
config.Config.Labels = map[string]string{}
}
config.Config.Labels[TestLabel] = "true"
container, err := s.client.CreateContainer(config)
c.Assert(err, checker.IsNil, check.Commentf("Error creating a container using config %v", config))
err = s.client.StartContainer(container.ID, &docker.HostConfig{})
c.Assert(err, checker.IsNil, check.Commentf("Error starting container %v", container))
return container.Name
}
func (s *DockerSuite) SetUpSuite(c *check.C) {
dockerHost := os.Getenv("DOCKER_HOST")
if dockerHost == "" {
// FIXME Handle windows -- see if dockerClient already handle that or not
dockerHost = fmt.Sprintf("unix://%s", opts.DefaultUnixSocket)
}
// Make sure we can speak to docker
dockerClient, err := docker.NewClient(dockerHost)
c.Assert(err, checker.IsNil, check.Commentf("Error connecting to docker daemon"))
s.client = dockerClient
c.Assert(s.client.Ping(), checker.IsNil)
// Pull required images
for repository, tag := range RequiredImages {
image := fmt.Sprintf("%s:%s", repository, tag)
_, err := s.client.InspectImage(image)
if err != nil {
if err != docker.ErrNoSuchImage {
c.Fatalf("Error while inspect image %s", image)
}
err = s.client.PullImage(docker.PullImageOptions{
Repository: repository,
Tag: tag,
}, docker.AuthConfiguration{})
c.Assert(err, checker.IsNil, check.Commentf("Error while pulling image %s", image))
}
}
}
func (s *DockerSuite) cleanContainers(c *check.C) {
// Clean the mess, a.k.a. the running containers with the right label
containerList, err := s.client.ListContainers(docker.ListContainersOptions{
Filters: map[string][]string{
"label": {fmt.Sprintf("%s=true", TestLabel)},
},
})
c.Assert(err, checker.IsNil, check.Commentf("Error listing containers started by traefik"))
for _, container := range containerList {
err = s.client.KillContainer(docker.KillContainerOptions{
ID: container.ID,
})
c.Assert(err, checker.IsNil, check.Commentf("Error killing container %v", container))
if os.Getenv("CIRCLECI") == "" {
// On circleci, we won't delete them — it errors out for now >_<
err = s.client.RemoveContainer(docker.RemoveContainerOptions{
ID: container.ID,
RemoveVolumes: true,
})
c.Assert(err, checker.IsNil, check.Commentf("Error removing container %v", container))
}
}
}
func (s *DockerSuite) TearDownTest(c *check.C) {
s.cleanContainers(c)
}
func (s *DockerSuite) TearDownSuite(c *check.C) {
// Call cleanContainers, just in case (?)
// s.cleanContainers(c)
}
func (s *DockerSuite) TestSimpleConfiguration(c *check.C) {
file := s.adaptFileForHost(c, "fixtures/docker/simple.toml")
defer os.Remove(file)
@@ -17,15 +142,110 @@ func (s *DockerSuite) TestSimpleConfiguration(c *check.C) {
cmd := exec.Command(traefikBinary, file)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
time.Sleep(500 * time.Millisecond)
// TODO validate : run on 80
resp, err := http.Get("http://127.0.0.1/")
c.Assert(err, checker.IsNil)
// Expected a 404 as we did not comfigure anything
c.Assert(resp.StatusCode, checker.Equals, 404)
}
func (s *DockerSuite) TestDefaultDockerContainers(c *check.C) {
file := s.adaptFileForHost(c, "fixtures/docker/simple.toml")
defer os.Remove(file)
name := s.startContainer(c, "swarm:1.0.0", "manage", "token://blablabla")
// Start traefik
cmd := exec.Command(traefikBinary, file)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// FIXME Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
time.Sleep(1500 * time.Millisecond)
client := &http.Client{}
req, err := http.NewRequest("GET", "http://127.0.0.1/version", nil)
c.Assert(err, checker.IsNil)
req.Host = fmt.Sprintf("%s.docker.localhost", name)
resp, err := client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 200)
body, err := ioutil.ReadAll(resp.Body)
c.Assert(err, checker.IsNil)
var version map[string]interface{}
c.Assert(json.Unmarshal(body, &version), checker.IsNil)
c.Assert(version["Version"], checker.Equals, "swarm/1.0.0")
}
func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) {
file := s.adaptFileForHost(c, "fixtures/docker/simple.toml")
defer os.Remove(file)
// Start a container with some labels
labels := map[string]string{
"traefik.frontend.rule": "Host",
"traefik.frontend.value": "my.super.host",
}
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla")
// Start traefik
cmd := exec.Command(traefikBinary, file)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// FIXME Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
time.Sleep(1500 * time.Millisecond)
client := &http.Client{}
req, err := http.NewRequest("GET", "http://127.0.0.1/version", nil)
c.Assert(err, checker.IsNil)
req.Host = fmt.Sprintf("my.super.host")
resp, err := client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 200)
body, err := ioutil.ReadAll(resp.Body)
c.Assert(err, checker.IsNil)
var version map[string]interface{}
c.Assert(json.Unmarshal(body, &version), checker.IsNil)
c.Assert(version["Version"], checker.Equals, "swarm/1.0.0")
}
func (s *DockerSuite) TestDockerContainersWithOneMissingLabels(c *check.C) {
file := s.adaptFileForHost(c, "fixtures/docker/simple.toml")
defer os.Remove(file)
// Start a container with some labels
labels := map[string]string{
"traefik.frontend.value": "my.super.host",
}
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla")
// Start traefik
cmd := exec.Command(traefikBinary, file)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// FIXME Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
time.Sleep(1500 * time.Millisecond)
client := &http.Client{}
req, err := http.NewRequest("GET", "http://127.0.0.1/version", nil)
c.Assert(err, checker.IsNil)
req.Host = fmt.Sprintf("my.super.host")
resp, err := client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 404)
killErr := cmd.Process.Kill()
c.Assert(killErr, checker.IsNil)
}

View File

@@ -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)
}

View File

@@ -0,0 +1,5 @@
logLevel = "DEBUG"
[web]
address = ":8080"

View File

@@ -32,9 +32,6 @@ func init() {
var traefikBinary = "../dist/traefik"
// SimpleSuite
type SimpleSuite struct{ BaseSuite }
// File test suites
type FileSuite struct{ BaseSuite }
@@ -44,17 +41,6 @@ func (s *FileSuite) SetUpSuite(c *check.C) {
s.composeProject.Up()
}
// Docker test suites
type DockerSuite struct{ BaseSuite }
func (s *DockerSuite) SetUpSuite(c *check.C) {
// Make sure we can speak to docker
}
func (s *DockerSuite) TearDownSuite(c *check.C) {
// Clean the mess
}
// Consul test suites (using libcompose)
type ConsulSuite struct{ BaseSuite }

View File

@@ -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)
}

View File

@@ -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}

View File

@@ -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()
}

View File

@@ -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}
}

View File

@@ -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,

View File

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

19
provider/boltdb.go Normal file
View 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
View 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)
}

View File

@@ -1,77 +1,83 @@
package main
package provider
import (
"bytes"
"errors"
"fmt"
"strconv"
"strings"
"text/template"
"time"
"fmt"
"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"
)
type DockerProvider struct {
// Docker holds configurations of the Docker provider.
type Docker 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 {
// 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
} 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}
}
}
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 <- 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 *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *Configuration {
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 {
@@ -118,7 +124,7 @@ func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *C
return strings.Replace(s3, s1, s2, -1)
},
}
configuration := new(Configuration)
configuration := new(types.Configuration)
containerList, _ := dockerClient.ListContainers(docker.ListContainersOptions{})
containersInspected := []docker.Container{}
frontends := map[string][]docker.Container{}
@@ -145,7 +151,8 @@ func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *C
return false
}
if _, err := provider.getLabels(container, []string{"traefik.frontend.rule", "traefik.frontend.value"}); err != nil {
labels, err := provider.getLabels(container, []string{"traefik.frontend.rule", "traefik.frontend.value"})
if len(labels) != 0 && err != nil {
log.Debugf("Filtering bad labeled container %s", container.Name)
return false
}
@@ -174,7 +181,7 @@ func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *C
return nil
}
} else {
buf, err := Asset("templates/docker.tmpl")
buf, err := autogen.Asset("templates/docker.tmpl")
if err != nil {
log.Error("Error reading file", err)
}
@@ -199,17 +206,17 @@ func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *C
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
frontendName := fmt.Sprintf("%s-%s", provider.GetFrontendRule(container), provider.GetFrontendValue(container))
return strings.Replace(frontendName, ".", "-", -1)
}
func (provider *DockerProvider) getEscapedName(name string) string {
func (provider *Docker) getEscapedName(name string) string {
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 {
if key == label {
return value, nil
@@ -218,26 +225,34 @@ func (provider *DockerProvider) getLabel(container docker.Container, label strin
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) {
var globalErr error
foundLabels := map[string]string{}
for _, label := range labels {
if foundLabel, err := provider.getLabel(container, label); err != nil {
return nil, errors.New("Label not found: " + label)
} else {
foundLabels[label] = foundLabel
foundLabel, err := provider.getLabel(container, label)
// Error out only if one of them is defined.
if err != nil {
globalErr = errors.New("Label not found: " + label)
continue
}
foundLabels[label] = foundLabel
}
return foundLabels, nil
return foundLabels, globalErr
}
func (provider *DockerProvider) GetFrontendValue(container docker.Container) string {
// 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
}
func (provider *DockerProvider) GetFrontendRule(container docker.Container) string {
// 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
}

19
provider/etcd.go Normal file
View 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)
}

View File

@@ -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
View File

@@ -0,0 +1 @@
package provider

View File

@@ -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()
@@ -109,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
}{
@@ -167,7 +172,7 @@ func (provider *KvProvider) loadConfig() *Configuration {
return nil
}
} else {
buf, err := Asset("templates/kv.tmpl")
buf, err := autogen.Asset("templates/kv.tmpl")
if err != nil {
log.Error("Error reading file", err)
}

View File

@@ -1,28 +1,33 @@
package main
package provider
import (
"bytes"
"errors"
"strconv"
"strings"
"text/template"
"errors"
"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"
)
type MarathonProvider struct {
// Marathon holds configuration of the Marathon provider.
type Marathon struct {
Watch bool
Endpoint string
marathonClient marathon.Marathon
Domain string
Filename string
NetworkInterface string
marathonClient marathon.Marathon
}
func (provider *MarathonProvider) Provide(configurationChan chan<- configMessage) error {
// 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
@@ -43,7 +48,7 @@ func (provider *MarathonProvider) Provide(configurationChan chan<- configMessage
log.Debug("Marathon event receveived", event)
configuration := provider.loadMarathonConfig()
if configuration != nil {
configurationChan <- configMessage{"marathon", configuration}
configurationChan <- types.ConfigMessage{"marathon", configuration}
}
}
}()
@@ -51,11 +56,11 @@ func (provider *MarathonProvider) Provide(configurationChan chan<- configMessage
}
configuration := provider.loadMarathonConfig()
configurationChan <- configMessage{"marathon", configuration}
configurationChan <- types.ConfigMessage{"marathon", configuration}
return nil
}
func (provider *MarathonProvider) loadMarathonConfig() *Configuration {
func (provider *Marathon) loadMarathonConfig() *types.Configuration {
var MarathonFuncMap = template.FuncMap{
"getPort": func(task marathon.Task) string {
for _, port := range task.Ports {
@@ -103,7 +108,7 @@ func (provider *MarathonProvider) loadMarathonConfig() *Configuration {
"getFrontendValue": provider.GetFrontendValue,
"getFrontendRule": provider.GetFrontendRule,
}
configuration := new(Configuration)
configuration := new(types.Configuration)
applications, err := provider.marathonClient.Applications(nil)
if err != nil {
@@ -187,7 +192,7 @@ func (provider *MarathonProvider) loadMarathonConfig() *Configuration {
return nil
}
} else {
buf, err := Asset("templates/marathon.tmpl")
buf, err := autogen.Asset("templates/marathon.tmpl")
if err != nil {
log.Error("Error reading file", err)
}
@@ -223,7 +228,7 @@ func getApplication(task marathon.Task, apps []marathon.Application) (marathon.A
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 {
if key == label {
return value, nil
@@ -232,18 +237,22 @@ func (provider *MarathonProvider) getLabel(application marathon.Application, lab
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)
}
func (provider *MarathonProvider) GetFrontendValue(application marathon.Application) string {
// 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
}
func (provider *MarathonProvider) GetFrontendRule(application marathon.Application) string {
// 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
}

10
provider/provider.go Normal file
View 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
View 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)
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

31
script/validate-golint Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/bash
source "$(dirname "$BASH_SOURCE")/.validate"
IFS=$'\n'
files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/\|autogen' || true) )
unset IFS
errors=()
for f in "${files[@]}"; do
# we use "git show" here to validate that what's committed passes go vet
failedLint=$(golint "$f")
if [ "$failedLint" ]; then
errors+=( "$failedLint" )
fi
done
if [ ${#errors[@]} -eq 0 ]; then
echo 'Congratulations! All Go source files have been linted.'
else
{
echo "Errors from golint:"
for err in "${errors[@]}"; do
echo "$err"
done
echo
echo 'Please fix the above errors. You can test via "golint" and commit the result.'
echo
} >&2
false
fi

View File

@@ -2,21 +2,23 @@ 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"
@@ -24,7 +26,6 @@ import (
"github.com/mailgun/oxy/roundrobin"
"github.com/thoas/stats"
"gopkg.in/alecthomas/kingpin.v2"
"runtime"
)
var (
@@ -42,15 +43,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
@@ -84,22 +85,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]
}
}()
}
@@ -109,9 +110,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
@@ -119,7 +120,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 {
@@ -264,20 +265,20 @@ func prepareServer(router *mux.Router, globalConfiguration *GlobalConfiguration,
Handler: negroni,
TLSConfig: tlsConfig,
}), nil
} else {
server, err := oldServer.HijackListener(&http.Server{
Addr: globalConfiguration.Port,
Handler: negroni,
}, tlsConfig)
if err != nil {
log.Fatalf("Error hijacking server %s", err)
return nil, err
} else {
return server, nil
}
}
server, err := oldServer.HijackListener(&http.Server{
Addr: globalConfiguration.Port,
Handler: negroni,
}, tlsConfig)
if err != nil {
log.Fatalf("Error hijacking server %s", err)
return nil, err
}
return server, nil
}
// LoadConfig returns a new gorrilla.mux Route from the specified global configuration and the dynamic
// provider configurations.
func LoadConfig(configurations configs, globalConfiguration *GlobalConfiguration) (*mux.Router, error) {
router := mux.NewRouter()
router.NotFoundHandler = http.HandlerFunc(notFoundHandler)
@@ -299,12 +300,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
@@ -316,7 +317,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 {
@@ -344,13 +345,15 @@ func LoadConfig(configurations configs, globalConfiguration *GlobalConfiguration
newRoute.Handler(backends[frontend.Backend])
err := newRoute.GetError()
if err != nil {
log.Error("Error building route: %s", err)
log.Errorf("Error building route: %s", err)
}
}
}
return router, nil
}
// Invoke calls the specified method with the specified arguments on the specified interface.
// It uses the go(lang) reflect package.
func Invoke(any interface{}, name string, args ...interface{}) []reflect.Value {
inputs := make([]reflect.Value, len(args))
for i := range args {
@@ -358,11 +361,3 @@ func Invoke(any interface{}, name string, args ...interface{}) []reflect.Value {
}
return reflect.ValueOf(any).MethodByName(name).Call(inputs)
}
func LoadFileConfig(file string) *GlobalConfiguration {
configuration := NewGlobalConfiguration()
if _, err := toml.DecodeFile(file, configuration); err != nil {
fmtlog.Fatalf("Error reading file: %s", err)
}
return configuration
}

84
types/types.go Normal file
View 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
}

View File

@@ -1,6 +1,8 @@
package main
var (
Version = ""
// Version holds the current version of traefik.
Version = ""
// BuildDate holds the build date of traefik.
BuildDate = ""
)

14
web.go
View File

@@ -8,10 +8,14 @@ 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"
)
// WebProvider is a provider.Provider implementation that provides the UI.
// FIXME to be handled another way.
type WebProvider struct {
Address string
CertFile, KeyFile string
@@ -23,7 +27,9 @@ var (
})
)
func (provider *WebProvider) Provide(configurationChan chan<- configMessage) error {
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage) error {
systemRouter := mux.NewRouter()
// health route
@@ -41,11 +47,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)
@@ -65,7 +71,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
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)
}