Compare commits

...

6 Commits

Author SHA1 Message Date
Vincent Demeester
6c3c5578c6 Merge pull request #225 from containous/add-path-prefix
Add PathPrefixStrip and PathStrip rules
2016-02-26 16:52:12 +01:00
Emile Vauge
122783e36b Add PathPrefixStrip and PathStrip rules
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-02-26 15:58:55 +01:00
Emile Vauge
b84b95fe97 Merge pull request #223 from goguardian/kv-multi
Support multiple endpoints for KV stores:
2016-02-26 11:41:18 +01:00
Advait Shinde
a99010b8c2 Create an integration test for Etcd:
- Integration test specifically spins up an Etcd cluster with three
    nodes.
2016-02-25 23:34:51 +00:00
Advait Shinde
8954aa7118 Update docs to mention commas. 2016-02-25 23:34:51 +00:00
Advait Shinde
3cf848958f Support multiple endpoints for KV stores:
- Fixes #222
2016-02-25 23:34:51 +00:00
10 changed files with 164 additions and 43 deletions

6
cmd.go
View File

@@ -131,7 +131,7 @@ func init() {
traefikCmd.PersistentFlags().BoolVar(&arguments.consul, "consul", false, "Enable Consul backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Consul.Watch, "consul.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Filename, "consul.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Endpoint, "consul.endpoint", "127.0.0.1:8500", "Consul server endpoint")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Endpoint, "consul.endpoint", "127.0.0.1:8500", "Comma sepparated Consul server endpoints")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Prefix, "consul.prefix", "/traefik", "Prefix used for KV store")
traefikCmd.PersistentFlags().BoolVar(&arguments.consulTLS, "consul.tls", false, "Enable Consul TLS support")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.TLS.CA, "consul.tls.ca", "", "TLS CA")
@@ -146,13 +146,13 @@ func init() {
traefikCmd.PersistentFlags().BoolVar(&arguments.zookeeper, "zookeeper", false, "Enable Zookeeper backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Zookeeper.Watch, "zookeeper.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Filename, "zookeeper.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Endpoint, "zookeeper.endpoint", "127.0.0.1:2181", "Zookeeper server endpoint")
traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Endpoint, "zookeeper.endpoint", "127.0.0.1:2181", "Comma sepparated Zookeeper server endpoints")
traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Prefix, "zookeeper.prefix", "/traefik", "Prefix used for KV store")
traefikCmd.PersistentFlags().BoolVar(&arguments.etcd, "etcd", false, "Enable Etcd backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Etcd.Watch, "etcd.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Filename, "etcd.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Endpoint, "etcd.endpoint", "127.0.0.1:4001", "Etcd server endpoint")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Endpoint, "etcd.endpoint", "127.0.0.1:4001", "Comma sepparated Etcd server endpoints")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Prefix, "etcd.prefix", "/traefik", "Prefix used for KV store")
traefikCmd.PersistentFlags().BoolVar(&arguments.etcdTLS, "etcd.tls", false, "Enable Etcd TLS support")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.TLS.CA, "etcd.tls.ca", "", "TLS CA")

View File

@@ -37,7 +37,9 @@ Frontends can be defined using the following rules:
- `Host`: Host adds a matcher for the URL host. It accepts a template with zero or more URL variables enclosed by `{}`. Variables can define an optional regexp pattern to be matched: `www.traefik.io`, `{subdomain:[a-z]+}.traefik.io`
- `Methods`: Methods adds a matcher for HTTP methods. It accepts a sequence of one or more methods to be matched, e.g.: `GET`, `POST`, `PUT`
- `Path`: Path adds a matcher for the URL path. It accepts a template with zero or more URL variables enclosed by `{}`. The template must start with a `/`. For exemple `/products/` `/articles/{category}/{id:[0-9]+}`
- `PathStrip`: Same as `Path` but strip the given prefix from the request URL's Path.
- `PathPrefix`: PathPrefix adds a matcher for the URL path prefix. This matches if the given template is a prefix of the full URL path.
- `PathPrefixStrip`: Same as `PathPrefix` but strip the given prefix from the request URL's Path.
A frontend is a set of rules that forwards the incoming http traffic to a backend.
@@ -106,7 +108,7 @@ Flags:
--boltdb.watch Watch provider (default true)
-c, --configFile string Configuration file to use (TOML, JSON, YAML, HCL).
--consul Enable Consul backend
--consul.endpoint string Consul server endpoint (default "127.0.0.1:8500")
--consul.endpoint string Comma sepparated Consul server endpoints (default "127.0.0.1:8500")
--consul.filename string Override default configuration template. For advanced users :)
--consul.prefix string Prefix used for KV store (default "/traefik")
--consul.tls Enable Consul TLS support
@@ -131,7 +133,7 @@ Flags:
--docker.watch Watch provider (default true)
--entryPoints value Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'
--etcd Enable Etcd backend
--etcd.endpoint string Etcd server endpoint (default "127.0.0.1:4001")
--etcd.endpoint string Comma sepparated Etcd server endpoints (default "127.0.0.1:4001")
--etcd.filename string Override default configuration template. For advanced users :)
--etcd.prefix string Prefix used for KV store (default "/traefik")
--etcd.tls Enable Etcd TLS support
@@ -160,7 +162,7 @@ Flags:
--web.keyFile string SSL certificate
--web.readOnly Enable read only API
--zookeeper Enable Zookeeper backend
--zookeeper.endpoint string Zookeeper server endpoint (default "127.0.0.1:2181")
--zookeeper.endpoint string Comma sepparated Zookeeper server endpoints (default "127.0.0.1:2181")
--zookeeper.filename string Override default configuration template. For advanced users :)
--zookeeper.prefix string Prefix used for KV store (default "/traefik")
--zookeeper.watch Watch provider (default true)

27
integration/etcd_test.go Normal file
View File

@@ -0,0 +1,27 @@
package main
import (
"net/http"
"os/exec"
"time"
"fmt"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) {
cmd := exec.Command(traefikBinary, "--configFile=fixtures/etcd/simple.toml")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
time.Sleep(1000 * time.Millisecond)
// TODO validate : run on 80
resp, err := http.Get("http://127.0.0.1:8000/")
// Expected no response as we did not configure anything
c.Assert(resp, checker.IsNil)
c.Assert(err, checker.NotNil)
c.Assert(err.Error(), checker.Contains, fmt.Sprintf("getsockopt: connection refused"))
}

View File

@@ -0,0 +1,10 @@
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"
logLevel = "DEBUG"
[etcd]
endpoint = "127.0.0.1:4003,127.0.0.1:4002,127.0.0.1:4001"

View File

@@ -29,6 +29,7 @@ func init() {
check.Suite(&DockerSuite{})
check.Suite(&ConsulSuite{})
check.Suite(&ConsulCatalogSuite{})
check.Suite(&EtcdSuite{})
check.Suite(&MarathonSuite{})
}
@@ -50,6 +51,13 @@ func (s *ConsulSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "consul")
}
// Etcd test suites (using libcompose)
type EtcdSuite struct{ BaseSuite }
func (s *EtcdSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "etcd")
}
// Marathon test suites (using libcompose)
type MarathonSuite struct{ BaseSuite }

View File

@@ -0,0 +1,30 @@
etcd1:
image: quay.io/coreos/etcd:v2.2.0
net: "host"
command: >
--name etcd1
--listen-peer-urls http://localhost:7001
--listen-client-urls http://localhost:4001
--initial-advertise-peer-urls http://localhost:7001
--advertise-client-urls http://localhost:4001
--initial-cluster etcd1=http://localhost:7001,etcd2=http://localhost:7002,etcd3=http://localhost:7003
etcd2:
image: quay.io/coreos/etcd:v2.2.0
net: "host"
command: >
--name etcd2
--listen-peer-urls http://localhost:7002
--listen-client-urls http://localhost:4002
--initial-advertise-peer-urls http://localhost:7002
--advertise-client-urls http://localhost:4002
--initial-cluster etcd1=http://localhost:7001,etcd2=http://localhost:7002,etcd3=http://localhost:7003
etcd3:
image: quay.io/coreos/etcd:v2.2.0
net: "host"
command: >
--name etcd3
--listen-peer-urls http://localhost:7003
--listen-client-urls http://localhost:4003
--initial-advertise-peer-urls http://localhost:7003
--advertise-client-urls http://localhost:4003
--initial-cluster etcd1=http://localhost:7001,etcd2=http://localhost:7002,etcd3=http://localhost:7003

View File

@@ -0,0 +1,22 @@
package middlewares
import (
"net/http"
"strings"
)
// StripPrefix is a middleware used to strip prefix from an URL request
type StripPrefix struct {
Handler http.Handler
Prefix string
}
func (s *StripPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if p := strings.TrimPrefix(r.URL.Path, s.Prefix); len(p) < len(r.URL.Path) {
r.URL.Path = p
r.RequestURI = r.URL.RequestURI()
s.Handler.ServeHTTP(w, r)
} else {
http.NotFound(w, r)
}
}

View File

@@ -69,7 +69,7 @@ func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage) error
kv, err := libkv.NewStore(
provider.storeType,
[]string{provider.Endpoint},
strings.Split(provider.Endpoint, ","),
storeConfig,
)
if err != nil {

View File

@@ -7,6 +7,16 @@ import (
"crypto/tls"
"encoding/json"
"errors"
"net/http"
"net/url"
"os"
"os/signal"
"reflect"
"regexp"
"sort"
"sync"
"syscall"
"time"
log "github.com/Sirupsen/logrus"
"github.com/codegangsta/negroni"
@@ -18,16 +28,6 @@ import (
"github.com/mailgun/oxy/cbreaker"
"github.com/mailgun/oxy/forward"
"github.com/mailgun/oxy/roundrobin"
"net/http"
"net/url"
"os"
"os/signal"
"reflect"
"regexp"
"sort"
"sync"
"syscall"
"time"
)
var oxyLogger = &OxyLogger{}
@@ -335,11 +335,11 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
newRoute := serverEntryPoints[entryPointName].httpRouter.NewRoute().Name(frontendName)
for routeName, route := range frontend.Routes {
log.Debugf("Creating route %s %s:%s", routeName, route.Rule, route.Value)
newRouteReflect, err := invoke(newRoute, route.Rule, route.Value)
route, err := getRoute(newRoute, route.Rule, route.Value)
if err != nil {
return nil, err
}
newRoute = newRouteReflect[0].Interface().(*mux.Route)
newRoute = route
}
entryPoint := globalConfiguration.EntryPoints[entryPointName]
if entryPoint.Redirect != nil {
@@ -399,7 +399,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
} else {
log.Debugf("Reusing backend %s", frontend.Backend)
}
newRoute.Handler(backends[frontend.Backend])
server.wireFrontendBackend(frontend.Routes, newRoute, backends[frontend.Backend])
}
err := newRoute.GetError()
if err != nil {
@@ -411,6 +411,32 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
return serverEntryPoints, nil
}
func (server *Server) wireFrontendBackend(routes map[string]types.Route, newRoute *mux.Route, handler http.Handler) {
// strip prefix
var strip bool
for _, route := range routes {
switch route.Rule {
case "PathStrip":
newRoute.Handler(&middlewares.StripPrefix{
Prefix: route.Value,
Handler: handler,
})
strip = true
break
case "PathPrefixStrip":
newRoute.Handler(&middlewares.StripPrefix{
Prefix: route.Value,
Handler: handler,
})
strip = true
break
}
}
if !strip {
newRoute.Handler(handler)
}
}
func (server *Server) loadEntryPointConfig(entryPointName string, entryPoint *EntryPoint) (http.Handler, error) {
regex := entryPoint.Redirect.Regex
replacement := entryPoint.Redirect.Replacement
@@ -443,9 +469,28 @@ func (server *Server) loadEntryPointConfig(entryPointName string, entryPoint *En
func (server *Server) buildDefaultHTTPRouter() *mux.Router {
router := mux.NewRouter()
router.NotFoundHandler = http.HandlerFunc(notFoundHandler)
router.StrictSlash(true)
return router
}
func getRoute(any interface{}, rule string, value ...interface{}) (*mux.Route, error) {
switch rule {
case "PathStrip":
rule = "Path"
case "PathPrefixStrip":
rule = "PathPrefix"
}
inputs := make([]reflect.Value, len(value))
for i := range value {
inputs[i] = reflect.ValueOf(value[i])
}
method := reflect.ValueOf(any).MethodByName(rule)
if method.IsValid() {
return method.Call(inputs)[0].Interface().(*mux.Route), nil
}
return nil, errors.New("Method not found: " + rule)
}
func sortedFrontendNamesForConfig(configuration *types.Configuration) []string {
keys := []string{}

View File

@@ -1,23 +0,0 @@
/*
Copyright
*/
package main
import (
"errors"
"reflect"
)
// 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, error) {
inputs := make([]reflect.Value, len(args))
for i := range args {
inputs[i] = reflect.ValueOf(args[i])
}
method := reflect.ValueOf(any).MethodByName(name)
if method.IsValid() {
return method.Call(inputs), nil
}
return nil, errors.New("Method not found: " + name)
}