Compare commits

...

18 Commits

Author SHA1 Message Date
Vincent Demeester
3a08655b06 Merge pull request #188 from emilevauge/add-marathon-tls-client-config
Add Marathon TLS client config
2016-02-18 14:23:38 +01:00
emile
9a9c8e5709 Add Marathon TLS client config
Signed-off-by: emile <emile@vauge.com>
2016-02-18 12:42:59 +00:00
Vincent Demeester
c7d34b54aa Merge pull request #205 from emilevauge/bump-go-1.6
Bump go 1.6
2016-02-18 11:07:18 +01:00
emile
8d860c84c8 Add HTTP2 support
Signed-off-by: emile <emile@vauge.com>
2016-02-18 10:31:56 +01:00
emile
1dc086730e Bump golang 1.6 2016-02-18 10:30:41 +01:00
Emile Vauge
5d79e56d30 Merge pull request #193 from dontrebootme/fix192
fix mantl url typo
2016-02-17 23:05:26 +01:00
Vincent Demeester
6e7677de79 Merge pull request #203 from goguardian/alias
Implement `/traefik/alias` for KV stores.
2016-02-17 09:56:43 +01:00
Patrick O'Connor
fab6b8be3c fix mantl url typo 2016-02-16 12:30:33 -08:00
Advait Shinde
f1c1eed437 Fix typo in documentation 2016-02-16 11:55:42 -05:00
Advait Shinde
348ab794c9 Add documentation for /traefik/alias. 2016-02-15 18:46:03 -05:00
Advait Shinde
aacedcc4b3 Implement /traefik/alias for KV stores. 2016-02-15 18:14:57 -05:00
Emile Vauge
786acc961a Merge pull request #200 from vdemeester/some-fixes
Do some build / test-integration fixes
2016-02-13 16:33:42 +01:00
Vincent Demeester
7adffdbd78 Run npm in quiet mode O:)
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-02-13 16:14:26 +01:00
Vincent Demeester
e3b519cdd8 Do some build / test-integration fixes
- target generate-webui depends on build-webui
- generate-webui will run only if the folder static does not exists
- create compose project before starting it >_<'', otherwise it does
  nothing :'D

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-02-13 16:04:08 +01:00
Vincent Demeester
e9c23195a0 Merge pull request #195 from emilevauge/fix-regression-marathon-backends
Fix regression on marathon backend
2016-02-12 15:31:59 +01:00
emile
c6c3af8099 Fix regression on marathon backend
Signed-off-by: emile <emile@vauge.com>
2016-02-12 15:03:28 +01:00
Emile Vauge
07c077cf94 Merge pull request #189 from vdemeester/fix-make-all
Fix make all
2016-02-10 14:10:26 +01:00
Vincent Demeester
4ac18f1989 Fix make all
Add build-ui as dependent target for all. Otherwise it does not build >_<

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-02-10 13:55:47 +01:00
14 changed files with 973 additions and 60 deletions

View File

@@ -24,13 +24,13 @@ print-%: ; @echo $*=$($*)
default: binary
all: build
all: generate-webui build
$(DOCKER_RUN_TRAEFIK) ./script/make.sh
binary: build-webui generate-webui build
binary: generate-webui build
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate binary
crossbinary: build-webui generate-webui build
crossbinary: generate-webui build
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate crossbinary
test: build
@@ -77,13 +77,15 @@ run-dev:
go build
./traefik
generate-webui:
mkdir -p static
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui gulp
echo 'For more informations show `webui/readme.md`' > $$PWD/static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md
generate-webui: build-webui
if [ ! -d "static" ]; then \
mkdir -p static; \
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui gulp; \
echo 'For more informations show `webui/readme.md`' > $$PWD/static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md; \
fi
lint:
$(foreach file,$(SRCS),golint $(file) || exit;)
fmt:
gofmt -s -l -w $(SRCS)
gofmt -s -l -w $(SRCS)

View File

@@ -27,6 +27,7 @@ It supports several backends ([Docker :whale:](https://www.docker.com/), [Mesos/
- SSL frontend support
- Clean AngularJS Web UI
- Websocket support
- HTTP/2 support
## Demo
@@ -80,7 +81,7 @@ Refer to the [benchmarks section](docs/index.md#benchmarks) in the documentation
These projects use Træfɪk internally. If your company uses Træfɪk, we would be glad to get your feedback :) Contact us on [![Join the chat at https://traefik.herokuapp.com](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://traefik.herokuapp.com)
- Project [Mantl](http://http://mantl.io/) from Cisco
- Project [Mantl](https://mantl.io/) from Cisco
![Web UI Providers](docs/img/mantl-logo.png)
> Mantl is a modern platform for rapidly deploying globally distributed services. A container orchestrator, docker, a network stack, something to pool your logs, something to monitor health, a sprinkle of service discovery and some automation.

View File

@@ -1,4 +1,4 @@
FROM golang:1.5.3
FROM golang:1.6
RUN go get github.com/Masterminds/glide
RUN go get github.com/mitchellh/gox

View File

@@ -15,6 +15,7 @@ ___
- [Etcd backend](#etcd)
- [Zookeeper backend](#zk)
- [Boltdb backend](#boltdb)
- [Atomic configuration changes](#atomicconfig)
- [Benchmarks](#benchmarks)
@@ -624,11 +625,11 @@ Træfɪk can be configured to use Marathon as a backend configuration:
#
endpoint = "http://127.0.0.1:8080"
# Network interface used to call Marathon web services
# Network interface used to call Marathon web services. Needed in case of multiple network interfaces.
# Optional
# Default: "eth0"
#
# networkInterface = "eth0"
networkInterface = "eth0"
# Enable watch Marathon changes
#
@@ -648,6 +649,21 @@ domain = "marathon.localhost"
# Optional
#
# filename = "marathon.tmpl"
# Enable Marathon basic authentication
#
# Optional
#
# [marathon.basic]
# httpBasicAuthUser = "foo"
# httpBasicPassword = "bar"
# TLS client configuration. https://golang.org/pkg/crypto/tls/#Config
#
# Optional
#
# [marathon.TLS]
# InsecureSkipVerify = true
```
Labels can be used on containers to override default behaviour:
@@ -946,6 +962,44 @@ Træfɪk can be configured to use BoltDB as a backend configuration:
# filename = "boltdb.tmpl"
```
## <a id="atomicconfig"></a> Atomic configuration changes
The [Etcd](https://github.com/coreos/etcd/issues/860) and [Consul](https://github.com/hashicorp/consul/issues/886) backends do not support updating multiple keys atomically. As a result, it may be possible for Træfɪk to read an intermediate configuration state despite judicious use of the `--providersThrottleDuration` flag. To solve this problem, Træfɪk supports a special key called `/traefik/alias`. If set, Træfɪk use the value as an alternative key prefix.
Given the key structure below, Træfɪk will use the `http://172.17.0.2:80` as its only backend (frontend keys have been omitted for brevity).
| Key | Value |
|-------------------------------------------------------------------------|-----------------------------|
| `/traefik/alias` | `/traefik_configurations/1` |
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
When an atomic configuration change is required, you may write a new configuration at an alternative prefix. Here, although the `/traefik_configurations/2/...` keys have been set, the old configuration is still active because the `/traefik/alias` key still points to `/traefik_configurations/1`:
| Key | Value |
|-------------------------------------------------------------------------|-----------------------------|
| `/traefik/alias` | `/traefik_configurations/1` |
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
Once the `/traefik/alias` key is updated, the new `/traefik_configurations/2` configuration becomes active atomically. Here, we have a 50% balance between the `http://172.17.0.3:80` and the `http://172.17.0.4:80` hosts while no traffic is sent to the `172.17.0.2:80` host:
| Key | Value |
|-------------------------------------------------------------------------|-----------------------------|
| `/traefik/alias` | `/traefik_configurations/2` |
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.3:80` |
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.4:80` |
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
Note that Træfɪk *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik` prefix. Further, if the `/traefik/alias` key is set, all other sibling keys with the `/traefik` prefix are ignored.
## <a id="benchmarks"></a> Benchmarks

View File

@@ -43,7 +43,7 @@ import:
- package: github.com/alecthomas/units
ref: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
- package: github.com/gambol99/go-marathon
ref: 8ce3f764250b2de3f2c627d12ca7dd21bd5e7f93
ref: ade11d1dc2884ee1f387078fc28509559b6235d1
- package: github.com/mailgun/predicate
ref: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
- package: github.com/thoas/stats
@@ -161,5 +161,7 @@ import:
- package: github.com/spf13/cobra
subpackages:
- /cobra
- package: github.com/google/go-querystring/query
- package: github.com/vulcand/vulcand/plugin/rewrite
- package: github.com/stretchr/testify/mock

View File

@@ -87,6 +87,9 @@ func (s *BaseSuite) createComposeProject(c *check.C, name string) {
c.Assert(err, checker.IsNil)
s.composeProject = composeProject
err = composeProject.Create()
c.Assert(err, checker.IsNil)
s.started = make(chan bool)
s.stopped = make(chan bool)
s.deleted = make(chan bool)
@@ -96,7 +99,8 @@ func (s *BaseSuite) createComposeProject(c *check.C, name string) {
composeProject.AddListener(s.listenChan)
composeProject.Start()
err = composeProject.Start()
c.Assert(err, checker.IsNil)
// Wait for compose to start
<-s.started

829
mocks/Marathon.go Normal file
View File

@@ -0,0 +1,829 @@
package mocks
import "github.com/gambol99/go-marathon"
import "github.com/stretchr/testify/mock"
import "net/url"
import "time"
// Marathon is a mock of marathon.Marathon
type Marathon struct {
mock.Mock
}
// ListApplications provides a mock function with given fields: _a0
func (_m *Marathon) ListApplications(_a0 url.Values) ([]string, error) {
ret := _m.Called(_a0)
var r0 []string
if rf, ok := ret.Get(0).(func(url.Values) []string); ok {
r0 = rf(_a0)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(url.Values) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ApplicationVersions provides a mock function with given fields: name
func (_m *Marathon) ApplicationVersions(name string) (*marathon.ApplicationVersions, error) {
ret := _m.Called(name)
var r0 *marathon.ApplicationVersions
if rf, ok := ret.Get(0).(func(string) *marathon.ApplicationVersions); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.ApplicationVersions)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// HasApplicationVersion provides a mock function with given fields: name, version
func (_m *Marathon) HasApplicationVersion(name string, version string) (bool, error) {
ret := _m.Called(name, version)
var r0 bool
if rf, ok := ret.Get(0).(func(string, string) bool); ok {
r0 = rf(name, version)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(string, string) error); ok {
r1 = rf(name, version)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SetApplicationVersion provides a mock function with given fields: name, version
func (_m *Marathon) SetApplicationVersion(name string, version *marathon.ApplicationVersion) (*marathon.DeploymentID, error) {
ret := _m.Called(name, version)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string, *marathon.ApplicationVersion) *marathon.DeploymentID); ok {
r0 = rf(name, version)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, *marathon.ApplicationVersion) error); ok {
r1 = rf(name, version)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ApplicationOK provides a mock function with given fields: name
func (_m *Marathon) ApplicationOK(name string) (bool, error) {
ret := _m.Called(name)
var r0 bool
if rf, ok := ret.Get(0).(func(string) bool); ok {
r0 = rf(name)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateApplication provides a mock function with given fields: application
func (_m *Marathon) CreateApplication(application *marathon.Application) (*marathon.Application, error) {
ret := _m.Called(application)
var r0 *marathon.Application
if rf, ok := ret.Get(0).(func(*marathon.Application) *marathon.Application); ok {
r0 = rf(application)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Application)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*marathon.Application) error); ok {
r1 = rf(application)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// DeleteApplication provides a mock function with given fields: name
func (_m *Marathon) DeleteApplication(name string) (*marathon.DeploymentID, error) {
ret := _m.Called(name)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string) *marathon.DeploymentID); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateApplication provides a mock function with given fields: application
func (_m *Marathon) UpdateApplication(application *marathon.Application) (*marathon.DeploymentID, error) {
ret := _m.Called(application)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(*marathon.Application) *marathon.DeploymentID); ok {
r0 = rf(application)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*marathon.Application) error); ok {
r1 = rf(application)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ApplicationDeployments provides a mock function with given fields: name
func (_m *Marathon) ApplicationDeployments(name string) ([]*marathon.DeploymentID, error) {
ret := _m.Called(name)
var r0 []*marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string) []*marathon.DeploymentID); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ScaleApplicationInstances provides a mock function with given fields: name, instances, force
func (_m *Marathon) ScaleApplicationInstances(name string, instances int, force bool) (*marathon.DeploymentID, error) {
ret := _m.Called(name, instances, force)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string, int, bool) *marathon.DeploymentID); ok {
r0 = rf(name, instances, force)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, int, bool) error); ok {
r1 = rf(name, instances, force)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RestartApplication provides a mock function with given fields: name, force
func (_m *Marathon) RestartApplication(name string, force bool) (*marathon.DeploymentID, error) {
ret := _m.Called(name, force)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string, bool) *marathon.DeploymentID); ok {
r0 = rf(name, force)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, bool) error); ok {
r1 = rf(name, force)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Applications provides a mock function with given fields: _a0
func (_m *Marathon) Applications(_a0 url.Values) (*marathon.Applications, error) {
ret := _m.Called(_a0)
var r0 *marathon.Applications
if rf, ok := ret.Get(0).(func(url.Values) *marathon.Applications); ok {
r0 = rf(_a0)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Applications)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(url.Values) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Application provides a mock function with given fields: name
func (_m *Marathon) Application(name string) (*marathon.Application, error) {
ret := _m.Called(name)
var r0 *marathon.Application
if rf, ok := ret.Get(0).(func(string) *marathon.Application); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Application)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// WaitOnApplication provides a mock function with given fields: name, timeout
func (_m *Marathon) WaitOnApplication(name string, timeout time.Duration) error {
ret := _m.Called(name, timeout)
var r0 error
if rf, ok := ret.Get(0).(func(string, time.Duration) error); ok {
r0 = rf(name, timeout)
} else {
r0 = ret.Error(0)
}
return r0
}
// Tasks provides a mock function with given fields: application
func (_m *Marathon) Tasks(application string) (*marathon.Tasks, error) {
ret := _m.Called(application)
var r0 *marathon.Tasks
if rf, ok := ret.Get(0).(func(string) *marathon.Tasks); ok {
r0 = rf(application)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Tasks)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(application)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AllTasks provides a mock function with given fields: opts
func (_m *Marathon) AllTasks(opts *marathon.AllTasksOpts) (*marathon.Tasks, error) {
ret := _m.Called(opts)
var r0 *marathon.Tasks
if rf, ok := ret.Get(0).(func(*marathon.AllTasksOpts) *marathon.Tasks); ok {
r0 = rf(opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Tasks)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*marathon.AllTasksOpts) error); ok {
r1 = rf(opts)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// TaskEndpoints provides a mock function with given fields: name, port, healthCheck
func (_m *Marathon) TaskEndpoints(name string, port int, healthCheck bool) ([]string, error) {
ret := _m.Called(name, port, healthCheck)
var r0 []string
if rf, ok := ret.Get(0).(func(string, int, bool) []string); ok {
r0 = rf(name, port, healthCheck)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, int, bool) error); ok {
r1 = rf(name, port, healthCheck)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// KillApplicationTasks provides a mock function with given fields: applicationID, opts
func (_m *Marathon) KillApplicationTasks(applicationID string, opts *marathon.KillApplicationTasksOpts) (*marathon.Tasks, error) {
ret := _m.Called(applicationID, opts)
var r0 *marathon.Tasks
if rf, ok := ret.Get(0).(func(string, *marathon.KillApplicationTasksOpts) *marathon.Tasks); ok {
r0 = rf(applicationID, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Tasks)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, *marathon.KillApplicationTasksOpts) error); ok {
r1 = rf(applicationID, opts)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// KillTask provides a mock function with given fields: taskID, opts
func (_m *Marathon) KillTask(taskID string, opts *marathon.KillTaskOpts) (*marathon.Task, error) {
ret := _m.Called(taskID, opts)
var r0 *marathon.Task
if rf, ok := ret.Get(0).(func(string, *marathon.KillTaskOpts) *marathon.Task); ok {
r0 = rf(taskID, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Task)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, *marathon.KillTaskOpts) error); ok {
r1 = rf(taskID, opts)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// KillTasks provides a mock function with given fields: taskIDs, opts
func (_m *Marathon) KillTasks(taskIDs []string, opts *marathon.KillTaskOpts) error {
ret := _m.Called(taskIDs, opts)
var r0 error
if rf, ok := ret.Get(0).(func([]string, *marathon.KillTaskOpts) error); ok {
r0 = rf(taskIDs, opts)
} else {
r0 = ret.Error(0)
}
return r0
}
// Groups provides a mock function with given fields:
func (_m *Marathon) Groups() (*marathon.Groups, error) {
ret := _m.Called()
var r0 *marathon.Groups
if rf, ok := ret.Get(0).(func() *marathon.Groups); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Groups)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Group provides a mock function with given fields: name
func (_m *Marathon) Group(name string) (*marathon.Group, error) {
ret := _m.Called(name)
var r0 *marathon.Group
if rf, ok := ret.Get(0).(func(string) *marathon.Group); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Group)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateGroup provides a mock function with given fields: group
func (_m *Marathon) CreateGroup(group *marathon.Group) error {
ret := _m.Called(group)
var r0 error
if rf, ok := ret.Get(0).(func(*marathon.Group) error); ok {
r0 = rf(group)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteGroup provides a mock function with given fields: name
func (_m *Marathon) DeleteGroup(name string) (*marathon.DeploymentID, error) {
ret := _m.Called(name)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string) *marathon.DeploymentID); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateGroup provides a mock function with given fields: id, group
func (_m *Marathon) UpdateGroup(id string, group *marathon.Group) (*marathon.DeploymentID, error) {
ret := _m.Called(id, group)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string, *marathon.Group) *marathon.DeploymentID); ok {
r0 = rf(id, group)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, *marathon.Group) error); ok {
r1 = rf(id, group)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// HasGroup provides a mock function with given fields: name
func (_m *Marathon) HasGroup(name string) (bool, error) {
ret := _m.Called(name)
var r0 bool
if rf, ok := ret.Get(0).(func(string) bool); ok {
r0 = rf(name)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// WaitOnGroup provides a mock function with given fields: name, timeout
func (_m *Marathon) WaitOnGroup(name string, timeout time.Duration) error {
ret := _m.Called(name, timeout)
var r0 error
if rf, ok := ret.Get(0).(func(string, time.Duration) error); ok {
r0 = rf(name, timeout)
} else {
r0 = ret.Error(0)
}
return r0
}
// Deployments provides a mock function with given fields:
func (_m *Marathon) Deployments() ([]*marathon.Deployment, error) {
ret := _m.Called()
var r0 []*marathon.Deployment
if rf, ok := ret.Get(0).(func() []*marathon.Deployment); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*marathon.Deployment)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// DeleteDeployment provides a mock function with given fields: id, force
func (_m *Marathon) DeleteDeployment(id string, force bool) (*marathon.DeploymentID, error) {
ret := _m.Called(id, force)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string, bool) *marathon.DeploymentID); ok {
r0 = rf(id, force)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, bool) error); ok {
r1 = rf(id, force)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// HasDeployment provides a mock function with given fields: id
func (_m *Marathon) HasDeployment(id string) (bool, error) {
ret := _m.Called(id)
var r0 bool
if rf, ok := ret.Get(0).(func(string) bool); ok {
r0 = rf(id)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// WaitOnDeployment provides a mock function with given fields: id, timeout
func (_m *Marathon) WaitOnDeployment(id string, timeout time.Duration) error {
ret := _m.Called(id, timeout)
var r0 error
if rf, ok := ret.Get(0).(func(string, time.Duration) error); ok {
r0 = rf(id, timeout)
} else {
r0 = ret.Error(0)
}
return r0
}
// Subscriptions provides a mock function with given fields:
func (_m *Marathon) Subscriptions() (*marathon.Subscriptions, error) {
ret := _m.Called()
var r0 *marathon.Subscriptions
if rf, ok := ret.Get(0).(func() *marathon.Subscriptions); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Subscriptions)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AddEventsListener provides a mock function with given fields: channel, filter
func (_m *Marathon) AddEventsListener(channel marathon.EventsChannel, filter int) error {
ret := _m.Called(channel, filter)
var r0 error
if rf, ok := ret.Get(0).(func(marathon.EventsChannel, int) error); ok {
r0 = rf(channel, filter)
} else {
r0 = ret.Error(0)
}
return r0
}
// RemoveEventsListener provides a mock function with given fields: channel
func (_m *Marathon) RemoveEventsListener(channel marathon.EventsChannel) {
_m.Called(channel)
}
// Unsubscribe provides a mock function with given fields: _a0
func (_m *Marathon) Unsubscribe(_a0 string) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetMarathonURL provides a mock function with given fields:
func (_m *Marathon) GetMarathonURL() string {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
// Ping provides a mock function with given fields:
func (_m *Marathon) Ping() (bool, error) {
ret := _m.Called()
var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Info provides a mock function with given fields:
func (_m *Marathon) Info() (*marathon.Info, error) {
ret := _m.Called()
var r0 *marathon.Info
if rf, ok := ret.Get(0).(func() *marathon.Info); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Info)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Leader provides a mock function with given fields:
func (_m *Marathon) Leader() (string, error) {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AbdicateLeader provides a mock function with given fields:
func (_m *Marathon) AbdicateLeader() (string, error) {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@@ -70,7 +70,8 @@ func (provider *Kv) loadConfig() *types.Configuration {
templateObjects := struct {
Prefix string
}{
provider.Prefix,
// Allow `/traefik/alias` to superesede `provider.Prefix`
strings.TrimSuffix(provider.get(provider.Prefix, provider.Prefix+"/alias"), "/"),
}
var KvFuncMap = template.FuncMap{
"List": provider.list,

View File

@@ -7,10 +7,12 @@ import (
"strings"
"text/template"
"crypto/tls"
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus"
"github.com/emilevauge/traefik/types"
"github.com/gambol99/go-marathon"
"net/http"
)
// Marathon holds configuration of the Marathon provider.
@@ -20,7 +22,8 @@ type Marathon struct {
Domain string
NetworkInterface string
Basic *MarathonBasic
marathonClient lightMarathonClient
TLS *tls.Config
marathonClient marathon.Marathon
}
// MarathonBasic holds basic authentication specific configurations
@@ -44,6 +47,11 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage)
config.HTTPBasicAuthUser = provider.Basic.HTTPBasicAuthUser
config.HTTPBasicPassword = provider.Basic.HTTPBasicPassword
}
config.HTTPClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: provider.TLS,
},
}
client, err := marathon.NewClient(config)
if err != nil {
log.Errorf("Failed to create a client for marathon, error: %s", err)
@@ -81,16 +89,17 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage)
func (provider *Marathon) loadMarathonConfig() *types.Configuration {
var MarathonFuncMap = template.FuncMap{
"getBackend": provider.getBackend,
"getPort": provider.getPort,
"getWeight": provider.getWeight,
"getDomain": provider.getDomain,
"getProtocol": provider.getProtocol,
"getPassHostHeader": provider.getPassHostHeader,
"getEntryPoints": provider.getEntryPoints,
"getFrontendValue": provider.getFrontendValue,
"getFrontendRule": provider.getFrontendRule,
"replace": replace,
"getBackend": provider.getBackend,
"getPort": provider.getPort,
"getWeight": provider.getWeight,
"getDomain": provider.getDomain,
"getProtocol": provider.getProtocol,
"getPassHostHeader": provider.getPassHostHeader,
"getEntryPoints": provider.getEntryPoints,
"getFrontendValue": provider.getFrontendValue,
"getFrontendRule": provider.getFrontendRule,
"getFrontendBackend": provider.getFrontendBackend,
"replace": replace,
}
applications, err := provider.marathonClient.Applications(nil)
@@ -99,7 +108,7 @@ func (provider *Marathon) loadMarathonConfig() *types.Configuration {
return nil
}
tasks, err := provider.marathonClient.AllTasks((url.Values{"status": []string{"running"}}))
tasks, err := provider.marathonClient.AllTasks(&marathon.AllTasksOpts{Status: "running"})
if err != nil {
log.Errorf("Failed to create a client for marathon, error: %s", err)
return nil
@@ -189,7 +198,7 @@ func taskFilter(task marathon.Task, applications *marathon.Applications) bool {
//filter healthchecks
if application.HasHealthChecks() {
if task.HasHealthCheckResults() {
for _, healthcheck := range task.HealthCheckResult {
for _, healthcheck := range task.HealthCheckResults {
// found one bad healthcheck, return false
if !healthcheck.Alive {
log.Debugf("Filtering marathon task %s with bad healthcheck", task.AppID)
@@ -313,7 +322,16 @@ func (provider *Marathon) getFrontendRule(application marathon.Application) stri
return "Host"
}
func (provider *Marathon) getBackend(application marathon.Application) string {
func (provider *Marathon) getBackend(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 ""
}
return provider.getFrontendBackend(application)
}
func (provider *Marathon) getFrontendBackend(application marathon.Application) string {
if label, err := provider.getLabel(application, "traefik.backend"); err == nil {
return label
}

View File

@@ -1,34 +1,32 @@
package provider
import (
"errors"
"net/url"
"reflect"
"testing"
"errors"
"github.com/emilevauge/traefik/mocks"
"github.com/emilevauge/traefik/types"
"github.com/gambol99/go-marathon"
"github.com/stretchr/testify/mock"
)
type fakeClient struct {
applicationsError bool
applications *marathon.Applications
tasksError bool
tasks *marathon.Tasks
mocks.Marathon
}
func (c *fakeClient) Applications(url.Values) (*marathon.Applications, error) {
if c.applicationsError {
return nil, errors.New("error")
func newFakeClient(applicationsError bool, applications *marathon.Applications, tasksError bool, tasks *marathon.Tasks) *fakeClient {
// create an instance of our test object
fakeClient := new(fakeClient)
if applicationsError {
fakeClient.On("Applications", mock.AnythingOfType("url.Values")).Return(nil, errors.New("error"))
}
return c.applications, nil
}
func (c *fakeClient) AllTasks(v url.Values) (*marathon.Tasks, error) {
if c.tasksError {
return nil, errors.New("error")
fakeClient.On("Applications", mock.AnythingOfType("url.Values")).Return(applications, nil)
if tasksError {
fakeClient.On("AllTasks", mock.AnythingOfType("*marathon.AllTasksOpts")).Return(nil, errors.New("error"))
}
return c.tasks, nil
fakeClient.On("AllTasks", mock.AnythingOfType("*marathon.AllTasksOpts")).Return(tasks, nil)
return fakeClient
}
func TestMarathonLoadConfig(t *testing.T) {
@@ -110,14 +108,10 @@ func TestMarathonLoadConfig(t *testing.T) {
}
for _, c := range cases {
fakeClient := newFakeClient(c.applicationsError, c.applications, c.tasksError, c.tasks)
provider := &Marathon{
Domain: "docker.localhost",
marathonClient: &fakeClient{
applicationsError: c.applicationsError,
applications: c.applications,
tasksError: c.tasksError,
tasks: c.tasks,
},
Domain: "docker.localhost",
marathonClient: fakeClient,
}
actualConfig := provider.loadMarathonConfig()
if c.expectedNil {
@@ -315,7 +309,7 @@ func TestMarathonTaskFilter(t *testing.T) {
task: marathon.Task{
AppID: "foo",
Ports: []int{80},
HealthCheckResult: []*marathon.HealthCheckResult{
HealthCheckResults: []*marathon.HealthCheckResult{
{
Alive: false,
},
@@ -338,7 +332,7 @@ func TestMarathonTaskFilter(t *testing.T) {
task: marathon.Task{
AppID: "foo",
Ports: []int{80},
HealthCheckResult: []*marathon.HealthCheckResult{
HealthCheckResults: []*marathon.HealthCheckResult{
{
Alive: true,
},
@@ -379,7 +373,7 @@ func TestMarathonTaskFilter(t *testing.T) {
task: marathon.Task{
AppID: "foo",
Ports: []int{80},
HealthCheckResult: []*marathon.HealthCheckResult{
HealthCheckResults: []*marathon.HealthCheckResult{
{
Alive: true,
},
@@ -851,7 +845,7 @@ func TestMarathonGetBackend(t *testing.T) {
}
for _, a := range applications {
actual := provider.getBackend(a.application)
actual := provider.getFrontendBackend(a.application)
if actual != a.expected {
t.Fatalf("expected %q, got %q", a.expected, actual)
}

View File

@@ -1,13 +1,13 @@
{{$apps := .Applications}}
[backends]{{range .Tasks}}
[backends.backend{{with index $apps 0 }}{{getBackend .}}{{end}}.servers.server-{{.ID | replace "." "-"}}]
[backends.backend{{getBackend . $apps}}.servers.server-{{.ID | replace "." "-"}}]
url = "{{getProtocol . $apps}}://{{.Host}}:{{getPort . $apps}}"
weight = {{getWeight . $apps}}
{{end}}
[frontends]{{range .Applications}}
[frontends.frontend{{.ID | replace "/" "-"}}]
backend = "backend{{getBackend .}}"
backend = "backend{{getFrontendBackend .}}"
passHostHeader = {{getPassHostHeader .}}
entryPoints = [{{range getEntryPoints .}}
"{{.}}",

View File

@@ -31,6 +31,7 @@ slave:
- /usr/bin/docker:/usr/bin/docker:ro
- /usr/lib/x86_64-linux-gnu/libapparmor.so.1:/usr/lib/x86_64-linux-gnu/libapparmor.so.1:ro
- /var/run/docker.sock:/var/run/docker.sock
- /lib/x86_64-linux-gnu/libsystemd-journal.so.0:/lib/x86_64-linux-gnu/libsystemd-journal.so.0
marathon:
image: mesosphere/marathon:v0.13.0

View File

@@ -239,6 +239,13 @@
# httpBasicAuthUser = "foo"
# httpBasicPassword = "bar"
# TLS client configuration. https://golang.org/pkg/crypto/tls/#Config
#
# Optional
#
# [marathon.TLS]
# InsecureSkipVerify = true
################################################################
# Consul KV configuration backend

View File

@@ -11,8 +11,8 @@ COPY bower.json $WEBUI_DIR/
WORKDIR $WEBUI_DIR
RUN npm set progress=false
RUN npm install
RUN bower install --allow-root
RUN npm install --quiet
RUN bower install --allow-root --quiet
COPY . $WEBUI_DIR/