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
|
# 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)
|
## [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)
|
[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 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))
|
- 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)
|
## [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)
|
[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 CI \
|
||||||
-e CONTAINER=DOCKER # Indicator for integration tests that we are running inside a container.
|
-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"
|
BIND_DIR := "dist"
|
||||||
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/containous/traefik/$(BIND_DIR)"
|
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
|
||||||
|
|
||||||
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:
|
Here is an example of backends and servers definition:
|
||||||
|
|
||||||
|
@ -497,9 +500,9 @@ Usage:
|
||||||
traefik [command] [--flag=flag_argument]
|
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.
|
- `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.
|
- `bug`: The easiest way to submit a pre-filled issue.
|
||||||
- `healthcheck`: Calls traefik `/ping` to check health.
|
- `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.passHostHeader=true` | Forward client `Host` header to the backend. |
|
||||||
| `traefik.frontend.priority=10` | Override default frontend priority |
|
| `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.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` |
|
||||||
|
|
|
@ -78,17 +78,18 @@ SecretAccessKey = "123"
|
||||||
|
|
||||||
Labels can be used on task containers to override default behaviour:
|
Labels can be used on task containers to override default behaviour:
|
||||||
|
|
||||||
| Label | Description |
|
| Label | Description |
|
||||||
|----------------------------------------------|------------------------------------------------------------------------------------------|
|
|---------------------------------------------------|------------------------------------------------------------------------------------------|
|
||||||
| `traefik.protocol=https` | override the default `http` protocol |
|
| `traefik.protocol=https` | override the default `http` protocol |
|
||||||
| `traefik.weight=10` | assign this weight to the container |
|
| `traefik.weight=10` | assign this weight to the container |
|
||||||
| `traefik.enable=false` | disable this container in Træfik |
|
| `traefik.enable=false` | disable this container in Træfik |
|
||||||
| `traefik.backend.loadbalancer.method=drr` | override the default `wrr` load balancer algorithm |
|
| `traefik.backend.loadbalancer.method=drr` | override the default `wrr` load balancer algorithm |
|
||||||
| `traefik.backend.loadbalancer.sticky=true` | enable backend sticky sessions |
|
| `traefik.backend.loadbalancer.sticky=true` | enable backend sticky sessions |
|
||||||
| `traefik.frontend.rule=Host:test.traefik.io` | override the default frontend rule (Default: `Host:{containerName}.{domain}`). |
|
| `traefik.frontend.rule=Host:test.traefik.io` | override the default frontend rule (Default: `Host:{containerName}.{domain}`). |
|
||||||
| `traefik.frontend.passHostHeader=true` | forward client `Host` header to the backend. |
|
| `traefik.frontend.passHostHeader=true` | forward client `Host` header to the backend. |
|
||||||
| `traefik.frontend.priority=10` | override default frontend priority |
|
| `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.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:
|
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",
|
"Version": "2012-10-17",
|
||||||
"Statement": [
|
"Statement": [
|
||||||
{
|
{
|
||||||
"Sid": "Traefik ECS read access",
|
"Sid": "TraefikECSReadAccess",
|
||||||
"Effect": "Allow",
|
"Effect": "Allow",
|
||||||
"Action": [
|
"Action": [
|
||||||
"ecs:ListClusters",
|
"ecs:ListClusters",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Clustering / High Availability (beta)
|
# 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.
|
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
|
## Prerequisites
|
||||||
|
|
|
@ -66,6 +66,30 @@ func (s *ConsulCatalogSuite) registerService(name string, address string, port i
|
||||||
return err
|
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 {
|
func (s *ConsulCatalogSuite) deregisterService(name string, address string) error {
|
||||||
catalog := s.consulClient.Catalog()
|
catalog := s.consulClient.Catalog()
|
||||||
_, err := catalog.Deregister(
|
_, err := catalog.Deregister(
|
||||||
|
@ -104,7 +128,7 @@ func (s *ConsulCatalogSuite) TestSingleService(c *check.C) {
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
defer cmd.Process.Kill()
|
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{})
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
||||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
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)
|
c.Assert(err, checker.IsNil)
|
||||||
req.Host = "test.consul.localhost"
|
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)
|
c.Assert(err, checker.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +153,7 @@ func (s *ConsulCatalogSuite) TestExposedByDefaultFalseSingleService(c *check.C)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
defer cmd.Process.Kill()
|
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{})
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
||||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
@ -154,7 +178,7 @@ func (s *ConsulCatalogSuite) TestExposedByDefaultFalseSimpleServiceMultipleNode(
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
defer cmd.Process.Kill()
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
nginx := s.composeProject.Container(c, "nginx")
|
nginx := s.composeProject.Container(c, "nginx1")
|
||||||
nginx2 := s.composeProject.Container(c, "nginx2")
|
nginx2 := s.composeProject.Container(c, "nginx2")
|
||||||
|
|
||||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
||||||
|
@ -184,14 +208,14 @@ func (s *ConsulCatalogSuite) TestExposedByDefaultTrueSimpleServiceMultipleNode(c
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
defer cmd.Process.Kill()
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
nginx := s.composeProject.Container(c, "nginx")
|
nginx := s.composeProject.Container(c, "nginx1")
|
||||||
nginx2 := s.composeProject.Container(c, "nginx2")
|
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"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
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"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
defer s.deregisterService("test", nginx2.NetworkSettings.IPAddress)
|
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())
|
err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||||
c.Assert(err, checker.IsNil)
|
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"]
|
defaultEntryPoints = ["http"]
|
||||||
logLevel = "DEBUG"
|
logLevel = "DEBUG"
|
||||||
|
|
||||||
|
[web]
|
||||||
|
address = ":8080"
|
||||||
|
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":8000"
|
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"
|
- "8301/udp"
|
||||||
- "8302"
|
- "8302"
|
||||||
- "8302/udp"
|
- "8302/udp"
|
||||||
nginx:
|
nginx1:
|
||||||
image: nginx:alpine
|
image: nginx:alpine
|
||||||
nginx2:
|
nginx2:
|
||||||
image: nginx:alpine
|
image: nginx:alpine
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
@ -232,3 +235,58 @@ func (suite *WebsocketSuite) TestWrongOriginIgnoredByServer(c *check.C) {
|
||||||
c.Assert(string(msg), checker.Equals, "OK")
|
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() {
|
func init() {
|
||||||
logger = logrus.StandardLogger().WithFields(logrus.Fields{})
|
logger = logrus.StandardLogger().WithFields(logrus.Fields{})
|
||||||
|
logrus.SetOutput(os.Stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context sets the Context of the logger
|
// Context sets the Context of the logger
|
||||||
|
|
|
@ -9,7 +9,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// default format for time presentation
|
// 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
|
// CommonLogFormatter provides formatting in the Traefik common log format
|
||||||
type CommonLogFormatter struct{}
|
type CommonLogFormatter struct{}
|
||||||
|
@ -21,27 +24,26 @@ func (f *CommonLogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||||
timestamp := entry.Data[StartUTC].(time.Time).Format(commonLogTimeFormat)
|
timestamp := entry.Data[StartUTC].(time.Time).Format(commonLogTimeFormat)
|
||||||
elapsedMillis := entry.Data[Duration].(time.Duration).Nanoseconds() / 1000000
|
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[ClientHost],
|
||||||
entry.Data[ClientUsername],
|
entry.Data[ClientUsername],
|
||||||
timestamp,
|
timestamp,
|
||||||
entry.Data[RequestMethod],
|
entry.Data[RequestMethod],
|
||||||
entry.Data[RequestPath],
|
entry.Data[RequestPath],
|
||||||
entry.Data[RequestProtocol],
|
entry.Data[RequestProtocol],
|
||||||
entry.Data[OriginStatus],
|
toLog(entry.Data[OriginStatus]),
|
||||||
entry.Data[OriginContentSize],
|
toLog(entry.Data[OriginContentSize]),
|
||||||
toLogString(entry.Data["request_Referer"]),
|
toLog(entry.Data["request_Referer"]),
|
||||||
toLogString(entry.Data["request_User-Agent"]),
|
toLog(entry.Data["request_User-Agent"]),
|
||||||
entry.Data[RequestCount],
|
toLog(entry.Data[RequestCount]),
|
||||||
toLogString(entry.Data[FrontendName]),
|
toLog(entry.Data[FrontendName]),
|
||||||
toLogString(entry.Data[BackendURL]),
|
toLog(entry.Data[BackendURL]),
|
||||||
elapsedMillis)
|
elapsedMillis)
|
||||||
|
|
||||||
return b.Bytes(), err
|
return b.Bytes(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func toLogString(v interface{}) string {
|
func toLog(v interface{}) interface{} {
|
||||||
defaultValue := "-"
|
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
@ -54,7 +56,7 @@ func toLogString(v interface{}) string {
|
||||||
return quoted(s.String(), defaultValue)
|
return quoted(s.String(), defaultValue)
|
||||||
|
|
||||||
default:
|
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"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
"github.com/containous/traefik/metrics"
|
"github.com/containous/traefik/metrics"
|
||||||
gokitmetrics "github.com/go-kit/kit/metrics"
|
gokitmetrics "github.com/go-kit/kit/metrics"
|
||||||
)
|
)
|
||||||
|
@ -17,7 +19,7 @@ type MetricsWrapper struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMetricsWrapper return a MetricsWrapper struct with
|
// 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 {
|
func NewMetricsWrapper(registry metrics.Registry, service string) *MetricsWrapper {
|
||||||
var metricsWrapper = MetricsWrapper{
|
var metricsWrapper = MetricsWrapper{
|
||||||
registry: registry,
|
registry: registry,
|
||||||
|
@ -32,7 +34,7 @@ func (m *MetricsWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Request, next
|
||||||
prw := &responseRecorder{rw, http.StatusOK}
|
prw := &responseRecorder{rw, http.StatusOK}
|
||||||
next(prw, r)
|
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)
|
m.registry.ReqsCounter().With(reqLabels...).Add(1)
|
||||||
|
|
||||||
reqDurationLabels := []string{"service", m.serviceName, "code", strconv.Itoa(prw.statusCode)}
|
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}
|
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
|
// MetricsRetryListener is an implementation of the RetryListener interface to
|
||||||
// record RequestMetrics about retry attempts.
|
// record RequestMetrics about retry attempts.
|
||||||
type MetricsRetryListener struct {
|
type MetricsRetryListener struct {
|
||||||
|
|
|
@ -77,7 +77,7 @@ func (a nodeSorter) Less(i int, j int) bool {
|
||||||
return lentr.Service.Port < rentr.Service.Port
|
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)
|
currKeySet := fun.Set(fun.Keys(currState).([]string)).(map[string]bool)
|
||||||
prevKeySet := fun.Set(fun.Keys(prevState).([]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)
|
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) {
|
func (p *CatalogProvider) watchHealthState(stopCh <-chan struct{}, watchCh chan<- map[string][]string) {
|
||||||
health := p.client.Health()
|
health := p.client.Health()
|
||||||
catalog := p.client.Catalog()
|
catalog := p.client.Catalog()
|
||||||
|
|
||||||
safe.Go(func() {
|
safe.Go(func() {
|
||||||
// variable to hold previous state
|
// variable to hold previous state
|
||||||
var flashback map[string][]string
|
var flashback []string
|
||||||
|
|
||||||
options := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
|
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.
|
// Listening to changes that leads to `passing` state or degrades from it.
|
||||||
// The call is used just as a trigger for further actions
|
healthyState, meta, err := health.State("passing", options)
|
||||||
// (intentionally there is no interest in the received data).
|
|
||||||
_, meta, err := health.State("passing", options)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Error("Failed to retrieve health checks")
|
log.WithError(err).Error("Failed to retrieve health checks")
|
||||||
return
|
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
|
// If LastIndex didn't change then it means `Get` returned
|
||||||
// because of the WaitTime and the key didn't changed.
|
// because of the WaitTime and the key didn't changed.
|
||||||
if options.WaitIndex == meta.LastIndex {
|
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.
|
// 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.
|
// 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...
|
// Thus it is required to do extra check for changes...
|
||||||
addedKeys, removedKeys := getChangedKeys(data, flashback)
|
addedKeys, removedKeys := getChangedHealthyKeys(current, flashback)
|
||||||
|
|
||||||
if len(addedKeys) > 0 {
|
if len(addedKeys) > 0 {
|
||||||
log.WithField("DiscoveredServices", addedKeys).Debug("Health State change detected.")
|
log.WithField("DiscoveredServices", addedKeys).Debug("Health State change detected.")
|
||||||
watchCh <- data
|
watchCh <- data
|
||||||
flashback = data
|
flashback = current
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(removedKeys) > 0 {
|
if len(removedKeys) > 0 {
|
||||||
log.WithField("MissingServices", removedKeys).Debug("Health State change detected.")
|
log.WithField("MissingServices", removedKeys).Debug("Health State change detected.")
|
||||||
watchCh <- data
|
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) {
|
func (p *CatalogProvider) watchCatalogServices(stopCh <-chan struct{}, watchCh chan<- map[string][]string) {
|
||||||
catalog := p.client.Catalog()
|
catalog := p.client.Catalog()
|
||||||
|
|
||||||
safe.Go(func() {
|
safe.Go(func() {
|
||||||
|
current := make(map[string]Service)
|
||||||
// variable to hold previous state
|
// variable to hold previous state
|
||||||
var flashback map[string][]string
|
var flashback map[string]Service
|
||||||
|
|
||||||
options := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
|
options := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
|
||||||
|
|
||||||
|
@ -179,26 +216,55 @@ func (p *CatalogProvider) watchCatalogServices(stopCh <-chan struct{}, watchCh c
|
||||||
options.WaitIndex = meta.LastIndex
|
options.WaitIndex = meta.LastIndex
|
||||||
|
|
||||||
if data != nil {
|
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.
|
// 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.
|
// 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...
|
// Thus it is required to do extra check for changes...
|
||||||
addedKeys, removedKeys := getChangedKeys(data, flashback)
|
addedServiceKeys, removedServiceKeys := getChangedServiceKeys(current, flashback)
|
||||||
|
|
||||||
if len(addedKeys) > 0 {
|
addedServiceNodeKeys, removedServiceNodeKeys := getChangedServiceNodeKeys(current, flashback)
|
||||||
log.WithField("DiscoveredServices", addedKeys).Debug("Catalog Services change detected.")
|
|
||||||
|
if len(addedServiceKeys) > 0 || len(addedServiceNodeKeys) > 0 {
|
||||||
|
log.WithField("DiscoveredServices", addedServiceKeys).Debug("Catalog Services change detected.")
|
||||||
watchCh <- data
|
watchCh <- data
|
||||||
flashback = data
|
flashback = current
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(removedKeys) > 0 {
|
if len(removedServiceKeys) > 0 || len(removedServiceNodeKeys) > 0 {
|
||||||
log.WithField("MissingServices", removedKeys).Debug("Catalog Services change detected.")
|
log.WithField("MissingServices", removedServiceKeys).Debug("Catalog Services change detected.")
|
||||||
watchCh <- data
|
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) {
|
func (p *CatalogProvider) healthyNodes(service string) (catalogUpdate, error) {
|
||||||
health := p.client.Health()
|
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)
|
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 {
|
func (p *CatalogProvider) hasTag(name string, tags []string) bool {
|
||||||
// Very-very unlikely that a Consul tag would ever start with '=!='
|
// Very-very unlikely that a Consul tag would ever start with '=!='
|
||||||
tag := p.getTag(name, tags, "=!=")
|
tag := p.getTag(name, tags, "=!=")
|
||||||
|
@ -377,6 +451,7 @@ func (p *CatalogProvider) buildConfig(catalog []catalogUpdate) *types.Configurat
|
||||||
"getBackendName": p.getBackendName,
|
"getBackendName": p.getBackendName,
|
||||||
"getBackendAddress": p.getBackendAddress,
|
"getBackendAddress": p.getBackendAddress,
|
||||||
"getAttribute": p.getAttribute,
|
"getAttribute": p.getAttribute,
|
||||||
|
"getBasicAuth": p.getBasicAuth,
|
||||||
"getTag": p.getTag,
|
"getTag": p.getTag,
|
||||||
"hasTag": p.hasTag,
|
"hasTag": p.hasTag,
|
||||||
"getEntryPoints": p.getEntryPoints,
|
"getEntryPoints": p.getEntryPoints,
|
||||||
|
|
|
@ -348,6 +348,7 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
|
||||||
"random.foo=bar",
|
"random.foo=bar",
|
||||||
"traefik.backend.maxconn.amount=1000",
|
"traefik.backend.maxconn.amount=1000",
|
||||||
"traefik.backend.maxconn.extractorfunc=client.ip",
|
"traefik.backend.maxconn.extractorfunc=client.ip",
|
||||||
|
"traefik.frontend.auth.basic=test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Nodes: []*api.ServiceEntry{
|
Nodes: []*api.ServiceEntry{
|
||||||
|
@ -380,6 +381,7 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
|
||||||
Rule: "Host:test.localhost",
|
Rule: "Host:test.localhost",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedBackends: map[string]*types.Backend{
|
expectedBackends: map[string]*types.Backend{
|
||||||
|
@ -411,6 +413,7 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
|
||||||
t.Fatalf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends)
|
t.Fatalf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) {
|
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)
|
t.Fatalf("expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -610,8 +613,8 @@ func TestConsulCatalogNodeSorter(t *testing.T) {
|
||||||
|
|
||||||
func TestConsulCatalogGetChangedKeys(t *testing.T) {
|
func TestConsulCatalogGetChangedKeys(t *testing.T) {
|
||||||
type Input struct {
|
type Input struct {
|
||||||
currState map[string][]string
|
currState map[string]Service
|
||||||
prevState map[string][]string
|
prevState map[string]Service
|
||||||
}
|
}
|
||||||
|
|
||||||
type Output struct {
|
type Output struct {
|
||||||
|
@ -625,37 +628,37 @@ func TestConsulCatalogGetChangedKeys(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
input: Input{
|
input: Input{
|
||||||
currState: map[string][]string{
|
currState: map[string]Service{
|
||||||
"foo-service": {"v1"},
|
"foo-service": {Name: "v1"},
|
||||||
"bar-service": {"v1"},
|
"bar-service": {Name: "v1"},
|
||||||
"baz-service": {"v1"},
|
"baz-service": {Name: "v1"},
|
||||||
"qux-service": {"v1"},
|
"qux-service": {Name: "v1"},
|
||||||
"quux-service": {"v1"},
|
"quux-service": {Name: "v1"},
|
||||||
"quuz-service": {"v1"},
|
"quuz-service": {Name: "v1"},
|
||||||
"corge-service": {"v1"},
|
"corge-service": {Name: "v1"},
|
||||||
"grault-service": {"v1"},
|
"grault-service": {Name: "v1"},
|
||||||
"garply-service": {"v1"},
|
"garply-service": {Name: "v1"},
|
||||||
"waldo-service": {"v1"},
|
"waldo-service": {Name: "v1"},
|
||||||
"fred-service": {"v1"},
|
"fred-service": {Name: "v1"},
|
||||||
"plugh-service": {"v1"},
|
"plugh-service": {Name: "v1"},
|
||||||
"xyzzy-service": {"v1"},
|
"xyzzy-service": {Name: "v1"},
|
||||||
"thud-service": {"v1"},
|
"thud-service": {Name: "v1"},
|
||||||
},
|
},
|
||||||
prevState: map[string][]string{
|
prevState: map[string]Service{
|
||||||
"foo-service": {"v1"},
|
"foo-service": {Name: "v1"},
|
||||||
"bar-service": {"v1"},
|
"bar-service": {Name: "v1"},
|
||||||
"baz-service": {"v1"},
|
"baz-service": {Name: "v1"},
|
||||||
"qux-service": {"v1"},
|
"qux-service": {Name: "v1"},
|
||||||
"quux-service": {"v1"},
|
"quux-service": {Name: "v1"},
|
||||||
"quuz-service": {"v1"},
|
"quuz-service": {Name: "v1"},
|
||||||
"corge-service": {"v1"},
|
"corge-service": {Name: "v1"},
|
||||||
"grault-service": {"v1"},
|
"grault-service": {Name: "v1"},
|
||||||
"garply-service": {"v1"},
|
"garply-service": {Name: "v1"},
|
||||||
"waldo-service": {"v1"},
|
"waldo-service": {Name: "v1"},
|
||||||
"fred-service": {"v1"},
|
"fred-service": {Name: "v1"},
|
||||||
"plugh-service": {"v1"},
|
"plugh-service": {Name: "v1"},
|
||||||
"xyzzy-service": {"v1"},
|
"xyzzy-service": {Name: "v1"},
|
||||||
"thud-service": {"v1"},
|
"thud-service": {Name: "v1"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
output: Output{
|
output: Output{
|
||||||
|
@ -665,34 +668,34 @@ func TestConsulCatalogGetChangedKeys(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: Input{
|
input: Input{
|
||||||
currState: map[string][]string{
|
currState: map[string]Service{
|
||||||
"foo-service": {"v1"},
|
"foo-service": {Name: "v1"},
|
||||||
"bar-service": {"v1"},
|
"bar-service": {Name: "v1"},
|
||||||
"baz-service": {"v1"},
|
"baz-service": {Name: "v1"},
|
||||||
"qux-service": {"v1"},
|
"qux-service": {Name: "v1"},
|
||||||
"quux-service": {"v1"},
|
"quux-service": {Name: "v1"},
|
||||||
"quuz-service": {"v1"},
|
"quuz-service": {Name: "v1"},
|
||||||
"corge-service": {"v1"},
|
"corge-service": {Name: "v1"},
|
||||||
"grault-service": {"v1"},
|
"grault-service": {Name: "v1"},
|
||||||
"garply-service": {"v1"},
|
"garply-service": {Name: "v1"},
|
||||||
"waldo-service": {"v1"},
|
"waldo-service": {Name: "v1"},
|
||||||
"fred-service": {"v1"},
|
"fred-service": {Name: "v1"},
|
||||||
"plugh-service": {"v1"},
|
"plugh-service": {Name: "v1"},
|
||||||
"xyzzy-service": {"v1"},
|
"xyzzy-service": {Name: "v1"},
|
||||||
"thud-service": {"v1"},
|
"thud-service": {Name: "v1"},
|
||||||
},
|
},
|
||||||
prevState: map[string][]string{
|
prevState: map[string]Service{
|
||||||
"foo-service": {"v1"},
|
"foo-service": {Name: "v1"},
|
||||||
"bar-service": {"v1"},
|
"bar-service": {Name: "v1"},
|
||||||
"baz-service": {"v1"},
|
"baz-service": {Name: "v1"},
|
||||||
"corge-service": {"v1"},
|
"corge-service": {Name: "v1"},
|
||||||
"grault-service": {"v1"},
|
"grault-service": {Name: "v1"},
|
||||||
"garply-service": {"v1"},
|
"garply-service": {Name: "v1"},
|
||||||
"waldo-service": {"v1"},
|
"waldo-service": {Name: "v1"},
|
||||||
"fred-service": {"v1"},
|
"fred-service": {Name: "v1"},
|
||||||
"plugh-service": {"v1"},
|
"plugh-service": {Name: "v1"},
|
||||||
"xyzzy-service": {"v1"},
|
"xyzzy-service": {Name: "v1"},
|
||||||
"thud-service": {"v1"},
|
"thud-service": {Name: "v1"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
output: Output{
|
output: Output{
|
||||||
|
@ -702,33 +705,33 @@ func TestConsulCatalogGetChangedKeys(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: Input{
|
input: Input{
|
||||||
currState: map[string][]string{
|
currState: map[string]Service{
|
||||||
"foo-service": {"v1"},
|
"foo-service": {Name: "v1"},
|
||||||
"qux-service": {"v1"},
|
"qux-service": {Name: "v1"},
|
||||||
"quux-service": {"v1"},
|
"quux-service": {Name: "v1"},
|
||||||
"quuz-service": {"v1"},
|
"quuz-service": {Name: "v1"},
|
||||||
"corge-service": {"v1"},
|
"corge-service": {Name: "v1"},
|
||||||
"grault-service": {"v1"},
|
"grault-service": {Name: "v1"},
|
||||||
"garply-service": {"v1"},
|
"garply-service": {Name: "v1"},
|
||||||
"waldo-service": {"v1"},
|
"waldo-service": {Name: "v1"},
|
||||||
"fred-service": {"v1"},
|
"fred-service": {Name: "v1"},
|
||||||
"plugh-service": {"v1"},
|
"plugh-service": {Name: "v1"},
|
||||||
"xyzzy-service": {"v1"},
|
"xyzzy-service": {Name: "v1"},
|
||||||
"thud-service": {"v1"},
|
"thud-service": {Name: "v1"},
|
||||||
},
|
},
|
||||||
prevState: map[string][]string{
|
prevState: map[string]Service{
|
||||||
"foo-service": {"v1"},
|
"foo-service": {Name: "v1"},
|
||||||
"bar-service": {"v1"},
|
"bar-service": {Name: "v1"},
|
||||||
"baz-service": {"v1"},
|
"baz-service": {Name: "v1"},
|
||||||
"qux-service": {"v1"},
|
"qux-service": {Name: "v1"},
|
||||||
"quux-service": {"v1"},
|
"quux-service": {Name: "v1"},
|
||||||
"quuz-service": {"v1"},
|
"quuz-service": {Name: "v1"},
|
||||||
"corge-service": {"v1"},
|
"corge-service": {Name: "v1"},
|
||||||
"waldo-service": {"v1"},
|
"waldo-service": {Name: "v1"},
|
||||||
"fred-service": {"v1"},
|
"fred-service": {Name: "v1"},
|
||||||
"plugh-service": {"v1"},
|
"plugh-service": {Name: "v1"},
|
||||||
"xyzzy-service": {"v1"},
|
"xyzzy-service": {Name: "v1"},
|
||||||
"thud-service": {"v1"},
|
"thud-service": {Name: "v1"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
output: Output{
|
output: Output{
|
||||||
|
@ -739,7 +742,7 @@ func TestConsulCatalogGetChangedKeys(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
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)) {
|
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)
|
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{
|
var ecsFuncMap = template.FuncMap{
|
||||||
"filterFrontends": p.filterFrontends,
|
"filterFrontends": p.filterFrontends,
|
||||||
"getFrontendRule": p.getFrontendRule,
|
"getFrontendRule": p.getFrontendRule,
|
||||||
|
"getBasicAuth": p.getBasicAuth,
|
||||||
"getLoadBalancerSticky": p.getLoadBalancerSticky,
|
"getLoadBalancerSticky": p.getLoadBalancerSticky,
|
||||||
"getLoadBalancerMethod": p.getLoadBalancerMethod,
|
"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
|
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 {
|
func (p *Provider) getLoadBalancerSticky(instances []ecsInstance) string {
|
||||||
if len(instances) > 0 {
|
if len(instances) > 0 {
|
||||||
label := p.label(instances[0], types.LabelBackendLoadbalancerSticky)
|
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
|
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
|
// 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 {
|
if err != nil {
|
||||||
log.Errorf("Cannot get Rancher Environments %+v", err)
|
log.Errorf("Cannot get Rancher Environments %+v", err)
|
||||||
|
@ -193,12 +193,13 @@ func listRancherContainer(client *rancher.RancherClient) []*rancher.Container {
|
||||||
return containerList
|
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
|
var rancherDataList []rancherData
|
||||||
|
|
||||||
for _, environment := range environments {
|
for _, environment := range environments {
|
||||||
|
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
|
|
||||||
if service.EnvironmentId != environment.Id {
|
if service.EnvironmentId != environment.Id {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,17 +18,19 @@ if [ -z "$DATE" ]; then
|
||||||
DATE=$(date -u '+%Y-%m-%d_%I:%M:%S%p')
|
DATE=$(date -u '+%Y-%m-%d_%I:%M:%S%p')
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "Building ${VERSION} ${CODENAME} ${DATE}"
|
||||||
|
|
||||||
GIT_REPO_URL='github.com/containous/traefik/version'
|
GIT_REPO_URL='github.com/containous/traefik/version'
|
||||||
GO_BUILD_CMD="go build -ldflags"
|
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
|
# Build 386 amd64 binaries
|
||||||
OS_PLATFORM_ARG=(linux windows darwin)
|
OS_PLATFORM_ARG=(linux windows darwin)
|
||||||
OS_ARCH_ARG=(amd64)
|
OS_ARCH_ARG=(amd64)
|
||||||
for OS in ${OS_PLATFORM_ARG[@]}; do
|
for OS in ${OS_PLATFORM_ARG[@]}; do
|
||||||
for ARCH in ${OS_ARCH_ARG[@]}; do
|
for ARCH in ${OS_ARCH_ARG[@]}; do
|
||||||
echo "Building binary for $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
|
||||||
done
|
done
|
||||||
|
|
||||||
|
@ -37,7 +39,7 @@ OS_PLATFORM_ARG=(linux)
|
||||||
OS_ARCH_ARG=(arm64)
|
OS_ARCH_ARG=(arm64)
|
||||||
for OS in ${OS_PLATFORM_ARG[@]}; do
|
for OS in ${OS_PLATFORM_ARG[@]}; do
|
||||||
for ARCH in ${OS_ARCH_ARG[@]}; do
|
for ARCH in ${OS_ARCH_ARG[@]}; do
|
||||||
echo "Building binary for $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
|
||||||
done
|
done
|
||||||
|
|
|
@ -18,9 +18,11 @@ if [ -z "$DATE" ]; then
|
||||||
DATE=$(date -u '+%Y-%m-%d_%I:%M:%S%p')
|
DATE=$(date -u '+%Y-%m-%d_%I:%M:%S%p')
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "Building ${VERSION} ${CODENAME} ${DATE}"
|
||||||
|
|
||||||
GIT_REPO_URL='github.com/containous/traefik/version'
|
GIT_REPO_URL='github.com/containous/traefik/version'
|
||||||
GO_BUILD_CMD="go build -ldflags"
|
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
|
# Build arm binaries
|
||||||
OS_PLATFORM_ARG=(linux windows darwin)
|
OS_PLATFORM_ARG=(linux windows darwin)
|
||||||
|
@ -28,7 +30,7 @@ OS_ARCH_ARG=(386)
|
||||||
for OS in ${OS_PLATFORM_ARG[@]}; do
|
for OS in ${OS_PLATFORM_ARG[@]}; do
|
||||||
for ARCH in ${OS_ARCH_ARG[@]}; do
|
for ARCH in ${OS_ARCH_ARG[@]}; do
|
||||||
echo "Building binary for $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
|
||||||
done
|
done
|
||||||
|
|
||||||
|
@ -38,18 +40,21 @@ OS_ARCH_ARG=(386 amd64)
|
||||||
for OS in ${OS_PLATFORM_ARG[@]}; do
|
for OS in ${OS_PLATFORM_ARG[@]}; do
|
||||||
for ARCH in ${OS_ARCH_ARG[@]}; do
|
for ARCH in ${OS_ARCH_ARG[@]}; do
|
||||||
# Get rid of existing binaries
|
# Get rid of existing binaries
|
||||||
rm -f dist/traefik_$OS-$ARCH
|
rm -f dist/traefik_${OS}-${ARCH}
|
||||||
echo "Building binary for $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
|
||||||
done
|
done
|
||||||
|
|
||||||
# Build arm binaries
|
# Build arm binaries
|
||||||
OS_PLATFORM_ARG=(linux)
|
OS_PLATFORM_ARG=(linux)
|
||||||
OS_ARCH_ARG=(arm)
|
OS_ARCH_ARG=(arm)
|
||||||
|
ARM_ARG=(6)
|
||||||
for OS in ${OS_PLATFORM_ARG[@]}; do
|
for OS in ${OS_PLATFORM_ARG[@]}; do
|
||||||
for ARCH in ${OS_ARCH_ARG[@]}; do
|
for ARCH in ${OS_ARCH_ARG[@]}; do
|
||||||
echo "Building binary for $OS/$ARCH..."
|
for ARM in ${ARM_ARG[@]}; do
|
||||||
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}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
|
||||||
done
|
done
|
||||||
|
|
|
@ -17,7 +17,6 @@ find_dirs() {
|
||||||
find . -not \( \
|
find . -not \( \
|
||||||
\( \
|
\( \
|
||||||
-path './integration/*' \
|
-path './integration/*' \
|
||||||
-o -path './.glide/*' \
|
|
||||||
-o -path './vendor/*' \
|
-o -path './vendor/*' \
|
||||||
-o -path './.git/*' \
|
-o -path './.git/*' \
|
||||||
\) \
|
\) \
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
source "$(dirname "$BASH_SOURCE")/.validate"
|
source "$(dirname "$BASH_SOURCE")/.validate"
|
||||||
|
|
||||||
IFS=$'\n'
|
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
|
unset IFS
|
||||||
|
|
||||||
badFiles=()
|
badFiles=()
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
source "$(dirname "$BASH_SOURCE")/.validate"
|
source "$(dirname "$BASH_SOURCE")/.validate"
|
||||||
|
|
||||||
IFS=$'\n'
|
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
|
unset IFS
|
||||||
|
|
||||||
errors=()
|
errors=()
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
source "$(dirname "$BASH_SOURCE")/.validate"
|
source "$(dirname "$BASH_SOURCE")/.validate"
|
||||||
|
|
||||||
IFS=$'\n'
|
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
|
unset IFS
|
||||||
|
|
||||||
errors=()
|
errors=()
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
source "$(dirname "$BASH_SOURCE")/.validate"
|
source "$(dirname "$BASH_SOURCE")/.validate"
|
||||||
|
|
||||||
IFS=$'\n'
|
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') )
|
docs=( $(validate_diff --diff-filter=ACMR --name-only -- 'docs/*.md') )
|
||||||
unset IFS
|
unset IFS
|
||||||
files=("${src[@]}" "${docs[@]}")
|
files=("${src[@]}" "${docs[@]}")
|
||||||
|
|
|
@ -647,6 +647,7 @@ func (server *Server) prepareServer(entryPointName string, entryPoint *configura
|
||||||
listener, err := net.Listen("tcp", entryPoint.Address)
|
listener, err := net.Listen("tcp", entryPoint.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error opening listener ", err)
|
log.Error("Error opening listener ", err)
|
||||||
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if entryPoint.ProxyProtocol {
|
if entryPoint.ProxyProtocol {
|
||||||
|
|
|
@ -96,7 +96,7 @@ func TestPrepareServerTimeouts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
entryPointName := "http"
|
entryPointName := "http"
|
||||||
entryPoint := &configuration.EntryPoint{Address: "localhost:8080"}
|
entryPoint := &configuration.EntryPoint{Address: "localhost:0"}
|
||||||
router := middlewares.NewHandlerSwitcher(mux.NewRouter())
|
router := middlewares.NewHandlerSwitcher(mux.NewRouter())
|
||||||
|
|
||||||
srv := NewServer(test.globalConfig)
|
srv := NewServer(test.globalConfig)
|
||||||
|
@ -504,14 +504,14 @@ func TestServerEntrypointWhitelistConfig(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "no whitelist middleware if no config on entrypoint",
|
desc: "no whitelist middleware if no config on entrypoint",
|
||||||
entrypoint: &configuration.EntryPoint{
|
entrypoint: &configuration.EntryPoint{
|
||||||
Address: ":8080",
|
Address: ":0",
|
||||||
},
|
},
|
||||||
wantMiddleware: false,
|
wantMiddleware: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "whitelist middleware should be added if configured on entrypoint",
|
desc: "whitelist middleware should be added if configured on entrypoint",
|
||||||
entrypoint: &configuration.EntryPoint{
|
entrypoint: &configuration.EntryPoint{
|
||||||
Address: ":8080",
|
Address: ":0",
|
||||||
WhitelistSourceRange: []string{
|
WhitelistSourceRange: []string{
|
||||||
"127.0.0.1/32",
|
"127.0.0.1/32",
|
||||||
},
|
},
|
||||||
|
|
|
@ -40,6 +40,9 @@
|
||||||
"{{.}}",
|
"{{.}}",
|
||||||
{{end}}]
|
{{end}}]
|
||||||
{{end}}
|
{{end}}
|
||||||
|
basicAuth = [{{range getBasicAuth .Attributes}}
|
||||||
|
"{{.}}",
|
||||||
|
{{end}}]
|
||||||
[frontends."frontend-{{.ServiceName}}".routes."route-host-{{.ServiceName}}"]
|
[frontends."frontend-{{.ServiceName}}".routes."route-host-{{.ServiceName}}"]
|
||||||
rule = "{{getFrontendRule .}}"
|
rule = "{{getFrontendRule .}}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
priority = {{ getPriority .}}
|
priority = {{ getPriority .}}
|
||||||
entryPoints = [{{range getEntryPoints .}}
|
entryPoints = [{{range getEntryPoints .}}
|
||||||
"{{.}}",
|
"{{.}}",
|
||||||
|
{{end}}]
|
||||||
|
basicAuth = [{{range getBasicAuth .}}
|
||||||
|
"{{.}}",
|
||||||
{{end}}]
|
{{end}}]
|
||||||
[frontends.frontend-{{ $serviceName }}.routes.route-frontend-{{ $serviceName }}]
|
[frontends.frontend-{{ $serviceName }}.routes.route-frontend-{{ $serviceName }}]
|
||||||
rule = "{{getFrontendRule .}}"
|
rule = "{{getFrontendRule .}}"
|
||||||
|
|
Loading…
Reference in a new issue