Merge v1.4.0-rc2 into master
This commit is contained in:
commit
9fba37b409
64 changed files with 829 additions and 289 deletions
35
CHANGELOG.md
35
CHANGELOG.md
|
@ -1,5 +1,34 @@
|
|||
# Change Log
|
||||
|
||||
## [v1.4.0-rc2](https://github.com/containous/traefik/tree/v1.4.0-rc2) (2017-09-08)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.4.0-rc1...v1.4.0-rc2)
|
||||
|
||||
**Enhancements:**
|
||||
- **[authentication,consul]** Add Basic auth for consul catalog ([#2027](https://github.com/containous/traefik/pull/2027) by [mmatur](https://github.com/mmatur))
|
||||
- **[authentication,ecs]** Add basic auth for ecs ([#2026](https://github.com/containous/traefik/pull/2026) by [mmatur](https://github.com/mmatur))
|
||||
- **[logs]** Send traefik logs to stdout instead stderr ([#2054](https://github.com/containous/traefik/pull/2054) by [marco-jantke](https://github.com/marco-jantke))
|
||||
- **[websocket]** Add test for SSL TERMINATION in Websocket IT ([#2063](https://github.com/containous/traefik/pull/2063) by [Juliens](https://github.com/Juliens))
|
||||
|
||||
**Bug fixes:**
|
||||
- **[consul]** Fix consul catalog refresh problems ([#2089](https://github.com/containous/traefik/pull/2089) by [Juliens](https://github.com/Juliens))
|
||||
- **[logs,middleware]** Access log default values ([#2061](https://github.com/containous/traefik/pull/2061) by [ldez](https://github.com/ldez))
|
||||
- **[metrics]** prometheus, HTTP method and utf8 ([#2081](https://github.com/containous/traefik/pull/2081) by [ldez](https://github.com/ldez))
|
||||
- **[rancher]** fix rancher api environment get ([#2053](https://github.com/containous/traefik/pull/2053) by [SantoDE](https://github.com/SantoDE))
|
||||
- **[websocket]** RawPath and Transfer TLSConfig in websocket ([#2088](https://github.com/containous/traefik/pull/2088) by [Juliens](https://github.com/Juliens))
|
||||
- Fix error in prepareServer ([#2076](https://github.com/containous/traefik/pull/2076) by [emilevauge](https://github.com/emilevauge))
|
||||
|
||||
**Documentation:**
|
||||
- **[acme,provider]** Fix whitespaces ([#2075](https://github.com/containous/traefik/pull/2075) by [chulkilee](https://github.com/chulkilee))
|
||||
- **[ecs]** Fix IAM policy sid. ([#2066](https://github.com/containous/traefik/pull/2066) by [charlieoleary](https://github.com/charlieoleary))
|
||||
- **[k8s]** Fix invalid service yaml example ([#2059](https://github.com/containous/traefik/pull/2059) by [kairen](https://github.com/kairen))
|
||||
- **[mesos]** fix: documentation Mesos. ([#2029](https://github.com/containous/traefik/pull/2029) by [ldez](https://github.com/ldez))
|
||||
- Update cluster.md ([#2073](https://github.com/containous/traefik/pull/2073) by [kmbremner](https://github.com/kmbremner))
|
||||
- Enhance documentation. ([#2048](https://github.com/containous/traefik/pull/2048) by [ldez](https://github.com/ldez))
|
||||
- doc: add notes on server urls with path ([#2045](https://github.com/containous/traefik/pull/2045) by [chulkilee](https://github.com/chulkilee))
|
||||
- Enhance security headers doc. ([#2042](https://github.com/containous/traefik/pull/2042) by [ldez](https://github.com/ldez))
|
||||
- HTTPS for images, video and links in docs. ([#2041](https://github.com/containous/traefik/pull/2041) by [ldez](https://github.com/ldez))
|
||||
- Fix error pages configuration. ([#2038](https://github.com/containous/traefik/pull/2038) by [ldez](https://github.com/ldez))
|
||||
|
||||
## [v1.4.0-rc1](https://github.com/containous/traefik/tree/v1.4.0-rc1) (2017-08-28)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.3.0-rc1...v1.4.0-rc1)
|
||||
|
||||
|
@ -143,6 +172,12 @@
|
|||
- Merge current v1.3 to master ([#1643](https://github.com/containous/traefik/pull/1643) by [ldez](https://github.com/ldez))
|
||||
- Merge v1.3.0-rc2 master ([#1613](https://github.com/containous/traefik/pull/1613) by [emilevauge](https://github.com/emilevauge))
|
||||
|
||||
## [v1.3.8](https://github.com/containous/traefik/tree/v1.3.8) (2017-09-07)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.3.7...v1.3.8)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[middleware]** Compress and Webscocket ([#2079](https://github.com/containous/traefik/pull/2079) by [ldez](https://github.com/ldez))
|
||||
|
||||
## [v1.3.7](https://github.com/containous/traefik/tree/v1.3.7) (2017-08-25)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.3.6...v1.3.7)
|
||||
|
||||
|
|
2
Makefile
2
Makefile
|
@ -11,7 +11,7 @@ TRAEFIK_ENVS := \
|
|||
-e CI \
|
||||
-e CONTAINER=DOCKER # Indicator for integration tests that we are running inside a container.
|
||||
|
||||
SRCS = $(shell git ls-files '*.go' | grep -v '^vendor/' | grep -v '^integration/vendor/')
|
||||
SRCS = $(shell git ls-files '*.go' | grep -v '^vendor/')
|
||||
|
||||
BIND_DIR := "dist"
|
||||
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/containous/traefik/$(BIND_DIR)"
|
||||
|
|
|
@ -381,7 +381,10 @@ To use a different port for the healthcheck:
|
|||
|
||||
### Servers
|
||||
|
||||
Servers are simply defined using a `URL`. You can also apply a custom `weight` to each server (this will be used by load-balancing).
|
||||
Servers are simply defined using a `url`. You can also apply a custom `weight` to each server (this will be used by load-balancing).
|
||||
|
||||
!!! note
|
||||
Paths in `url` are ignored. Use `Modifier` to specify paths instead.
|
||||
|
||||
Here is an example of backends and servers definition:
|
||||
|
||||
|
@ -497,9 +500,9 @@ Usage:
|
|||
traefik [command] [--flag=flag_argument]
|
||||
```
|
||||
|
||||
List of Træfik available commands with description :
|
||||
List of Træfik available commands with description :
|
||||
|
||||
- `version` : Print version
|
||||
- `version` : Print version
|
||||
- `storeconfig` : Store the static traefik configuration into a Key-value stores. Please refer to the [Store Træfik configuration](/user-guide/kv-config/#store-trfk-configuration) section to get documentation on it.
|
||||
- `bug`: The easiest way to submit a pre-filled issue.
|
||||
- `healthcheck`: Calls traefik `/ping` to check health.
|
||||
|
|
|
@ -114,3 +114,4 @@ Additional settings can be defined using Consul Catalog tags:
|
|||
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
|
||||
| `traefik.frontend.priority=10` | Override default frontend priority |
|
||||
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||
|
|
|
@ -79,7 +79,7 @@ SecretAccessKey = "123"
|
|||
Labels can be used on task containers to override default behaviour:
|
||||
|
||||
| Label | Description |
|
||||
|----------------------------------------------|------------------------------------------------------------------------------------------|
|
||||
|---------------------------------------------------|------------------------------------------------------------------------------------------|
|
||||
| `traefik.protocol=https` | override the default `http` protocol |
|
||||
| `traefik.weight=10` | assign this weight to the container |
|
||||
| `traefik.enable=false` | disable this container in Træfik |
|
||||
|
@ -89,6 +89,7 @@ Labels can be used on task containers to override default behaviour:
|
|||
| `traefik.frontend.passHostHeader=true` | forward client `Host` header to the backend. |
|
||||
| `traefik.frontend.priority=10` | override default frontend priority |
|
||||
| `traefik.frontend.entryPoints=http,https` | assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||
|
||||
If `AccessKeyID`/`SecretAccessKey` is not given credentials will be resolved in the following order:
|
||||
|
||||
|
@ -103,7 +104,7 @@ Træfik needs the following policy to read ECS information:
|
|||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "Traefik ECS read access",
|
||||
"Sid": "TraefikECSReadAccess",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ecs:ListClusters",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Clustering / High Availability (beta)
|
||||
|
||||
This guide explains how tu use Træfik in high availability mode.
|
||||
This guide explains how to use Træfik in high availability mode.
|
||||
In order to deploy and configure multiple Træfik instances, without copying the same configuration file on each instance, we will use a distributed Key-Value store.
|
||||
|
||||
## Prerequisites
|
||||
|
|
|
@ -66,6 +66,30 @@ func (s *ConsulCatalogSuite) registerService(name string, address string, port i
|
|||
return err
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) registerAgentService(name string, address string, port int, tags []string) error {
|
||||
agent := s.consulClient.Agent()
|
||||
err := agent.ServiceRegister(
|
||||
&api.AgentServiceRegistration{
|
||||
ID: address,
|
||||
Tags: tags,
|
||||
Name: name,
|
||||
Address: address,
|
||||
Port: port,
|
||||
Check: &api.AgentServiceCheck{
|
||||
HTTP: "http://" + address,
|
||||
Interval: "10s",
|
||||
},
|
||||
},
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) deregisterAgentService(address string) error {
|
||||
agent := s.consulClient.Agent()
|
||||
err := agent.ServiceDeregister(address)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) deregisterService(name string, address string) error {
|
||||
catalog := s.consulClient.Catalog()
|
||||
_, err := catalog.Deregister(
|
||||
|
@ -104,7 +128,7 @@ func (s *ConsulCatalogSuite) TestSingleService(c *check.C) {
|
|||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
nginx := s.composeProject.Container(c, "nginx")
|
||||
nginx := s.composeProject.Container(c, "nginx1")
|
||||
|
||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
|
@ -114,7 +138,7 @@ func (s *ConsulCatalogSuite) TestSingleService(c *check.C) {
|
|||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test.consul.localhost"
|
||||
|
||||
err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
|
@ -129,7 +153,7 @@ func (s *ConsulCatalogSuite) TestExposedByDefaultFalseSingleService(c *check.C)
|
|||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
nginx := s.composeProject.Container(c, "nginx")
|
||||
nginx := s.composeProject.Container(c, "nginx1")
|
||||
|
||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
|
@ -154,7 +178,7 @@ func (s *ConsulCatalogSuite) TestExposedByDefaultFalseSimpleServiceMultipleNode(
|
|||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
nginx := s.composeProject.Container(c, "nginx")
|
||||
nginx := s.composeProject.Container(c, "nginx1")
|
||||
nginx2 := s.composeProject.Container(c, "nginx2")
|
||||
|
||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
||||
|
@ -184,14 +208,14 @@ func (s *ConsulCatalogSuite) TestExposedByDefaultTrueSimpleServiceMultipleNode(c
|
|||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
nginx := s.composeProject.Container(c, "nginx")
|
||||
nginx := s.composeProject.Container(c, "nginx1")
|
||||
nginx2 := s.composeProject.Container(c, "nginx2")
|
||||
|
||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"name=nginx1"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||
|
||||
err = s.registerService("test", nginx2.NetworkSettings.IPAddress, 80, []string{})
|
||||
err = s.registerService("test", nginx2.NetworkSettings.IPAddress, 80, []string{"name=nginx2"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", nginx2.NetworkSettings.IPAddress)
|
||||
|
||||
|
@ -201,4 +225,98 @@ func (s *ConsulCatalogSuite) TestExposedByDefaultTrueSimpleServiceMultipleNode(c
|
|||
|
||||
err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 60*time.Second, try.BodyContains("nginx1", "nginx2"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) TestRefreshConfigWithMultipleNodeWithoutHealthCheck(c *check.C) {
|
||||
cmd, _ := s.cmdTraefik(
|
||||
withConfigFile("fixtures/consul_catalog/simple.toml"),
|
||||
"--consulCatalog",
|
||||
"--consulCatalog.exposedByDefault=true",
|
||||
"--consulCatalog.endpoint="+s.consulIP+":8500",
|
||||
"--consulCatalog.domain=consul.localhost")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
nginx := s.composeProject.Container(c, "nginx1")
|
||||
nginx2 := s.composeProject.Container(c, "nginx2")
|
||||
|
||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"name=nginx1"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||
|
||||
err = s.registerAgentService("test", nginx.NetworkSettings.IPAddress, 80, []string{"name=nginx1"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering agent service"))
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test.consul.localhost"
|
||||
|
||||
err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 60*time.Second, try.BodyContains("nginx1"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = s.registerService("test", nginx2.NetworkSettings.IPAddress, 80, []string{"name=nginx2"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 60*time.Second, try.BodyContains("nginx1", "nginx2"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
s.deregisterService("test", nginx2.NetworkSettings.IPAddress)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 60*time.Second, try.BodyContains("nginx1"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = s.registerService("test", nginx2.NetworkSettings.IPAddress, 80, []string{"name=nginx2"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", nginx2.NetworkSettings.IPAddress)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 60*time.Second, try.BodyContains("nginx1", "nginx2"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) TestBasicAuthSimpleService(c *check.C) {
|
||||
cmd, output := s.cmdTraefik(
|
||||
withConfigFile("fixtures/consul_catalog/simple.toml"),
|
||||
"--consulCatalog",
|
||||
"--consulCatalog.exposedByDefault=true",
|
||||
"--consulCatalog.endpoint="+s.consulIP+":8500",
|
||||
"--consulCatalog.domain=consul.localhost")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
defer func() {
|
||||
s.displayTraefikLog(c, output)
|
||||
}()
|
||||
|
||||
nginx := s.composeProject.Container(c, "nginx1")
|
||||
|
||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{
|
||||
"traefik.frontend.auth.basic=test:$2a$06$O5NksJPAcgrC9MuANkSoE.Xe9DSg7KcLLFYNr1Lj6hPcMmvgwxhme,test2:$2y$10$xP1SZ70QbZ4K2bTGKJOhpujkpcLxQcB3kEPF6XAV19IdcqsZTyDEe",
|
||||
})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test.consul.localhost"
|
||||
|
||||
err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusUnauthorized), try.HasBody())
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
req.SetBasicAuth("test", "test")
|
||||
err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
req.SetBasicAuth("test2", "test2")
|
||||
err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
defaultEntryPoints = ["http"]
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[web]
|
||||
address = ":8080"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":8000"
|
||||
|
|
27
integration/fixtures/websocket/config_https.toml
Normal file
27
integration/fixtures/websocket/config_https.toml
Normal file
|
@ -0,0 +1,27 @@
|
|||
defaultEntryPoints = ["wss"]
|
||||
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.wss]
|
||||
address = ":8000"
|
||||
[entryPoints.wss.tls]
|
||||
[[entryPoints.wss.tls.certificates]]
|
||||
CertFile = "resources/tls/local.cert"
|
||||
KeyFile = "resources/tls/local.key"
|
||||
|
||||
[web]
|
||||
address = ":8080"
|
||||
|
||||
[file]
|
||||
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.servers.server1]
|
||||
url = "{{ .WebsocketServer }}"
|
||||
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Path:/ws"
|
|
@ -11,7 +11,7 @@ consul:
|
|||
- "8301/udp"
|
||||
- "8302"
|
||||
- "8302/udp"
|
||||
nginx:
|
||||
nginx1:
|
||||
image: nginx:alpine
|
||||
nginx2:
|
||||
image: nginx:alpine
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
@ -232,3 +235,58 @@ func (suite *WebsocketSuite) TestWrongOriginIgnoredByServer(c *check.C) {
|
|||
c.Assert(string(msg), checker.Equals, "OK")
|
||||
|
||||
}
|
||||
|
||||
func (suite *WebsocketSuite) TestSSLTermination(c *check.C) {
|
||||
var upgrader = gorillawebsocket.Upgrader{} // use default options
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
for {
|
||||
mt, message, err := c.ReadMessage()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
err = c.WriteMessage(mt, message)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}))
|
||||
file := suite.adaptFile(c, "fixtures/websocket/config_https.toml", struct {
|
||||
WebsocketServer string
|
||||
}{
|
||||
WebsocketServer: srv.URL,
|
||||
})
|
||||
|
||||
defer os.Remove(file)
|
||||
cmd, _ := suite.cmdTraefik(withConfigFile(file), "--debug")
|
||||
|
||||
err := cmd.Start()
|
||||
c.Assert(err, check.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
// wait for traefik
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 10*time.Second, try.BodyContains("127.0.0.1"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
//Add client self-signed cert
|
||||
roots := x509.NewCertPool()
|
||||
certContent, err := ioutil.ReadFile("./resources/tls/local.cert")
|
||||
roots.AppendCertsFromPEM(certContent)
|
||||
gorillawebsocket.DefaultDialer.TLSClientConfig = &tls.Config{
|
||||
RootCAs: roots,
|
||||
}
|
||||
conn, _, err := gorillawebsocket.DefaultDialer.Dial("wss://127.0.0.1:8000/ws", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = conn.WriteMessage(gorillawebsocket.TextMessage, []byte("OK"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
_, msg, err := conn.ReadMessage()
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(string(msg), checker.Equals, "OK")
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ var (
|
|||
|
||||
func init() {
|
||||
logger = logrus.StandardLogger().WithFields(logrus.Fields{})
|
||||
logrus.SetOutput(os.Stdout)
|
||||
}
|
||||
|
||||
// Context sets the Context of the logger
|
||||
|
|
|
@ -9,7 +9,10 @@ import (
|
|||
)
|
||||
|
||||
// default format for time presentation
|
||||
const commonLogTimeFormat = "02/Jan/2006:15:04:05 -0700"
|
||||
const (
|
||||
commonLogTimeFormat = "02/Jan/2006:15:04:05 -0700"
|
||||
defaultValue = "-"
|
||||
)
|
||||
|
||||
// CommonLogFormatter provides formatting in the Traefik common log format
|
||||
type CommonLogFormatter struct{}
|
||||
|
@ -21,27 +24,26 @@ func (f *CommonLogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
|||
timestamp := entry.Data[StartUTC].(time.Time).Format(commonLogTimeFormat)
|
||||
elapsedMillis := entry.Data[Duration].(time.Duration).Nanoseconds() / 1000000
|
||||
|
||||
_, err := fmt.Fprintf(b, "%s - %s [%s] \"%s %s %s\" %d %d %s %s %d %s %s %dms\n",
|
||||
_, err := fmt.Fprintf(b, "%s - %s [%s] \"%s %s %s\" %v %v %s %s %v %s %s %dms\n",
|
||||
entry.Data[ClientHost],
|
||||
entry.Data[ClientUsername],
|
||||
timestamp,
|
||||
entry.Data[RequestMethod],
|
||||
entry.Data[RequestPath],
|
||||
entry.Data[RequestProtocol],
|
||||
entry.Data[OriginStatus],
|
||||
entry.Data[OriginContentSize],
|
||||
toLogString(entry.Data["request_Referer"]),
|
||||
toLogString(entry.Data["request_User-Agent"]),
|
||||
entry.Data[RequestCount],
|
||||
toLogString(entry.Data[FrontendName]),
|
||||
toLogString(entry.Data[BackendURL]),
|
||||
toLog(entry.Data[OriginStatus]),
|
||||
toLog(entry.Data[OriginContentSize]),
|
||||
toLog(entry.Data["request_Referer"]),
|
||||
toLog(entry.Data["request_User-Agent"]),
|
||||
toLog(entry.Data[RequestCount]),
|
||||
toLog(entry.Data[FrontendName]),
|
||||
toLog(entry.Data[BackendURL]),
|
||||
elapsedMillis)
|
||||
|
||||
return b.Bytes(), err
|
||||
}
|
||||
|
||||
func toLogString(v interface{}) string {
|
||||
defaultValue := "-"
|
||||
func toLog(v interface{}) interface{} {
|
||||
if v == nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
@ -54,7 +56,7 @@ func toLogString(v interface{}) string {
|
|||
return quoted(s.String(), defaultValue)
|
||||
|
||||
default:
|
||||
return defaultValue
|
||||
return v
|
||||
}
|
||||
|
||||
}
|
||||
|
|
114
middlewares/accesslog/logger_formatters_test.go
Normal file
114
middlewares/accesslog/logger_formatters_test.go
Normal file
|
@ -0,0 +1,114 @@
|
|||
package accesslog
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCommonLogFormatter_Format(t *testing.T) {
|
||||
clf := CommonLogFormatter{}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
data map[string]interface{}
|
||||
expectedLog string
|
||||
}{
|
||||
{
|
||||
name: "OriginStatus & OriginContentSize are nil",
|
||||
data: map[string]interface{}{
|
||||
StartUTC: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
|
||||
Duration: 123 * time.Second,
|
||||
ClientHost: "10.0.0.1",
|
||||
ClientUsername: "Client",
|
||||
RequestMethod: http.MethodGet,
|
||||
RequestPath: "/foo",
|
||||
RequestProtocol: "http",
|
||||
OriginStatus: nil,
|
||||
OriginContentSize: nil,
|
||||
"request_Referer": "",
|
||||
"request_User-Agent": "",
|
||||
RequestCount: 0,
|
||||
FrontendName: "",
|
||||
BackendURL: "",
|
||||
},
|
||||
expectedLog: `10.0.0.1 - Client [10/Nov/2009:23:00:00 +0000] "GET /foo http" - - - - 0 - - 123000ms
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "all data",
|
||||
data: map[string]interface{}{
|
||||
StartUTC: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
|
||||
Duration: 123 * time.Second,
|
||||
ClientHost: "10.0.0.1",
|
||||
ClientUsername: "Client",
|
||||
RequestMethod: http.MethodGet,
|
||||
RequestPath: "/foo",
|
||||
RequestProtocol: "http",
|
||||
OriginStatus: 123,
|
||||
OriginContentSize: 132,
|
||||
"request_Referer": "referer",
|
||||
"request_User-Agent": "agent",
|
||||
RequestCount: nil,
|
||||
FrontendName: "foo",
|
||||
BackendURL: "http://10.0.0.2/toto",
|
||||
},
|
||||
expectedLog: `10.0.0.1 - Client [10/Nov/2009:23:00:00 +0000] "GET /foo http" 123 132 "referer" "agent" - "foo" "http://10.0.0.2/toto" 123000ms
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
entry := &logrus.Entry{Data: test.data}
|
||||
|
||||
raw, err := clf.Format(entry)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expectedLog, string(raw))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Test_toLog(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
value interface{}
|
||||
expectedLog interface{}
|
||||
}{
|
||||
{
|
||||
name: "",
|
||||
value: 1,
|
||||
expectedLog: 1,
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
value: "foo",
|
||||
expectedLog: `"foo"`,
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
value: nil,
|
||||
expectedLog: "-",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
lg := toLog(test.value)
|
||||
|
||||
assert.Equal(t, test.expectedLog, lg)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -4,7 +4,9 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/metrics"
|
||||
gokitmetrics "github.com/go-kit/kit/metrics"
|
||||
)
|
||||
|
@ -17,7 +19,7 @@ type MetricsWrapper struct {
|
|||
}
|
||||
|
||||
// NewMetricsWrapper return a MetricsWrapper struct with
|
||||
// a given Metrics implementation e.g Prometheuss
|
||||
// a given Metrics implementation
|
||||
func NewMetricsWrapper(registry metrics.Registry, service string) *MetricsWrapper {
|
||||
var metricsWrapper = MetricsWrapper{
|
||||
registry: registry,
|
||||
|
@ -32,7 +34,7 @@ func (m *MetricsWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Request, next
|
|||
prw := &responseRecorder{rw, http.StatusOK}
|
||||
next(prw, r)
|
||||
|
||||
reqLabels := []string{"service", m.serviceName, "code", strconv.Itoa(prw.statusCode), "method", r.Method}
|
||||
reqLabels := []string{"service", m.serviceName, "code", strconv.Itoa(prw.statusCode), "method", getMethod(r)}
|
||||
m.registry.ReqsCounter().With(reqLabels...).Add(1)
|
||||
|
||||
reqDurationLabels := []string{"service", m.serviceName, "code", strconv.Itoa(prw.statusCode)}
|
||||
|
@ -48,6 +50,14 @@ func NewMetricsRetryListener(retryMetrics retryMetrics, backendName string) Retr
|
|||
return &MetricsRetryListener{retryMetrics: retryMetrics, backendName: backendName}
|
||||
}
|
||||
|
||||
func getMethod(r *http.Request) string {
|
||||
if !utf8.ValidString(r.Method) {
|
||||
log.Warnf("Invalid HTTP method encoding: %s", r.Method)
|
||||
return "NON_UTF8_HTTP_METHOD"
|
||||
}
|
||||
return r.Method
|
||||
}
|
||||
|
||||
// MetricsRetryListener is an implementation of the RetryListener interface to
|
||||
// record RequestMetrics about retry attempts.
|
||||
type MetricsRetryListener struct {
|
||||
|
|
|
@ -77,7 +77,7 @@ func (a nodeSorter) Less(i int, j int) bool {
|
|||
return lentr.Service.Port < rentr.Service.Port
|
||||
}
|
||||
|
||||
func getChangedKeys(currState map[string][]string, prevState map[string][]string) ([]string, []string) {
|
||||
func getChangedServiceKeys(currState map[string]Service, prevState map[string]Service) ([]string, []string) {
|
||||
currKeySet := fun.Set(fun.Keys(currState).([]string)).(map[string]bool)
|
||||
prevKeySet := fun.Set(fun.Keys(prevState).([]string)).(map[string]bool)
|
||||
|
||||
|
@ -87,13 +87,36 @@ func getChangedKeys(currState map[string][]string, prevState map[string][]string
|
|||
return fun.Keys(addedKeys).([]string), fun.Keys(removedKeys).([]string)
|
||||
}
|
||||
|
||||
func getChangedServiceNodeKeys(currState map[string]Service, prevState map[string]Service) ([]string, []string) {
|
||||
var addedNodeKeys []string
|
||||
var removedNodeKeys []string
|
||||
for key, value := range currState {
|
||||
if prevValue, ok := prevState[key]; ok {
|
||||
addedKeys, removedKeys := getChangedHealthyKeys(value.Nodes, prevValue.Nodes)
|
||||
addedNodeKeys = append(addedKeys)
|
||||
removedNodeKeys = append(removedKeys)
|
||||
}
|
||||
}
|
||||
return addedNodeKeys, removedNodeKeys
|
||||
}
|
||||
|
||||
func getChangedHealthyKeys(currState []string, prevState []string) ([]string, []string) {
|
||||
currKeySet := fun.Set(currState).(map[string]bool)
|
||||
prevKeySet := fun.Set(prevState).(map[string]bool)
|
||||
|
||||
addedKeys := fun.Difference(currKeySet, prevKeySet).(map[string]bool)
|
||||
removedKeys := fun.Difference(prevKeySet, currKeySet).(map[string]bool)
|
||||
|
||||
return fun.Keys(addedKeys).([]string), fun.Keys(removedKeys).([]string)
|
||||
}
|
||||
|
||||
func (p *CatalogProvider) watchHealthState(stopCh <-chan struct{}, watchCh chan<- map[string][]string) {
|
||||
health := p.client.Health()
|
||||
catalog := p.client.Catalog()
|
||||
|
||||
safe.Go(func() {
|
||||
// variable to hold previous state
|
||||
var flashback map[string][]string
|
||||
var flashback []string
|
||||
|
||||
options := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
|
||||
|
||||
|
@ -105,14 +128,20 @@ func (p *CatalogProvider) watchHealthState(stopCh <-chan struct{}, watchCh chan<
|
|||
}
|
||||
|
||||
// Listening to changes that leads to `passing` state or degrades from it.
|
||||
// The call is used just as a trigger for further actions
|
||||
// (intentionally there is no interest in the received data).
|
||||
_, meta, err := health.State("passing", options)
|
||||
healthyState, meta, err := health.State("passing", options)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to retrieve health checks")
|
||||
return
|
||||
}
|
||||
|
||||
var current []string
|
||||
if healthyState != nil {
|
||||
for _, healthy := range healthyState {
|
||||
current = append(current, healthy.ServiceID)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If LastIndex didn't change then it means `Get` returned
|
||||
// because of the WaitTime and the key didn't changed.
|
||||
if options.WaitIndex == meta.LastIndex {
|
||||
|
@ -132,30 +161,38 @@ func (p *CatalogProvider) watchHealthState(stopCh <-chan struct{}, watchCh chan<
|
|||
// A critical note is that the return of a blocking request is no guarantee of a change.
|
||||
// It is possible that there was an idempotent write that does not affect the result of the query.
|
||||
// Thus it is required to do extra check for changes...
|
||||
addedKeys, removedKeys := getChangedKeys(data, flashback)
|
||||
addedKeys, removedKeys := getChangedHealthyKeys(current, flashback)
|
||||
|
||||
if len(addedKeys) > 0 {
|
||||
log.WithField("DiscoveredServices", addedKeys).Debug("Health State change detected.")
|
||||
watchCh <- data
|
||||
flashback = data
|
||||
flashback = current
|
||||
}
|
||||
|
||||
if len(removedKeys) > 0 {
|
||||
log.WithField("MissingServices", removedKeys).Debug("Health State change detected.")
|
||||
watchCh <- data
|
||||
flashback = data
|
||||
flashback = current
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Service represent a Consul service.
|
||||
type Service struct {
|
||||
Name string
|
||||
Tags []string
|
||||
Nodes []string
|
||||
}
|
||||
|
||||
func (p *CatalogProvider) watchCatalogServices(stopCh <-chan struct{}, watchCh chan<- map[string][]string) {
|
||||
catalog := p.client.Catalog()
|
||||
|
||||
safe.Go(func() {
|
||||
current := make(map[string]Service)
|
||||
// variable to hold previous state
|
||||
var flashback map[string][]string
|
||||
var flashback map[string]Service
|
||||
|
||||
options := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
|
||||
|
||||
|
@ -179,26 +216,55 @@ func (p *CatalogProvider) watchCatalogServices(stopCh <-chan struct{}, watchCh c
|
|||
options.WaitIndex = meta.LastIndex
|
||||
|
||||
if data != nil {
|
||||
|
||||
for key, value := range data {
|
||||
nodes, _, err := catalog.Service(key, "", options)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get detail of service %s: %s", key, err)
|
||||
return
|
||||
}
|
||||
nodesID := getServiceIds(nodes)
|
||||
if service, ok := current[key]; ok {
|
||||
service.Tags = value
|
||||
service.Nodes = nodesID
|
||||
} else {
|
||||
service := Service{
|
||||
Name: key,
|
||||
Tags: value,
|
||||
Nodes: nodesID,
|
||||
}
|
||||
current[key] = service
|
||||
}
|
||||
}
|
||||
// A critical note is that the return of a blocking request is no guarantee of a change.
|
||||
// It is possible that there was an idempotent write that does not affect the result of the query.
|
||||
// Thus it is required to do extra check for changes...
|
||||
addedKeys, removedKeys := getChangedKeys(data, flashback)
|
||||
addedServiceKeys, removedServiceKeys := getChangedServiceKeys(current, flashback)
|
||||
|
||||
if len(addedKeys) > 0 {
|
||||
log.WithField("DiscoveredServices", addedKeys).Debug("Catalog Services change detected.")
|
||||
addedServiceNodeKeys, removedServiceNodeKeys := getChangedServiceNodeKeys(current, flashback)
|
||||
|
||||
if len(addedServiceKeys) > 0 || len(addedServiceNodeKeys) > 0 {
|
||||
log.WithField("DiscoveredServices", addedServiceKeys).Debug("Catalog Services change detected.")
|
||||
watchCh <- data
|
||||
flashback = data
|
||||
flashback = current
|
||||
}
|
||||
|
||||
if len(removedKeys) > 0 {
|
||||
log.WithField("MissingServices", removedKeys).Debug("Catalog Services change detected.")
|
||||
if len(removedServiceKeys) > 0 || len(removedServiceNodeKeys) > 0 {
|
||||
log.WithField("MissingServices", removedServiceKeys).Debug("Catalog Services change detected.")
|
||||
watchCh <- data
|
||||
flashback = data
|
||||
flashback = current
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
func getServiceIds(services []*api.CatalogService) []string {
|
||||
var serviceIds []string
|
||||
for _, service := range services {
|
||||
serviceIds = append(serviceIds, service.ServiceID)
|
||||
}
|
||||
return serviceIds
|
||||
}
|
||||
|
||||
func (p *CatalogProvider) healthyNodes(service string) (catalogUpdate, error) {
|
||||
health := p.client.Health()
|
||||
|
@ -330,6 +396,14 @@ func (p *CatalogProvider) getAttribute(name string, tags []string, defaultValue
|
|||
return p.getTag(p.getPrefixedName(name), tags, defaultValue)
|
||||
}
|
||||
|
||||
func (p *CatalogProvider) getBasicAuth(tags []string) []string {
|
||||
list := p.getAttribute("frontend.auth.basic", tags, "")
|
||||
if list != "" {
|
||||
return strings.Split(list, ",")
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (p *CatalogProvider) hasTag(name string, tags []string) bool {
|
||||
// Very-very unlikely that a Consul tag would ever start with '=!='
|
||||
tag := p.getTag(name, tags, "=!=")
|
||||
|
@ -377,6 +451,7 @@ func (p *CatalogProvider) buildConfig(catalog []catalogUpdate) *types.Configurat
|
|||
"getBackendName": p.getBackendName,
|
||||
"getBackendAddress": p.getBackendAddress,
|
||||
"getAttribute": p.getAttribute,
|
||||
"getBasicAuth": p.getBasicAuth,
|
||||
"getTag": p.getTag,
|
||||
"hasTag": p.hasTag,
|
||||
"getEntryPoints": p.getEntryPoints,
|
||||
|
|
|
@ -348,6 +348,7 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
|
|||
"random.foo=bar",
|
||||
"traefik.backend.maxconn.amount=1000",
|
||||
"traefik.backend.maxconn.extractorfunc=client.ip",
|
||||
"traefik.frontend.auth.basic=test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
},
|
||||
},
|
||||
Nodes: []*api.ServiceEntry{
|
||||
|
@ -380,6 +381,7 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
|
|||
Rule: "Host:test.localhost",
|
||||
},
|
||||
},
|
||||
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
|
||||
},
|
||||
},
|
||||
expectedBackends: map[string]*types.Backend{
|
||||
|
@ -411,6 +413,7 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
|
|||
t.Fatalf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends)
|
||||
}
|
||||
if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) {
|
||||
t.Fatalf("expected %#v, got %#v", c.expectedFrontends["frontend-test"].BasicAuth, actualConfig.Frontends["frontend-test"].BasicAuth)
|
||||
t.Fatalf("expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends)
|
||||
}
|
||||
}
|
||||
|
@ -610,8 +613,8 @@ func TestConsulCatalogNodeSorter(t *testing.T) {
|
|||
|
||||
func TestConsulCatalogGetChangedKeys(t *testing.T) {
|
||||
type Input struct {
|
||||
currState map[string][]string
|
||||
prevState map[string][]string
|
||||
currState map[string]Service
|
||||
prevState map[string]Service
|
||||
}
|
||||
|
||||
type Output struct {
|
||||
|
@ -625,37 +628,37 @@ func TestConsulCatalogGetChangedKeys(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
input: Input{
|
||||
currState: map[string][]string{
|
||||
"foo-service": {"v1"},
|
||||
"bar-service": {"v1"},
|
||||
"baz-service": {"v1"},
|
||||
"qux-service": {"v1"},
|
||||
"quux-service": {"v1"},
|
||||
"quuz-service": {"v1"},
|
||||
"corge-service": {"v1"},
|
||||
"grault-service": {"v1"},
|
||||
"garply-service": {"v1"},
|
||||
"waldo-service": {"v1"},
|
||||
"fred-service": {"v1"},
|
||||
"plugh-service": {"v1"},
|
||||
"xyzzy-service": {"v1"},
|
||||
"thud-service": {"v1"},
|
||||
currState: map[string]Service{
|
||||
"foo-service": {Name: "v1"},
|
||||
"bar-service": {Name: "v1"},
|
||||
"baz-service": {Name: "v1"},
|
||||
"qux-service": {Name: "v1"},
|
||||
"quux-service": {Name: "v1"},
|
||||
"quuz-service": {Name: "v1"},
|
||||
"corge-service": {Name: "v1"},
|
||||
"grault-service": {Name: "v1"},
|
||||
"garply-service": {Name: "v1"},
|
||||
"waldo-service": {Name: "v1"},
|
||||
"fred-service": {Name: "v1"},
|
||||
"plugh-service": {Name: "v1"},
|
||||
"xyzzy-service": {Name: "v1"},
|
||||
"thud-service": {Name: "v1"},
|
||||
},
|
||||
prevState: map[string][]string{
|
||||
"foo-service": {"v1"},
|
||||
"bar-service": {"v1"},
|
||||
"baz-service": {"v1"},
|
||||
"qux-service": {"v1"},
|
||||
"quux-service": {"v1"},
|
||||
"quuz-service": {"v1"},
|
||||
"corge-service": {"v1"},
|
||||
"grault-service": {"v1"},
|
||||
"garply-service": {"v1"},
|
||||
"waldo-service": {"v1"},
|
||||
"fred-service": {"v1"},
|
||||
"plugh-service": {"v1"},
|
||||
"xyzzy-service": {"v1"},
|
||||
"thud-service": {"v1"},
|
||||
prevState: map[string]Service{
|
||||
"foo-service": {Name: "v1"},
|
||||
"bar-service": {Name: "v1"},
|
||||
"baz-service": {Name: "v1"},
|
||||
"qux-service": {Name: "v1"},
|
||||
"quux-service": {Name: "v1"},
|
||||
"quuz-service": {Name: "v1"},
|
||||
"corge-service": {Name: "v1"},
|
||||
"grault-service": {Name: "v1"},
|
||||
"garply-service": {Name: "v1"},
|
||||
"waldo-service": {Name: "v1"},
|
||||
"fred-service": {Name: "v1"},
|
||||
"plugh-service": {Name: "v1"},
|
||||
"xyzzy-service": {Name: "v1"},
|
||||
"thud-service": {Name: "v1"},
|
||||
},
|
||||
},
|
||||
output: Output{
|
||||
|
@ -665,34 +668,34 @@ func TestConsulCatalogGetChangedKeys(t *testing.T) {
|
|||
},
|
||||
{
|
||||
input: Input{
|
||||
currState: map[string][]string{
|
||||
"foo-service": {"v1"},
|
||||
"bar-service": {"v1"},
|
||||
"baz-service": {"v1"},
|
||||
"qux-service": {"v1"},
|
||||
"quux-service": {"v1"},
|
||||
"quuz-service": {"v1"},
|
||||
"corge-service": {"v1"},
|
||||
"grault-service": {"v1"},
|
||||
"garply-service": {"v1"},
|
||||
"waldo-service": {"v1"},
|
||||
"fred-service": {"v1"},
|
||||
"plugh-service": {"v1"},
|
||||
"xyzzy-service": {"v1"},
|
||||
"thud-service": {"v1"},
|
||||
currState: map[string]Service{
|
||||
"foo-service": {Name: "v1"},
|
||||
"bar-service": {Name: "v1"},
|
||||
"baz-service": {Name: "v1"},
|
||||
"qux-service": {Name: "v1"},
|
||||
"quux-service": {Name: "v1"},
|
||||
"quuz-service": {Name: "v1"},
|
||||
"corge-service": {Name: "v1"},
|
||||
"grault-service": {Name: "v1"},
|
||||
"garply-service": {Name: "v1"},
|
||||
"waldo-service": {Name: "v1"},
|
||||
"fred-service": {Name: "v1"},
|
||||
"plugh-service": {Name: "v1"},
|
||||
"xyzzy-service": {Name: "v1"},
|
||||
"thud-service": {Name: "v1"},
|
||||
},
|
||||
prevState: map[string][]string{
|
||||
"foo-service": {"v1"},
|
||||
"bar-service": {"v1"},
|
||||
"baz-service": {"v1"},
|
||||
"corge-service": {"v1"},
|
||||
"grault-service": {"v1"},
|
||||
"garply-service": {"v1"},
|
||||
"waldo-service": {"v1"},
|
||||
"fred-service": {"v1"},
|
||||
"plugh-service": {"v1"},
|
||||
"xyzzy-service": {"v1"},
|
||||
"thud-service": {"v1"},
|
||||
prevState: map[string]Service{
|
||||
"foo-service": {Name: "v1"},
|
||||
"bar-service": {Name: "v1"},
|
||||
"baz-service": {Name: "v1"},
|
||||
"corge-service": {Name: "v1"},
|
||||
"grault-service": {Name: "v1"},
|
||||
"garply-service": {Name: "v1"},
|
||||
"waldo-service": {Name: "v1"},
|
||||
"fred-service": {Name: "v1"},
|
||||
"plugh-service": {Name: "v1"},
|
||||
"xyzzy-service": {Name: "v1"},
|
||||
"thud-service": {Name: "v1"},
|
||||
},
|
||||
},
|
||||
output: Output{
|
||||
|
@ -702,33 +705,33 @@ func TestConsulCatalogGetChangedKeys(t *testing.T) {
|
|||
},
|
||||
{
|
||||
input: Input{
|
||||
currState: map[string][]string{
|
||||
"foo-service": {"v1"},
|
||||
"qux-service": {"v1"},
|
||||
"quux-service": {"v1"},
|
||||
"quuz-service": {"v1"},
|
||||
"corge-service": {"v1"},
|
||||
"grault-service": {"v1"},
|
||||
"garply-service": {"v1"},
|
||||
"waldo-service": {"v1"},
|
||||
"fred-service": {"v1"},
|
||||
"plugh-service": {"v1"},
|
||||
"xyzzy-service": {"v1"},
|
||||
"thud-service": {"v1"},
|
||||
currState: map[string]Service{
|
||||
"foo-service": {Name: "v1"},
|
||||
"qux-service": {Name: "v1"},
|
||||
"quux-service": {Name: "v1"},
|
||||
"quuz-service": {Name: "v1"},
|
||||
"corge-service": {Name: "v1"},
|
||||
"grault-service": {Name: "v1"},
|
||||
"garply-service": {Name: "v1"},
|
||||
"waldo-service": {Name: "v1"},
|
||||
"fred-service": {Name: "v1"},
|
||||
"plugh-service": {Name: "v1"},
|
||||
"xyzzy-service": {Name: "v1"},
|
||||
"thud-service": {Name: "v1"},
|
||||
},
|
||||
prevState: map[string][]string{
|
||||
"foo-service": {"v1"},
|
||||
"bar-service": {"v1"},
|
||||
"baz-service": {"v1"},
|
||||
"qux-service": {"v1"},
|
||||
"quux-service": {"v1"},
|
||||
"quuz-service": {"v1"},
|
||||
"corge-service": {"v1"},
|
||||
"waldo-service": {"v1"},
|
||||
"fred-service": {"v1"},
|
||||
"plugh-service": {"v1"},
|
||||
"xyzzy-service": {"v1"},
|
||||
"thud-service": {"v1"},
|
||||
prevState: map[string]Service{
|
||||
"foo-service": {Name: "v1"},
|
||||
"bar-service": {Name: "v1"},
|
||||
"baz-service": {Name: "v1"},
|
||||
"qux-service": {Name: "v1"},
|
||||
"quux-service": {Name: "v1"},
|
||||
"quuz-service": {Name: "v1"},
|
||||
"corge-service": {Name: "v1"},
|
||||
"waldo-service": {Name: "v1"},
|
||||
"fred-service": {Name: "v1"},
|
||||
"plugh-service": {Name: "v1"},
|
||||
"xyzzy-service": {Name: "v1"},
|
||||
"thud-service": {Name: "v1"},
|
||||
},
|
||||
},
|
||||
output: Output{
|
||||
|
@ -739,7 +742,7 @@ func TestConsulCatalogGetChangedKeys(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, c := range cases {
|
||||
addedKeys, removedKeys := getChangedKeys(c.input.currState, c.input.prevState)
|
||||
addedKeys, removedKeys := getChangedServiceKeys(c.input.currState, c.input.prevState)
|
||||
|
||||
if !reflect.DeepEqual(fun.Set(addedKeys), fun.Set(c.output.addedKeys)) {
|
||||
t.Fatalf("Added keys comparison results: got %q, want %q", addedKeys, c.output.addedKeys)
|
||||
|
@ -853,3 +856,38 @@ func TestConsulCatalogFilterEnabled(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConsulCatalogGetBasicAuth(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
tags []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
desc: "label missing",
|
||||
tags: []string{},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
desc: "label existing",
|
||||
tags: []string{
|
||||
"traefik.frontend.auth.basic=user:password",
|
||||
},
|
||||
expected: []string{"user:password"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
c := c
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
provider := &CatalogProvider{
|
||||
Prefix: "traefik",
|
||||
}
|
||||
actual := provider.getBasicAuth(c.tags)
|
||||
if !reflect.DeepEqual(actual, c.expected) {
|
||||
t.Errorf("actual %q, expected %q", actual, c.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -182,6 +182,7 @@ func (p *Provider) loadECSConfig(ctx context.Context, client *awsClient) (*types
|
|||
var ecsFuncMap = template.FuncMap{
|
||||
"filterFrontends": p.filterFrontends,
|
||||
"getFrontendRule": p.getFrontendRule,
|
||||
"getBasicAuth": p.getBasicAuth,
|
||||
"getLoadBalancerSticky": p.getLoadBalancerSticky,
|
||||
"getLoadBalancerMethod": p.getLoadBalancerMethod,
|
||||
}
|
||||
|
@ -469,6 +470,14 @@ func (p *Provider) getFrontendRule(i ecsInstance) string {
|
|||
return "Host:" + strings.ToLower(strings.Replace(i.Name, "_", "-", -1)) + "." + p.Domain
|
||||
}
|
||||
|
||||
func (p *Provider) getBasicAuth(i ecsInstance) []string {
|
||||
label := p.label(i, types.LabelFrontendAuthBasic)
|
||||
if label != "" {
|
||||
return strings.Split(label, ",")
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (p *Provider) getLoadBalancerSticky(instances []ecsInstance) string {
|
||||
if len(instances) > 0 {
|
||||
label := p.label(instances[0], types.LabelBackendLoadbalancerSticky)
|
||||
|
|
|
@ -521,3 +521,34 @@ func TestTaskChunking(t *testing.T) {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
func TestEcsGetBasicAuth(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
instance ecsInstance
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
desc: "label missing",
|
||||
instance: simpleEcsInstance(map[string]*string{}),
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
desc: "label existing",
|
||||
instance: simpleEcsInstance(map[string]*string{
|
||||
types.LabelFrontendAuthBasic: aws.String("user:password"),
|
||||
}),
|
||||
expected: []string{"user:password"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
provider := &Provider{}
|
||||
actual := provider.getBasicAuth(test.instance)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,14 +125,14 @@ func (p *Provider) apiProvide(configurationChan chan<- types.ConfigMessage, pool
|
|||
return nil
|
||||
}
|
||||
|
||||
func listRancherEnvironments(client *rancher.RancherClient) []*rancher.Project {
|
||||
func listRancherEnvironments(client *rancher.RancherClient) []*rancher.Environment {
|
||||
|
||||
// Rancher Environment in frontend UI is actually project in API
|
||||
// Rancher Environment in frontend UI is actually a stack
|
||||
// https://forums.rancher.com/t/api-key-for-all-environments/279/9
|
||||
|
||||
var environmentList = []*rancher.Project{}
|
||||
var environmentList = []*rancher.Environment{}
|
||||
|
||||
environments, err := client.Project.List(nil)
|
||||
environments, err := client.Environment.List(nil)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Cannot get Rancher Environments %+v", err)
|
||||
|
@ -193,12 +193,13 @@ func listRancherContainer(client *rancher.RancherClient) []*rancher.Container {
|
|||
return containerList
|
||||
}
|
||||
|
||||
func parseAPISourcedRancherData(environments []*rancher.Project, services []*rancher.Service, containers []*rancher.Container) []rancherData {
|
||||
func parseAPISourcedRancherData(environments []*rancher.Environment, services []*rancher.Service, containers []*rancher.Container) []rancherData {
|
||||
var rancherDataList []rancherData
|
||||
|
||||
for _, environment := range environments {
|
||||
|
||||
for _, service := range services {
|
||||
|
||||
if service.EnvironmentId != environment.Id {
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -18,17 +18,19 @@ if [ -z "$DATE" ]; then
|
|||
DATE=$(date -u '+%Y-%m-%d_%I:%M:%S%p')
|
||||
fi
|
||||
|
||||
echo "Building ${VERSION} ${CODENAME} ${DATE}"
|
||||
|
||||
GIT_REPO_URL='github.com/containous/traefik/version'
|
||||
GO_BUILD_CMD="go build -ldflags"
|
||||
GO_BUILD_OPT="-s -w -X ${GIT_REPO_URL}.Version=$VERSION -X ${GIT_REPO_URL}.Codename=$CODENAME -X ${GIT_REPO_URL}.BuildDate=$DATE"
|
||||
GO_BUILD_OPT="-s -w -X ${GIT_REPO_URL}.Version=${VERSION} -X ${GIT_REPO_URL}.Codename=${CODENAME} -X ${GIT_REPO_URL}.BuildDate=${DATE}"
|
||||
|
||||
# Build 386 amd64 binaries
|
||||
OS_PLATFORM_ARG=(linux windows darwin)
|
||||
OS_ARCH_ARG=(amd64)
|
||||
for OS in ${OS_PLATFORM_ARG[@]}; do
|
||||
for ARCH in ${OS_ARCH_ARG[@]}; do
|
||||
echo "Building binary for $OS/$ARCH..."
|
||||
GOARCH=$ARCH GOOS=$OS CGO_ENABLED=0 $GO_BUILD_CMD "$GO_BUILD_OPT" -o "dist/traefik_$OS-$ARCH" ./cmd/traefik/
|
||||
echo "Building binary for ${OS}/${ARCH}..."
|
||||
GOARCH=${ARCH} GOOS=${OS} CGO_ENABLED=0 ${GO_BUILD_CMD} "${GO_BUILD_OPT}" -o "dist/traefik_${OS}-${ARCH}" ./cmd/traefik/
|
||||
done
|
||||
done
|
||||
|
||||
|
@ -37,7 +39,7 @@ OS_PLATFORM_ARG=(linux)
|
|||
OS_ARCH_ARG=(arm64)
|
||||
for OS in ${OS_PLATFORM_ARG[@]}; do
|
||||
for ARCH in ${OS_ARCH_ARG[@]}; do
|
||||
echo "Building binary for $OS/$ARCH..."
|
||||
GOARCH=$ARCH GOOS=$OS CGO_ENABLED=0 $GO_BUILD_CMD "$GO_BUILD_OPT" -o "dist/traefik_$OS-$ARCH" ./cmd/traefik/
|
||||
echo "Building binary for ${OS}/${ARCH}..."
|
||||
GOARCH=${ARCH} GOOS=${OS} CGO_ENABLED=0 ${GO_BUILD_CMD} "${GO_BUILD_OPT}" -o "dist/traefik_${OS}-${ARCH}" ./cmd/traefik/
|
||||
done
|
||||
done
|
||||
|
|
|
@ -18,9 +18,11 @@ if [ -z "$DATE" ]; then
|
|||
DATE=$(date -u '+%Y-%m-%d_%I:%M:%S%p')
|
||||
fi
|
||||
|
||||
echo "Building ${VERSION} ${CODENAME} ${DATE}"
|
||||
|
||||
GIT_REPO_URL='github.com/containous/traefik/version'
|
||||
GO_BUILD_CMD="go build -ldflags"
|
||||
GO_BUILD_OPT="-s -w -X ${GIT_REPO_URL}.Version=$VERSION -X ${GIT_REPO_URL}.Codename=$CODENAME -X ${GIT_REPO_URL}.BuildDate=$DATE"
|
||||
GO_BUILD_OPT="-s -w -X ${GIT_REPO_URL}.Version=${VERSION} -X ${GIT_REPO_URL}.Codename=${CODENAME} -X ${GIT_REPO_URL}.BuildDate=${DATE}"
|
||||
|
||||
# Build arm binaries
|
||||
OS_PLATFORM_ARG=(linux windows darwin)
|
||||
|
@ -28,7 +30,7 @@ OS_ARCH_ARG=(386)
|
|||
for OS in ${OS_PLATFORM_ARG[@]}; do
|
||||
for ARCH in ${OS_ARCH_ARG[@]}; do
|
||||
echo "Building binary for $OS/$ARCH..."
|
||||
GOARCH=$ARCH GOOS=$OS CGO_ENABLED=0 $GO_BUILD_CMD "$GO_BUILD_OPT" -o "dist/traefik_$OS-$ARCH" ./cmd/traefik/
|
||||
GOARCH=${ARCH} GOOS=${OS} CGO_ENABLED=0 ${GO_BUILD_CMD} "$GO_BUILD_OPT" -o "dist/traefik_$OS-$ARCH" ./cmd/traefik/
|
||||
done
|
||||
done
|
||||
|
||||
|
@ -38,18 +40,21 @@ OS_ARCH_ARG=(386 amd64)
|
|||
for OS in ${OS_PLATFORM_ARG[@]}; do
|
||||
for ARCH in ${OS_ARCH_ARG[@]}; do
|
||||
# Get rid of existing binaries
|
||||
rm -f dist/traefik_$OS-$ARCH
|
||||
rm -f dist/traefik_${OS}-${ARCH}
|
||||
echo "Building binary for $OS/$ARCH..."
|
||||
GOARCH=$ARCH GOOS=$OS CGO_ENABLED=0 $GO_BUILD_CMD "$GO_BUILD_OPT" -o "dist/traefik_$OS-$ARCH" ./cmd/traefik/
|
||||
GOARCH=${ARCH} GOOS=${OS} CGO_ENABLED=0 ${GO_BUILD_CMD} "$GO_BUILD_OPT" -o "dist/traefik_$OS-$ARCH" ./cmd/traefik/
|
||||
done
|
||||
done
|
||||
|
||||
# Build arm binaries
|
||||
OS_PLATFORM_ARG=(linux)
|
||||
OS_ARCH_ARG=(arm)
|
||||
ARM_ARG=(6)
|
||||
for OS in ${OS_PLATFORM_ARG[@]}; do
|
||||
for ARCH in ${OS_ARCH_ARG[@]}; do
|
||||
echo "Building binary for $OS/$ARCH..."
|
||||
GOARCH=$ARCH GOOS=$OS CGO_ENABLED=0 $GO_BUILD_CMD "$GO_BUILD_OPT" -o "dist/traefik_$OS-$ARCH" ./cmd/traefik/
|
||||
for ARM in ${ARM_ARG[@]}; do
|
||||
echo "Building binary for $OS/${ARCH}32v${ARM}..."
|
||||
GOARCH=${ARCH} GOOS=${OS} GOARM=${ARM} CGO_ENABLED=0 ${GO_BUILD_CMD} "$GO_BUILD_OPT" -o "dist/traefik_$OS-${ARCH}" ./cmd/traefik/
|
||||
done
|
||||
done
|
||||
done
|
||||
|
|
|
@ -17,7 +17,6 @@ find_dirs() {
|
|||
find . -not \( \
|
||||
\( \
|
||||
-path './integration/*' \
|
||||
-o -path './.glide/*' \
|
||||
-o -path './vendor/*' \
|
||||
-o -path './.git/*' \
|
||||
\) \
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
source "$(dirname "$BASH_SOURCE")/.validate"
|
||||
|
||||
IFS=$'\n'
|
||||
files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^\(integration/\)\?vendor/' || true) )
|
||||
files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true) )
|
||||
unset IFS
|
||||
|
||||
badFiles=()
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
source "$(dirname "$BASH_SOURCE")/.validate"
|
||||
|
||||
IFS=$'\n'
|
||||
files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^\(integration/\)\?vendor/\|autogen' || true) )
|
||||
files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/\|autogen' || true) )
|
||||
unset IFS
|
||||
|
||||
errors=()
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
source "$(dirname "$BASH_SOURCE")/.validate"
|
||||
|
||||
IFS=$'\n'
|
||||
files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^\(integration/\)\?vendor/' || true) )
|
||||
files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true) )
|
||||
unset IFS
|
||||
|
||||
errors=()
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
source "$(dirname "$BASH_SOURCE")/.validate"
|
||||
|
||||
IFS=$'\n'
|
||||
src=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^\(integration/\)\?vendor/\|autogen' || true) )
|
||||
src=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/\|autogen' || true) )
|
||||
docs=( $(validate_diff --diff-filter=ACMR --name-only -- 'docs/*.md') )
|
||||
unset IFS
|
||||
files=("${src[@]}" "${docs[@]}")
|
||||
|
|
|
@ -647,6 +647,7 @@ func (server *Server) prepareServer(entryPointName string, entryPoint *configura
|
|||
listener, err := net.Listen("tcp", entryPoint.Address)
|
||||
if err != nil {
|
||||
log.Error("Error opening listener ", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if entryPoint.ProxyProtocol {
|
||||
|
|
|
@ -96,7 +96,7 @@ func TestPrepareServerTimeouts(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
entryPointName := "http"
|
||||
entryPoint := &configuration.EntryPoint{Address: "localhost:8080"}
|
||||
entryPoint := &configuration.EntryPoint{Address: "localhost:0"}
|
||||
router := middlewares.NewHandlerSwitcher(mux.NewRouter())
|
||||
|
||||
srv := NewServer(test.globalConfig)
|
||||
|
@ -504,14 +504,14 @@ func TestServerEntrypointWhitelistConfig(t *testing.T) {
|
|||
{
|
||||
desc: "no whitelist middleware if no config on entrypoint",
|
||||
entrypoint: &configuration.EntryPoint{
|
||||
Address: ":8080",
|
||||
Address: ":0",
|
||||
},
|
||||
wantMiddleware: false,
|
||||
},
|
||||
{
|
||||
desc: "whitelist middleware should be added if configured on entrypoint",
|
||||
entrypoint: &configuration.EntryPoint{
|
||||
Address: ":8080",
|
||||
Address: ":0",
|
||||
WhitelistSourceRange: []string{
|
||||
"127.0.0.1/32",
|
||||
},
|
||||
|
|
|
@ -40,6 +40,9 @@
|
|||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
basicAuth = [{{range getBasicAuth .Attributes}}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
[frontends."frontend-{{.ServiceName}}".routes."route-host-{{.ServiceName}}"]
|
||||
rule = "{{getFrontendRule .}}"
|
||||
{{end}}
|
||||
|
|
|
@ -18,6 +18,9 @@
|
|||
priority = {{ getPriority .}}
|
||||
entryPoints = [{{range getEntryPoints .}}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
basicAuth = [{{range getBasicAuth .}}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
[frontends.frontend-{{ $serviceName }}.routes.route-frontend-{{ $serviceName }}]
|
||||
rule = "{{getFrontendRule .}}"
|
||||
|
|
Loading…
Reference in a new issue