Compare commits

...

15 Commits

Author SHA1 Message Date
Emile Vauge
c0fd700904 Merge pull request #164 from vdemeester/bump-go
Bump Go 1.5.3
2016-01-14 16:00:51 +01:00
Vincent Demeester
72177c676e Bump Go 1.5.3
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-01-14 15:45:23 +01:00
Vincent Demeester
784fd74d3f Merge pull request #138 from dylanmei/marathon_uses_port_from_label
Marathon provider uses port from label.
2016-01-13 08:30:00 +01:00
Dylan Meissner
cfbd43d1ee Marathon provider uses port or portIndex from label. 2016-01-12 06:46:29 -08:00
Vincent Demeester
f10bbd8c69 Merge pull request #153 from PierreZ/master
Fix Issue #150: Add possibility to have REST API in read-only mode
2016-01-09 00:32:53 +01:00
Pierre Zemb
6bcb6f92f5 Update Doc about read-only mode 2016-01-03 20:45:53 +01:00
PierreZ
f6b5684a5b adding read only mode 2016-01-03 20:42:09 +01:00
Vincent Demeester
866e8db5f7 Merge pull request #137 from dylanmei/marathon_route_names_with_slashes
Marathon tmpl converts slashes in Marathon app names to dashes
2015-12-07 22:12:45 +01:00
Dylan Meissner
a9925c7521 The included Marathon tmpl converts multiple slashes in Marathon app names to dashes. 2015-12-05 10:02:39 -08:00
Vincent Demeester
f955cc33c5 Merge pull request #134 from emilevauge/add-getBool-kvprovider-or-not
Removes getBool from kv provider
2015-12-04 09:41:36 +01:00
emile
e728f32a15 Removes getBool from kv provider, fixes https://github.com/emilevauge/traefik/issues/117 2015-12-04 09:28:43 +01:00
Vincent Demeester
4abb4c6489 Merge pull request #135 from ViBiOh/master
Adding expose for default port
2015-12-03 17:45:22 +01:00
Vincent Boutour
66998e60b8 Adding expose for default port 2015-12-03 17:15:01 +01:00
Vincent Demeester
71288e5799 Merge pull request #123 from emilevauge/alltasks-status-marathon
Add filter task by running status in marathon
2015-12-02 09:49:28 +01:00
emile
8fdd0b20d1 Add filter task by running status in marathon 2015-12-01 22:53:31 +01:00
11 changed files with 239 additions and 92 deletions

View File

@@ -1,4 +1,5 @@
FROM scratch
COPY script/ca-certificates.crt /etc/ssl/certs/
COPY dist/traefik /
EXPOSE 80
ENTRYPOINT ["/traefik"]

View File

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

View File

@@ -243,6 +243,11 @@ address = ":8080"
#
# CertFile = "traefik.crt"
# KeyFile = "traefik.key"
#
# Set REST API to read-only mode
#
# Optional
# ReadOnly = false
```
- `/`: provides a simple HTML frontend of Træfik
@@ -482,7 +487,8 @@ domain = "marathon.localhost"
Labels can be used on containers to override default behaviour:
- `traefik.backend=foo`: assign the application to `foo` backend
- `traefik.port=80`: register this port. Useful when the application exposes multiples ports.
- `traefik.portIndex=1`: register port by index in the application's ports array. Useful when the application exposes multiple ports.
- `traefik.port=80`: register the explicit application port value. Cannot be used alongside `traefik.portIndex`.
- `traefik.protocol=https`: override the default `http` protocol
- `traefik.weight=10`: assign this weight to the application
- `traefik.enable=false`: disable this application in Træfɪk

View File

@@ -43,7 +43,7 @@ import:
- package: github.com/alecthomas/units
ref: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
- package: github.com/gambol99/go-marathon
ref: 0ba31bcb0d7633ba1888d744c42990eb15281cf1
ref: 8ce3f764250b2de3f2c627d12ca7dd21bd5e7f93
- package: github.com/mailgun/predicate
ref: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
- package: github.com/thoas/stats
@@ -142,3 +142,8 @@ import:
- package: github.com/codahale/hdrhistogram
ref: 954f16e8b9ef0e5d5189456aa4c1202758e04f17
- package: github.com/gorilla/websocket
- package: github.com/donovanhide/eventsource
ref: d8a3071799b98cacd30b6da92f536050ccfe6da4
- package: github.com/golang/glog
ref: fca8c8854093a154ff1eb580aae10276ad6b1b5f

View File

@@ -11,7 +11,6 @@ import (
"github.com/docker/libkv"
"github.com/docker/libkv/store"
"github.com/emilevauge/traefik/types"
"strconv"
)
// Kv holds common configurations of key-value providers.
@@ -74,10 +73,9 @@ func (provider *Kv) loadConfig() *types.Configuration {
provider.Prefix,
}
var KvFuncMap = template.FuncMap{
"List": provider.list,
"Get": provider.get,
"GetBool": provider.getBool,
"Last": provider.last,
"List": provider.list,
"Get": provider.get,
"Last": provider.last,
}
configuration, err := provider.getConfiguration("templates/kv.tmpl", KvFuncMap, templateObjects)
@@ -114,16 +112,6 @@ func (provider *Kv) get(keys ...string) string {
return string(keyPair.Value)
}
func (provider *Kv) getBool(keys ...string) bool {
value := provider.get(keys...)
b, err := strconv.ParseBool(string(value))
if err != nil {
log.Error("Error getting key: ", strings.Join(keys, ""), err)
return false
}
return b
}
func (provider *Kv) last(key string) string {
splittedKey := strings.Split(key, "/")
return splittedKey[len(splittedKey)-1]

View File

@@ -194,61 +194,6 @@ func TestKvGet(t *testing.T) {
}
}
func TestKvGetBool(t *testing.T) {
cases := []struct {
provider *Kv
keys []string
expected bool
}{
{
provider: &Kv{
kvclient: &Mock{
KVPairs: []*store.KVPair{
{
Key: "foo",
Value: []byte("true"),
},
},
},
},
keys: []string{"foo"},
expected: true,
},
{
provider: &Kv{
kvclient: &Mock{
KVPairs: []*store.KVPair{
{
Key: "foo",
Value: []byte("false"),
},
},
},
},
keys: []string{"foo"},
expected: false,
},
}
for _, c := range cases {
actual := c.provider.getBool(c.keys...)
if actual != c.expected {
t.Fatalf("expected %v, got %v for %v and %v", c.expected, actual, c.keys, c.provider)
}
}
// Error case
provider := &Kv{
kvclient: &Mock{
Error: true,
},
}
actual := provider.get("anything")
if actual != "" {
t.Fatalf("Should have return nil, got %v", actual)
}
}
func TestKvLast(t *testing.T) {
cases := []struct {
key string

View File

@@ -23,7 +23,7 @@ type Marathon struct {
type lightMarathonClient interface {
Applications(url.Values) (*marathon.Applications, error)
AllTasks() (*marathon.Tasks, error)
AllTasks(v url.Values) (*marathon.Tasks, error)
}
// Provide allows the provider to provide configurations to traefik
@@ -85,7 +85,7 @@ func (provider *Marathon) loadMarathonConfig() *types.Configuration {
return nil
}
tasks, err := provider.marathonClient.AllTasks()
tasks, err := provider.marathonClient.AllTasks((url.Values{"status": []string{"running"}}))
if err != nil {
log.Errorf("Failed to create a client for marathon, error: %s", err)
return nil
@@ -123,20 +123,55 @@ func taskFilter(task marathon.Task, applications *marathon.Applications) bool {
log.Debug("Filtering marathon task without port %s", task.AppID)
return false
}
application, errApp := getApplication(task, applications.Apps)
if errApp != nil {
application, err := getApplication(task, applications.Apps)
if err != nil {
log.Errorf("Unable to get marathon application from task %s", task.AppID)
return false
}
_, err := strconv.Atoi(application.Labels["traefik.port"])
if len(application.Ports) > 1 && err != nil {
log.Debugf("Filtering marathon task %s with more than 1 port and no traefik.port label", task.AppID)
return false
}
if application.Labels["traefik.enable"] == "false" {
log.Debugf("Filtering disabled marathon task %s", task.AppID)
return false
}
//filter indeterminable task port
portIndexLabel := application.Labels["traefik.portIndex"]
portValueLabel := application.Labels["traefik.port"]
if portIndexLabel != "" && portValueLabel != "" {
log.Debugf("Filtering marathon task %s specifying both traefik.portIndex and traefik.port labels", task.AppID)
return false
}
if portIndexLabel == "" && portValueLabel == "" && len(application.Ports) > 1 {
log.Debugf("Filtering marathon task %s with more than 1 port and no traefik.portIndex or traefik.port label", task.AppID)
return false
}
if portIndexLabel != "" {
index, err := strconv.Atoi(application.Labels["traefik.portIndex"])
if err != nil || index < 0 || index > len(application.Ports)-1 {
log.Debugf("Filtering marathon task %s with unexpected value for traefik.portIndex label", task.AppID)
return false
}
}
if portValueLabel != "" {
port, err := strconv.Atoi(application.Labels["traefik.port"])
if err != nil {
log.Debugf("Filtering marathon task %s with unexpected value for traefik.port label", task.AppID)
return false
}
var foundPort bool
for _, exposedPort := range task.Ports {
if port == exposedPort {
foundPort = true
break
}
}
if !foundPort {
log.Debugf("Filtering marathon task %s without a matching port for traefik.port label", task.AppID)
return false
}
}
//filter healthchecks
if application.HasHealthChecks() {
if task.HasHealthCheckResults() {
@@ -179,7 +214,22 @@ func (provider *Marathon) getLabel(application marathon.Application, label strin
return "", errors.New("Label not found:" + label)
}
func (provider *Marathon) getPort(task marathon.Task) string {
func (provider *Marathon) getPort(task marathon.Task, applications []marathon.Application) string {
application, err := getApplication(task, applications)
if err != nil {
log.Errorf("Unable to get marathon application from task %s", task.AppID)
return ""
}
if portIndexLabel, err := provider.getLabel(application, "traefik.portIndex"); err == nil {
if index, err := strconv.Atoi(portIndexLabel); err == nil {
return strconv.Itoa(task.Ports[index])
}
}
if portValueLabel, err := provider.getLabel(application, "traefik.port"); err == nil {
return portValueLabel
}
for _, port := range task.Ports {
return strconv.Itoa(port)
}

View File

@@ -24,7 +24,7 @@ func (c *fakeClient) Applications(url.Values) (*marathon.Applications, error) {
return c.applications, nil
}
func (c *fakeClient) AllTasks() (*marathon.Tasks, error) {
func (c *fakeClient) AllTasks(v url.Values) (*marathon.Tasks, error) {
if c.tasksError {
return nil, errors.New("error")
}
@@ -201,6 +201,97 @@ func TestMarathonTaskFilter(t *testing.T) {
},
expected: false,
},
{
task: marathon.Task{
AppID: "specify-port-number",
Ports: []int{80, 443},
},
applications: &marathon.Applications{
Apps: []marathon.Application{
{
ID: "specify-port-number",
Ports: []int{80, 443},
Labels: map[string]string{
"traefik.port": "80",
},
},
},
},
expected: true,
},
{
task: marathon.Task{
AppID: "specify-unknown-port-number",
Ports: []int{80, 443},
},
applications: &marathon.Applications{
Apps: []marathon.Application{
{
ID: "specify-unknown-port-number",
Ports: []int{80, 443},
Labels: map[string]string{
"traefik.port": "8080",
},
},
},
},
expected: false,
},
{
task: marathon.Task{
AppID: "specify-port-index",
Ports: []int{80, 443},
},
applications: &marathon.Applications{
Apps: []marathon.Application{
{
ID: "specify-port-index",
Ports: []int{80, 443},
Labels: map[string]string{
"traefik.portIndex": "0",
},
},
},
},
expected: true,
},
{
task: marathon.Task{
AppID: "specify-out-of-range-port-index",
Ports: []int{80, 443},
},
applications: &marathon.Applications{
Apps: []marathon.Application{
{
ID: "specify-out-of-range-port-index",
Ports: []int{80, 443},
Labels: map[string]string{
"traefik.portIndex": "2",
},
},
},
},
expected: false,
},
{
task: marathon.Task{
AppID: "specify-both-port-index-and-number",
Ports: []int{80, 443},
},
applications: &marathon.Applications{
Apps: []marathon.Application{
{
ID: "specify-both-port-index-and-number",
Ports: []int{80, 443},
Labels: map[string]string{
"traefik.port": "443",
"traefik.portIndex": "1",
},
},
},
},
expected: false,
},
{
task: marathon.Task{
AppID: "foo",
@@ -370,29 +461,84 @@ func TestMarathonGetPort(t *testing.T) {
provider := &Marathon{}
cases := []struct {
task marathon.Task
expected string
applications []marathon.Application
task marathon.Task
expected string
}{
{
task: marathon.Task{},
applications: []marathon.Application{},
task: marathon.Task{},
expected: "",
},
{
applications: []marathon.Application{
{
ID: "test1",
},
},
task: marathon.Task{
AppID: "test2",
},
expected: "",
},
{
applications: []marathon.Application{
{
ID: "test1",
},
},
task: marathon.Task{
AppID: "test1",
Ports: []int{80},
},
expected: "80",
},
{
applications: []marathon.Application{
{
ID: "test1",
},
},
task: marathon.Task{
AppID: "test1",
Ports: []int{80, 443},
},
expected: "80",
},
{
applications: []marathon.Application{
{
ID: "specify-port-number",
Labels: map[string]string{
"traefik.port": "443",
},
},
},
task: marathon.Task{
AppID: "specify-port-number",
Ports: []int{80, 443},
},
expected: "443",
},
{
applications: []marathon.Application{
{
ID: "specify-port-index",
Labels: map[string]string{
"traefik.portIndex": "1",
},
},
},
task: marathon.Task{
AppID: "specify-port-index",
Ports: []int{80, 443},
},
expected: "443",
},
}
for _, c := range cases {
actual := provider.getPort(c.task)
actual := provider.getPort(c.task, c.applications)
if actual != c.expected {
t.Fatalf("expected %q, got %q", c.expected, actual)
}

View File

@@ -28,7 +28,7 @@
{{$frontend := Last .}}
[frontends.{{$frontend}}]
backend = "{{Get . "/backend"}}"
passHostHeader = "{{GetBool . "/passHostHeader"}}"
passHostHeader = {{Get . "/passHostHeader"}}
{{$routes := List . "/routes/"}}
{{range $routes}}
[frontends.{{$frontend}}.routes.{{Last .}}]

View File

@@ -1,7 +1,7 @@
{{$apps := .Applications}}
[backends]{{range .Tasks}}
[backends.backend{{.AppID | replace "/" "-"}}.servers.server-{{.ID | replace "." "-"}}]
url = "{{getProtocol . $apps}}://{{.Host}}:{{getPort .}}"
url = "{{getProtocol . $apps}}://{{.Host}}:{{getPort . $apps}}"
weight = {{getWeight . $apps}}
{{end}}
@@ -9,7 +9,7 @@
[frontends.frontend{{.ID | replace "/" "-"}}]
backend = "backend{{.ID | replace "/" "-"}}"
passHostHeader = {{getPassHostHeader .}}
[frontends.frontend-{{.ID | replace "/" ""}}.routes.route-host-{{.ID | replace "/" ""}}]
[frontends.frontend{{.ID | replace "/" "-"}}.routes.route-host{{.ID | replace "/" "-"}}]
rule = "{{getFrontendRule .}}"
value = "{{getFrontendValue .}}"
{{end}}
{{end}}

6
web.go
View File

@@ -19,6 +19,7 @@ import (
type WebProvider struct {
Address string
CertFile, KeyFile string
ReadOnly bool
}
var (
@@ -40,6 +41,11 @@ func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessag
systemRouter.Methods("GET").Path("/api/providers").HandlerFunc(getConfigHandler)
systemRouter.Methods("GET").Path("/api/providers/{provider}").HandlerFunc(getProviderHandler)
systemRouter.Methods("PUT").Path("/api/providers/{provider}").HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
if provider.ReadOnly {
response.WriteHeader(http.StatusForbidden)
fmt.Fprintf(response, "REST API is in read-only mode")
return
}
vars := mux.Vars(request)
if vars["provider"] != "web" {
response.WriteHeader(http.StatusBadRequest)