1
0
mirror of https://github.com/containous/traefik.git synced 2025-09-21 09:44:22 +03:00

Compare commits

..

28 Commits

Author SHA1 Message Date
Michael
741c739ef1 Prepare release v1.4.6 2018-01-02 12:54:03 +01:00
SALLEYRON Julien
52f16e11a8 Use gorilla readMessage and writeMessage instead of just an io.Copy 2018-01-02 12:30:05 +01:00
Michael
e8e8b41eed Normalize serviceName added to the service backend names 2018-01-02 10:52:03 +01:00
Ludovic Fernandez
718fc7a79d Fix bug report command 2018-01-02 10:14:03 +01:00
Michael
cda09c843a Prepare release v1.4.5 2017-12-06 10:44:03 +01:00
Mikhail Vasin
ab1a930705 Emphasize the necessity of enabling file backend 2017-12-05 02:30:02 +01:00
Michael
47a5cfbd3e Fix empty ip when container is stopped 2017-11-28 13:58:04 +01:00
Ludovic Fernandez
b572879691 Add link to futur 1.5 documentation. 2017-11-28 13:06:03 +01:00
Kwok-kuen Cheung
0f09551a76 Fix kubernetes path prefix rule with rewrite-target 2017-11-27 11:22:03 +01:00
SALLEYRON Julien
419d46c958 Prepare release v1.4.4 2017-11-23 11:48:03 +01:00
Marco Jantke
676b79db42 Fix raw path handling in strip prefix 2017-11-21 14:28:03 +01:00
Ludovic Fernandez
c9129b8ecf Remove GzipHandler Fork 2017-11-20 18:32:03 +01:00
NicoMen
6619a787a3 Fix problems about duplicated and missing Docker backends/frontends. 2017-11-20 15:16:03 +01:00
Raúl Sánchez
aae17c817b Fix issue with label traefik.backend.loadbalancer.stickiness.cookieName 2017-11-20 11:42:03 +01:00
Ludovic Fernandez
8fe5c22075 Exclude RC from doc publication. 2017-11-20 09:42:02 +01:00
Ludovic Fernandez
1a4564d998 http.Server log goes to Debug level. 2017-11-18 01:10:03 +01:00
Ludovic Fernandez
66be04f39e Documentation archive 2017-11-16 09:20:03 +01:00
NicoMen
77b111702b Prepare release v1.4.3 2017-11-14 12:06:03 +01:00
NicoMen
96a7cc483f Add Traefik prefix to the KV key 2017-11-14 11:38:03 +01:00
Ludovic Fernandez
1e3506848a Flush and errorcode 2017-11-14 11:16:03 +01:00
Michael
5ee2cae85c Fix Traefik reload if Consul Catalog tags change 2017-11-13 12:14:02 +01:00
Ludovic Fernandez
5c119fe2d6 Exclude GRPC from compress 2017-11-10 14:12:02 +01:00
Ivan Rogov
2f62ec3632 Link corrected 2017-11-09 15:54:04 +01:00
Levi Blaney
56affb90ae Add secret creation to docs for kubernetes backend 2017-11-09 10:52:03 +01:00
SALLEYRON Julien
9bd0fff319 Keep status when stream mode and compress 2017-11-09 00:48:03 +01:00
Jan Collijs
58a438167b Minor fix for docker volume vs created directory 2017-11-08 15:12:03 +01:00
Tom Saleeba
bc8d68bd31 docs: fix some typos 2017-11-07 11:50:03 +01:00
Bernhard Millauer
70812c70fc Postfix windows binaries with .exe 2017-11-03 17:02:14 +01:00
40 changed files with 989 additions and 205 deletions

View File

@@ -36,6 +36,7 @@ deploy:
on:
repo: containous/traefik
tags: true
condition: ${TRAVIS_TAG} =~ ^v[0-9]+\.[0-9]+\.[0-9]+$
- provider: releases
api_key: ${GITHUB_TOKEN}
file: dist/traefik*

View File

@@ -1,5 +1,57 @@
# Change Log
## [v1.4.6](https://github.com/containous/traefik/tree/v1.4.6) (2018-01-02)
[All Commits](https://github.com/containous/traefik/compare/v1.4.5...v1.4.6)
**Bug fixes:**
- **[docker]** Normalize serviceName added to the service backend names ([#2631](https://github.com/containous/traefik/pull/2631) by [mmatur](https://github.com/mmatur))
- **[websocket]** Use gorilla readMessage and writeMessage instead of just an io.Copy ([#2640](https://github.com/containous/traefik/pull/2640) by [Juliens](https://github.com/Juliens))
- Fix bug report command ([#2638](https://github.com/containous/traefik/pull/2638) by [ldez](https://github.com/ldez))
## [v1.4.5](https://github.com/containous/traefik/tree/v1.4.5) (2017-12-05)
[All Commits](https://github.com/containous/traefik/compare/v1.4.4...v1.4.5)
**Bug fixes:**
- **[docker]** Fix empty ip when container is stopped ([#2478](https://github.com/containous/traefik/pull/2478) by [mmatur](https://github.com/mmatur))
- **[k8s]** Fix kubernetes path prefix rule with rewrite-target ([#2461](https://github.com/containous/traefik/pull/2461) by [cheungpat](https://github.com/cheungpat))
**Documentation:**
- **[file]** Emphasize the necessity of enabling file backend ([#2483](https://github.com/containous/traefik/pull/2483) by [mvasin](https://github.com/mvasin))
- Add link to future 1.5 documentation. ([#2477](https://github.com/containous/traefik/pull/2477) by [ldez](https://github.com/ldez))
## [v1.4.4](https://github.com/containous/traefik/tree/v1.4.4) (2017-11-21)
[All Commits](https://github.com/containous/traefik/compare/v1.4.3...v1.4.4)
**Enhancements:**
- **[middleware]** Remove GzipHandler Fork ([#2436](https://github.com/containous/traefik/pull/2436) by [ldez](https://github.com/ldez))
**Bug fixes:**
- **[docker]** Fix problems about duplicated and missing Docker backends/frontends. ([#2434](https://github.com/containous/traefik/pull/2434) by [nmengin](https://github.com/nmengin))
- **[middleware]** Fix raw path handling in strip prefix ([#2382](https://github.com/containous/traefik/pull/2382) by [marco-jantke](https://github.com/marco-jantke))
- **[rancher]** Fix issue with label traefik.backend.loadbalancer.stickiness.cookieName ([#2423](https://github.com/containous/traefik/pull/2423) by [rawmind0](https://github.com/rawmind0))
- http.Server log goes to Debug level. ([#2420](https://github.com/containous/traefik/pull/2420) by [ldez](https://github.com/ldez))
**Documentation:**
- Documentation archive ([#2405](https://github.com/containous/traefik/pull/2405) by [ldez](https://github.com/ldez))
## [v1.4.3](https://github.com/containous/traefik/tree/v1.4.3) (2017-11-14)
[All Commits](https://github.com/containous/traefik/compare/v1.4.2...v1.4.3)
**Bug fixes:**
- **[consulcatalog]** Fix Traefik reload if Consul Catalog tags change ([#2389](https://github.com/containous/traefik/pull/2389) by [mmatur](https://github.com/mmatur))
- **[kv]** Add Traefik prefix to the KV key ([#2400](https://github.com/containous/traefik/pull/2400) by [nmengin](https://github.com/nmengin))
- **[middleware]** Flush and Status code ([#2403](https://github.com/containous/traefik/pull/2403) by [ldez](https://github.com/ldez))
- **[middleware]** Exclude GRPC from compress ([#2391](https://github.com/containous/traefik/pull/2391) by [ldez](https://github.com/ldez))
- **[middleware]** Keep status when stream mode and compress ([#2380](https://github.com/containous/traefik/pull/2380) by [Juliens](https://github.com/Juliens))
**Documentation:**
- **[acme]** Fix some typos ([#2363](https://github.com/containous/traefik/pull/2363) by [tomsaleeba](https://github.com/tomsaleeba))
- **[docker]** Minor fix for docker volume vs created directory ([#2372](https://github.com/containous/traefik/pull/2372) by [visibilityspots](https://github.com/visibilityspots))
- **[k8s]** Link corrected ([#2385](https://github.com/containous/traefik/pull/2385) by [xlazex](https://github.com/xlazex))
**Misc:**
- **[k8s]** Add secret creation to docs for kubernetes backend ([#2374](https://github.com/containous/traefik/pull/2374) by [shadycuz](https://github.com/shadycuz))
## [v1.4.2](https://github.com/containous/traefik/tree/v1.4.2) (2017-11-02)
[All Commits](https://github.com/containous/traefik/compare/v1.4.1...v1.4.2)

View File

@@ -1,8 +1,9 @@
package anonymize
import (
"github.com/stretchr/testify/assert"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_doOnJSON(t *testing.T) {

View File

@@ -84,7 +84,7 @@ Add more configuration information here.
)
// newBugCmd builds a new Bug command
func newBugCmd(traefikConfiguration interface{}, traefikPointersConfiguration interface{}) *flaeg.Command {
func newBugCmd(traefikConfiguration *TraefikConfiguration, traefikPointersConfiguration *TraefikConfiguration) *flaeg.Command {
//version Command init
return &flaeg.Command{
@@ -99,7 +99,7 @@ func newBugCmd(traefikConfiguration interface{}, traefikPointersConfiguration in
}
}
func runBugCmd(traefikConfiguration interface{}) func() error {
func runBugCmd(traefikConfiguration *TraefikConfiguration) func() error {
return func() error {
body, err := createBugReport(traefikConfiguration)
@@ -113,7 +113,7 @@ func runBugCmd(traefikConfiguration interface{}) func() error {
}
}
func createBugReport(traefikConfiguration interface{}) (string, error) {
func createBugReport(traefikConfiguration *TraefikConfiguration) (string, error) {
var version bytes.Buffer
if err := getVersionPrint(&version); err != nil {
return "", err
@@ -124,7 +124,7 @@ func createBugReport(traefikConfiguration interface{}) (string, error) {
return "", err
}
config, err := anonymize.Do(&traefikConfiguration, true)
config, err := anonymize.Do(traefikConfiguration, true)
if err != nil {
return "", err
}

View File

@@ -6,16 +6,27 @@ import (
"github.com/containous/traefik/cmd/traefik/anonymize"
"github.com/containous/traefik/configuration"
"github.com/containous/traefik/provider/file"
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
)
func Test_createBugReport(t *testing.T) {
traefikConfiguration := TraefikConfiguration{
traefikConfiguration := &TraefikConfiguration{
ConfigFile: "FOO",
GlobalConfiguration: configuration.GlobalConfiguration{
EntryPoints: configuration.EntryPoints{
"goo": &configuration.EntryPoint{
Address: "hoo.bar",
Auth: &types.Auth{
Basic: &types.Basic{
UsersFile: "foo Basic UsersFile",
Users: types.Users{"foo Basic Users 1", "foo Basic Users 2", "foo Basic Users 3"},
},
Digest: &types.Digest{
UsersFile: "foo Digest UsersFile",
Users: types.Users{"foo Digest Users 1", "foo Digest Users 2", "foo Digest Users 3"},
},
},
},
},
File: &file.Provider{
@@ -27,6 +38,11 @@ func Test_createBugReport(t *testing.T) {
report, err := createBugReport(traefikConfiguration)
assert.NoError(t, err, report)
// exported anonymous configuration
assert.NotContains(t, "web Basic Users ", report)
assert.NotContains(t, "foo Digest Users ", report)
assert.NotContains(t, "hoo.bar", report)
}
func Test_anonymize_traefikConfiguration(t *testing.T) {

23
docs/archive.md Normal file
View File

@@ -0,0 +1,23 @@
## Current versions documentation
- [Latest stable](https://docs.traefik.io)
- [Experimental](https://master--traefik-docs.netlify.com/)
## Future version documentation
- [v1.5 RC](http://v1-5.archive.docs.traefik.io/)
## Previous versions documentation
- [v1.4 aka Roquefort](http://v1-4.archive.docs.traefik.io/)
- [v1.3 aka Raclette](http://v1-3.archive.docs.traefik.io/)
- [v1.2 aka Morbier](http://v1-2.archive.docs.traefik.io/)
- [v1.1 aka Camembert](http://v1-1.archive.docs.traefik.io/)
## More
[Change log](https://github.com/containous/traefik/blob/master/CHANGELOG.md)

View File

@@ -8,6 +8,8 @@ You have three choices:
- [Rules in a Separate File](/configuration/backends/file/#rules-in-a-separate-file)
- [Multiple `.toml` Files](/configuration/backends/file/#multiple-toml-files)
To enable the file backend, you must either pass the `--file` option to the Træfik binary or put the `[file]` section (with or without inner settings) in the configuration file.
## Simple
Add your configuration at the end of the global configuration file `traefik.toml`:

View File

@@ -2,7 +2,7 @@
Træfik can be configured to use Kubernetes Ingress as a backend configuration.
See also [Kubernetes user guide](/docs/user-guide/kubernetes).
See also [Kubernetes user guide](/user-guide/kubernetes).
## Configuration
@@ -118,10 +118,10 @@ If one of the Net-Specifications are invalid, the whole list is invalid and allo
### Authentication
Is possible to add additional authentication annotations in the Ingress rule.
The source of the authentication is a secret that contains usernames and passwords inside the the key auth.
The source of the authentication is a secret that contains usernames and passwords inside the key auth.
- `ingress.kubernetes.io/auth-type`: `basic`
- `ingress.kubernetes.io/auth-secret`
- `ingress.kubernetes.io/auth-secret`: `mysecret`
Contains the usernames and passwords with access to the paths defined in the Ingress Rule.
The secret must be created in the same namespace as the Ingress rule.

View File

@@ -59,8 +59,8 @@ services:
- web
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /srv/traefik/traefik.toml:/traefik.toml
- /srv/traefik/acme.json:/acme.json
- /opt/traefik/traefik.toml:/traefik.toml
- /opt/traefik/acme.json:/acme.json
container_name: traefik
networks:

View File

@@ -137,7 +137,7 @@ This configuration allows generating a Let's Encrypt certificate during the firs
* TLS handshakes will be slow when requesting a hostname certificate for the first time, this can leads to DDoS attacks.
* Let's Encrypt have rate limiting: https://letsencrypt.org/docs/rate-limits
That's why, it's better to use the `onHostRule` optin if possible.
That's why, it's better to use the `onHostRule` option if possible.
### DNS challenge
@@ -170,7 +170,7 @@ entryPoint = "https"
DNS challenge needs environment variables to be executed.
This variables have to be set on the machine/container which host Traefik.
These variables has described [in this section](/configuration/acme/#dnsprovider).
These variables are described [in this section](/configuration/acme/#dnsprovider).
### OnHostRule option and provided certificates
@@ -198,7 +198,7 @@ Traefik will only try to generate a Let's encrypt certificate if the domain cann
#### Prerequisites
Before to use Let's Encrypt in a Traefik cluster, take a look to [the key-value store explanations](/user-guide/kv-config) and more precisely to [this section](/user-guide/kv-config/#store-configuration-in-key-value-store) in the way to know how to migrate from a acme local storage *(acme.json file)* to a key-value store configuration.
Before you use Let's Encrypt in a Traefik cluster, take a look to [the key-value store explanations](/user-guide/kv-config) and more precisely at [this section](/user-guide/kv-config/#store-configuration-in-key-value-store), which will describe how to migrate from a acme local storage *(acme.json file)* to a key-value store configuration.
#### Configuration

View File

@@ -79,7 +79,7 @@ It is possible to use Træfik with a [Deployment](https://kubernetes.io/docs/con
The Deployment objects looks like this:
```yml
```yaml
---
apiVersion: v1
kind: ServiceAccount
@@ -327,6 +327,72 @@ echo "$(minikube ip) traefik-ui.minikube" | sudo tee -a /etc/hosts
We should now be able to visit [traefik-ui.minikube](http://traefik-ui.minikube) in the browser and view the Træfik Web UI.
## Basic Authentication
It's possible to add additional authentication annotations in the Ingress rule.
The source of the authentication is a secret that contains usernames and passwords inside the key auth.
To read about basic auth limitations see the [Kubernetes Ingress](/configuration/backends/kubernetes) configuration page.
#### Creating the Secret
A. Use `htpasswd` to create a file containing the username and the base64-encoded password:
```shell
htpasswd -c ./auth myusername
```
You will be prompted for a password which you will have to enter twice.
`htpasswd` will create a file with the following:
```shell
cat auth
```
```
myusername:$apr1$78Jyn/1K$ERHKVRPPlzAX8eBtLuvRZ0
```
B. Now use `kubectl` to create a secret in the monitoring namespace using the file created by `htpasswd`.
```shell
kubectl create secret generic mysecret --from-file auth --namespace=monitoring
```
!!! note
Secret must be in same namespace as the ingress rule.
C. Create the ingress using the following annotations to specify basic auth and that the username and password is stored in `mysecret`.
- `ingress.kubernetes.io/auth-type: "basic"`
- `ingress.kubernetes.io/auth-secret: "mysecret"`
Following is a full ingress example based on Prometheus:
```yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: prometheus-dashboard
namespace: monitoring
annotations:
kubernetes.io/ingress.class: traefik
ingress.kubernetes.io/auth-type: "basic"
ingress.kubernetes.io/auth-secret: "mysecret"
spec:
rules:
- host: dashboard.prometheus.example.com
http:
paths:
- backend:
serviceName: prometheus
servicePort: 9090
```
You can apply the example ingress as following:
```shell
kubectl create -f prometheus-ingress.yaml -n monitoring
```
## Name based routing
In this example we are going to setup websites for 3 of the United Kingdoms best loved cheeses, Cheddar, Stilton and Wensleydale.

View File

@@ -148,6 +148,37 @@ This variable must be initialized with the ACL token value.
If Traefik is launched into a Docker container, the variable `CONSUL_HTTP_TOKEN` can be initialized with the `-e` Docker option : `-e "CONSUL_HTTP_TOKEN=[consul-acl-token-value]"`
If a Consul ACL is used to restrict Træfik read/write access, one of the following configurations is needed.
- HCL format :
```
key "traefik" {
policy = "write"
},
session "" {
policy = "write"
}
```
- JSON format :
```json
{
"key": {
"traefik": {
"policy": "write"
}
},
"session": {
"": {
"policy": "write"
}
}
}
```
### TLS support
To connect to a Consul endpoint using SSL, simply specify `https://` in the `consul.endpoint` property

4
glide.lock generated
View File

@@ -383,7 +383,7 @@ imports:
repo: https://github.com/ijc25/Gotty.git
vcs: git
- name: github.com/NYTimes/gziphandler
version: 97ae7fbaf81620fe97840685304a78a306a39c64
version: d6f46609c7629af3a02d791a4666866eed3cbd3e
- name: github.com/ogier/pflag
version: 45c278ab3607870051a2ea9040bb85fcb8557481
- name: github.com/opencontainers/go-digest
@@ -481,7 +481,7 @@ imports:
- name: github.com/urfave/negroni
version: 490e6a555d47ca891a89a150d0c1ef3922dfffe9
- name: github.com/vulcand/oxy
version: 7e9763c4dc71b9758379da3581e6495c145caaab
version: bf0e6bab094f7b909a8d94ba9d7b74aaf7cc3025
repo: https://github.com/containous/oxy.git
vcs: git
subpackages:

View File

@@ -36,7 +36,7 @@ func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) {
}
func (s *ConsulCatalogSuite) waitToElectConsulLeader() error {
return try.Do(3*time.Second, func() error {
return try.Do(15*time.Second, func() error {
leader, err := s.consulClient.Status().Leader()
if err != nil || len(leader) == 0 {
@@ -344,8 +344,82 @@ func (s *ConsulCatalogSuite) TestBasicAuthSimpleService(c *check.C) {
c.Assert(err, checker.IsNil)
}
func (s *ConsulCatalogSuite) TestRetryWithConsulServer(c *check.C) {
func (s *ConsulCatalogSuite) TestRefreshConfigTagChange(c *check.C) {
cmd, display := s.traefikCmd(
withConfigFile("fixtures/consul_catalog/simple.toml"),
"--consulCatalog",
"--consulCatalog.exposedByDefault=false",
"--consulCatalog.watch=true",
"--consulCatalog.endpoint="+s.consulIP+":8500",
"--consulCatalog.domain=consul.localhost")
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
nginx := s.composeProject.Container(c, "nginx1")
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"name=nginx1", "traefik.enable=false", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 5*time.Second, try.BodyContains("nginx1"))
c.Assert(err, checker.NotNil)
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"name=nginx1", "traefik.enable=true", "traefik.backend.circuitbreaker=ResponseCodeRatio(500, 600, 0, 600) > 0.5"})
c.Assert(err, checker.IsNil, check.Commentf("Error registering 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, 20*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)
}
func (s *ConsulCatalogSuite) TestCircuitBreaker(c *check.C) {
cmd, display := s.traefikCmd(
withConfigFile("fixtures/consul_catalog/simple.toml"),
"--retry",
"--retry.attempts=1",
"--forwardingTimeouts.dialTimeout=5s",
"--forwardingTimeouts.responseHeaderTimeout=10s",
"--consulCatalog",
"--consulCatalog.exposedByDefault=false",
"--consulCatalog.watch=true",
"--consulCatalog.endpoint="+s.consulIP+":8500",
"--consulCatalog.domain=consul.localhost")
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
nginx := s.composeProject.Container(c, "nginx1")
nginx2 := s.composeProject.Container(c, "nginx2")
nginx3 := s.composeProject.Container(c, "nginx3")
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"name=nginx1", "traefik.enable=true", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
err = s.registerService("test", nginx2.NetworkSettings.IPAddress, 42, []string{"name=nginx2", "traefik.enable=true", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", nginx2.NetworkSettings.IPAddress)
err = s.registerService("test", nginx3.NetworkSettings.IPAddress, 42, []string{"name=nginx3", "traefik.enable=true", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", nginx3.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, 20*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable), try.HasBody())
c.Assert(err, checker.IsNil)
}
func (s *ConsulCatalogSuite) TestRetryWithConsulServer(c *check.C) {
//Scale consul to 0 to be able to start traefik before and test retry
s.composeProject.Scale(c, "consul", 0)

View File

@@ -49,6 +49,14 @@ func (s *DockerSuite) startContainerWithLabels(c *check.C, image string, labels
})
}
func (s *DockerSuite) startContainerWithNameAndLabels(c *check.C, name string, image string, labels map[string]string, args ...string) string {
return s.startContainerWithConfig(c, image, d.ContainerConfig{
Name: name,
Cmd: args,
Labels: labels,
})
}
func (s *DockerSuite) startContainerWithConfig(c *check.C, image string, config d.ContainerConfig) string {
if config.Name == "" {
config.Name = namesgenerator.GetRandomName(10)
@@ -60,6 +68,11 @@ func (s *DockerSuite) startContainerWithConfig(c *check.C, image string, config
return strings.SplitAfter(container.Name, "/")[1]
}
func (s *DockerSuite) stopAndRemoveContainerByName(c *check.C, name string) {
s.project.Stop(c, name)
s.project.Remove(c, name)
}
func (s *DockerSuite) SetUpSuite(c *check.C) {
project := docker.NewProjectFromEnv(c)
s.project = project
@@ -229,3 +242,53 @@ func (s *DockerSuite) TestDockerContainersWithServiceLabels(c *check.C) {
c.Assert(json.Unmarshal(body, &version), checker.IsNil)
c.Assert(version["Version"], checker.Equals, "swarm/1.0.0")
}
func (s *DockerSuite) TestRestartDockerContainers(c *check.C) {
file := s.adaptFileForHost(c, "fixtures/docker/simple.toml")
defer os.Remove(file)
// Start a container with some labels
labels := map[string]string{
types.LabelPrefix + "frontend.rule": "Host:my.super.host",
types.LabelPort: "2375",
}
s.startContainerWithNameAndLabels(c, "powpow", "swarm:1.0.0", labels, "manage", "token://blabla")
// Start traefik
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil)
c.Assert(err, checker.IsNil)
req.Host = "my.super.host"
// FIXME Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
resp, err := try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK)
c.Assert(err, checker.IsNil)
body, err := ioutil.ReadAll(resp.Body)
c.Assert(err, checker.IsNil)
var version map[string]interface{}
c.Assert(json.Unmarshal(body, &version), checker.IsNil)
c.Assert(version["Version"], checker.Equals, "swarm/1.0.0")
err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/backends", 60*time.Second, try.BodyContains("powpow"))
c.Assert(err, checker.IsNil)
s.stopAndRemoveContainerByName(c, "powpow")
defer s.project.Remove(c, "powpow")
time.Sleep(5 * time.Second)
err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/backends", 10*time.Second, try.BodyContains("powpow"))
c.Assert(err, checker.NotNil)
s.startContainerWithNameAndLabels(c, "powpow", "swarm:1.0.0", labels, "manage", "token://blabla")
err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/backends", 60*time.Second, try.BodyContains("powpow"))
c.Assert(err, checker.IsNil)
}

View File

@@ -6,6 +6,8 @@ logLevel = "DEBUG"
[entryPoints.http]
address = ":8000"
[web]
[docker]
# It's dynamagic !

View File

@@ -1,6 +1,6 @@
consul:
image: progrium/consul
command: -server -bootstrap -log-level debug -ui-dir /ui
image: consul
command: agent -server -bootstrap-expect 1 -client 0.0.0.0 -log-level debug -ui
ports:
- "8400:8400"
- "8500:8500"
@@ -15,3 +15,5 @@ nginx1:
image: nginx:alpine
nginx2:
image: nginx:alpine
nginx3:
image: nginx:alpine

View File

@@ -3,6 +3,7 @@ package middlewares
import (
"compress/gzip"
"net/http"
"strings"
"github.com/NYTimes/gziphandler"
"github.com/containous/traefik/log"
@@ -13,7 +14,12 @@ type Compress struct{}
// ServerHTTP is a function used by Negroni
func (c *Compress) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
gzipHandler(next).ServeHTTP(rw, r)
contentType := r.Header.Get("Content-Type")
if strings.HasPrefix(contentType, "application/grpc") {
next.ServeHTTP(rw, r)
} else {
gzipHandler(next).ServeHTTP(rw, r)
}
}
func gzipHandler(h http.Handler) http.Handler {

View File

@@ -16,6 +16,7 @@ import (
const (
acceptEncodingHeader = "Accept-Encoding"
contentEncodingHeader = "Content-Encoding"
contentTypeHeader = "Content-Type"
varyHeader = "Vary"
gzipValue = "gzip"
)
@@ -81,6 +82,26 @@ func TestShouldNotCompressWhenNoAcceptEncodingHeader(t *testing.T) {
assert.EqualValues(t, rw.Body.Bytes(), fakeBody)
}
func TestShouldNotCompressWhenGRPC(t *testing.T) {
handler := &Compress{}
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
req.Header.Add(acceptEncodingHeader, gzipValue)
req.Header.Add(contentTypeHeader, "application/grpc")
baseBody := generateBytes(gziphandler.DefaultMinSize)
next := func(rw http.ResponseWriter, r *http.Request) {
rw.Write(baseBody)
}
rw := httptest.NewRecorder()
handler.ServeHTTP(rw, req, next)
assert.Empty(t, rw.Header().Get(acceptEncodingHeader))
assert.Empty(t, rw.Header().Get(contentEncodingHeader))
assert.EqualValues(t, rw.Body.Bytes(), baseBody)
}
func TestIntegrationShouldNotCompress(t *testing.T) {
fakeCompressedBody := generateBytes(100000)
comp := &Compress{}
@@ -137,6 +158,31 @@ func TestIntegrationShouldNotCompress(t *testing.T) {
}
}
func TestShouldWriteHeaderWhenFlush(t *testing.T) {
comp := &Compress{}
negro := negroni.New(comp)
negro.UseHandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Add(contentEncodingHeader, gzipValue)
rw.Header().Add(varyHeader, acceptEncodingHeader)
rw.WriteHeader(http.StatusUnauthorized)
rw.(http.Flusher).Flush()
rw.Write([]byte("short"))
})
ts := httptest.NewServer(negro)
defer ts.Close()
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
req.Header.Add(acceptEncodingHeader, gzipValue)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
assert.Equal(t, gzipValue, resp.Header.Get(contentEncodingHeader))
assert.Equal(t, acceptEncodingHeader, resp.Header.Get(varyHeader))
}
func TestIntegrationShouldCompress(t *testing.T) {
fakeBody := generateBytes(100000)

View File

@@ -3,8 +3,9 @@ package middlewares
//Middleware based on https://github.com/unrolled/secure
import (
"github.com/containous/traefik/types"
"net/http"
"github.com/containous/traefik/types"
)
// HeaderOptions is a struct for specifying configuration options for the headers middleware.

View File

@@ -3,11 +3,12 @@ package middlewares
//Middleware tests based on https://github.com/unrolled/secure
import (
"github.com/containous/traefik/testhelpers"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"testing"
"github.com/containous/traefik/testhelpers"
"github.com/stretchr/testify/assert"
)
var myHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

View File

@@ -16,8 +16,11 @@ type StripPrefix struct {
func (s *StripPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) {
for _, prefix := range s.Prefixes {
if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) {
r.URL.Path = "/" + strings.TrimPrefix(p, "/")
if strings.HasPrefix(r.URL.Path, prefix) {
r.URL.Path = stripPrefix(r.URL.Path, prefix)
if r.URL.RawPath != "" {
r.URL.RawPath = stripPrefix(r.URL.RawPath, prefix)
}
s.serveRequest(w, r, strings.TrimSpace(prefix))
return
}
@@ -35,3 +38,11 @@ func (s *StripPrefix) serveRequest(w http.ResponseWriter, r *http.Request, prefi
func (s *StripPrefix) SetHandler(Handler http.Handler) {
s.Handler = Handler
}
func stripPrefix(s, prefix string) string {
return ensureLeadingSlash(strings.TrimPrefix(s, prefix))
}
func ensureLeadingSlash(str string) string {
return "/" + strings.TrimPrefix(str, "/")
}

View File

@@ -40,6 +40,9 @@ func (s *StripPrefixRegex) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
r.URL.Path = r.URL.Path[len(prefix.Path):]
if r.URL.RawPath != "" {
r.URL.RawPath = r.URL.RawPath[len(prefix.Path):]
}
r.Header.Add(ForwardedPrefixHeader, prefix.Path)
r.RequestURI = r.URL.RequestURI()
s.Handler.ServeHTTP(w, r)

View File

@@ -10,13 +10,13 @@ import (
)
func TestStripPrefixRegex(t *testing.T) {
testPrefixRegex := []string{"/a/api/", "/b/{regex}/", "/c/{category}/{id:[0-9]+}/"}
tests := []struct {
path string
expectedStatusCode int
expectedPath string
expectedRawPath string
expectedHeader string
}{
{
@@ -61,6 +61,13 @@ func TestStripPrefixRegex(t *testing.T) {
path: "/c/api/abc/test4",
expectedStatusCode: http.StatusNotFound,
},
{
path: "/a/api/a%2Fb",
expectedStatusCode: http.StatusOK,
expectedPath: "a/b",
expectedRawPath: "a%2Fb",
expectedHeader: "/a/api/",
},
}
for _, test := range tests {
@@ -68,9 +75,10 @@ func TestStripPrefixRegex(t *testing.T) {
t.Run(test.path, func(t *testing.T) {
t.Parallel()
var actualPath, actualHeader string
var actualPath, actualRawPath, actualHeader string
handlerPath := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
actualPath = r.URL.Path
actualRawPath = r.URL.RawPath
actualHeader = r.Header.Get(ForwardedPrefixHeader)
})
handler := NewStripPrefixRegex(handlerPath, testPrefixRegex)
@@ -82,6 +90,7 @@ func TestStripPrefixRegex(t *testing.T) {
assert.Equal(t, test.expectedStatusCode, resp.Code, "Unexpected status code.")
assert.Equal(t, test.expectedPath, actualPath, "Unexpected path.")
assert.Equal(t, test.expectedRawPath, actualRawPath, "Unexpected raw path.")
assert.Equal(t, test.expectedHeader, actualHeader, "Unexpected '%s' header.", ForwardedPrefixHeader)
})
}

View File

@@ -16,6 +16,7 @@ func TestStripPrefix(t *testing.T) {
path string
expectedStatusCode int
expectedPath string
expectedRawPath string
expectedHeader string
}{
{
@@ -94,6 +95,15 @@ func TestStripPrefix(t *testing.T) {
expectedPath: "/us",
expectedHeader: "/stat",
},
{
desc: "raw path is also stripped",
prefixes: []string{"/stat"},
path: "/stat/a%2Fb",
expectedStatusCode: http.StatusOK,
expectedPath: "/a/b",
expectedRawPath: "/a%2Fb",
expectedHeader: "/stat",
},
}
for _, test := range tests {
@@ -101,11 +111,12 @@ func TestStripPrefix(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
var actualPath, actualHeader, requestURI string
var actualPath, actualRawPath, actualHeader, requestURI string
handler := &StripPrefix{
Prefixes: test.prefixes,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
actualPath = r.URL.Path
actualRawPath = r.URL.RawPath
actualHeader = r.Header.Get(ForwardedPrefixHeader)
requestURI = r.RequestURI
}),
@@ -118,8 +129,15 @@ func TestStripPrefix(t *testing.T) {
assert.Equal(t, test.expectedStatusCode, resp.Code, "Unexpected status code.")
assert.Equal(t, test.expectedPath, actualPath, "Unexpected path.")
assert.Equal(t, test.expectedRawPath, actualRawPath, "Unexpected raw path.")
assert.Equal(t, test.expectedHeader, actualHeader, "Unexpected '%s' header.", ForwardedPrefixHeader)
assert.Equal(t, test.expectedPath, requestURI, "Unexpected request URI.")
expectedURI := test.expectedPath
if test.expectedRawPath != "" {
// go HTTP uses the raw path when existent in the RequestURI
expectedURI = test.expectedRawPath
}
assert.Equal(t, expectedURI, requestURI, "Unexpected request URI.")
})
}
}

View File

@@ -95,3 +95,4 @@ pages:
- 'Clustering/HA': 'user-guide/cluster.md'
- 'gRPC Example': 'user-guide/grpc.md'
- Benchmarks: benchmarks.md
- 'Archive': 'archive.md'

View File

@@ -77,9 +77,14 @@ func (a nodeSorter) Less(i int, j int) bool {
return lentr.Service.Port < rentr.Service.Port
}
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)
func hasChanged(current map[string]Service, previous map[string]Service) bool {
addedServiceKeys, removedServiceKeys := getChangedServiceKeys(current, previous)
return len(removedServiceKeys) > 0 || len(addedServiceKeys) > 0 || hasNodeOrTagsChanged(current, previous)
}
func getChangedServiceKeys(current map[string]Service, previous map[string]Service) ([]string, []string) {
currKeySet := fun.Set(fun.Keys(current).([]string)).(map[string]bool)
prevKeySet := fun.Set(fun.Keys(previous).([]string)).(map[string]bool)
addedKeys := fun.Difference(currKeySet, prevKeySet).(map[string]bool)
removedKeys := fun.Difference(prevKeySet, currKeySet).(map[string]bool)
@@ -87,20 +92,23 @@ func getChangedServiceKeys(currState map[string]Service, prevState map[string]Se
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)
func hasNodeOrTagsChanged(current map[string]Service, previous map[string]Service) bool {
var added []string
var removed []string
for key, value := range current {
if prevValue, ok := previous[key]; ok {
addedNodesKeys, removedNodesKeys := getChangedStringKeys(value.Nodes, prevValue.Nodes)
added = append(added, addedNodesKeys...)
removed = append(removed, removedNodesKeys...)
addedTagsKeys, removedTagsKeys := getChangedStringKeys(value.Tags, prevValue.Tags)
added = append(added, addedTagsKeys...)
removed = append(removed, removedTagsKeys...)
}
}
return addedNodeKeys, removedNodeKeys
return len(added) > 0 || len(removed) > 0
}
func getChangedHealthyKeys(currState []string, prevState []string) ([]string, []string) {
func getChangedStringKeys(currState []string, prevState []string) ([]string, []string) {
currKeySet := fun.Set(currState).(map[string]bool)
prevKeySet := fun.Set(prevState).(map[string]bool)
@@ -163,7 +171,7 @@ 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 := getChangedHealthyKeys(current, flashback)
addedKeys, removedKeys := getChangedStringKeys(current, flashback)
if len(addedKeys) > 0 {
log.WithField("DiscoveredServices", addedKeys).Debug("Health State change detected.")
@@ -242,12 +250,7 @@ func (p *CatalogProvider) watchCatalogServices(stopCh <-chan struct{}, watchCh c
// 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...
addedServiceKeys, removedServiceKeys := getChangedServiceKeys(current, flashback)
addedServiceNodeKeys, removedServiceNodeKeys := getChangedServiceNodeKeys(current, flashback)
if len(removedServiceKeys) > 0 || len(removedServiceNodeKeys) > 0 || len(addedServiceKeys) > 0 || len(addedServiceNodeKeys) > 0 {
log.WithField("MissingServices", removedServiceKeys).WithField("DiscoveredServices", addedServiceKeys).Debug("Catalog Services change detected.")
if hasChanged(current, flashback) {
watchCh <- data
flashback = current
}
@@ -255,6 +258,7 @@ func (p *CatalogProvider) watchCatalogServices(stopCh <-chan struct{}, watchCh c
}
})
}
func getServiceIds(services []*api.CatalogService) []string {
var serviceIds []string
for _, service := range services {
@@ -271,7 +275,6 @@ func (p *CatalogProvider) healthyNodes(service string) (catalogUpdate, error) {
log.WithError(err).Errorf("Failed to fetch details of %s", service)
return catalogUpdate{}, err
}
nodes := fun.Filter(func(node *api.ServiceEntry) bool {
return p.nodeFilter(service, node)
}, data).([]*api.ServiceEntry)

View File

@@ -1,7 +1,6 @@
package consul
import (
"reflect"
"sort"
"testing"
"text/template"
@@ -13,19 +12,13 @@ import (
)
func TestConsulCatalogGetFrontendRule(t *testing.T) {
provider := &CatalogProvider{
Domain: "localhost",
Prefix: "traefik",
FrontEndRule: "Host:{{.ServiceName}}.{{.Domain}}",
frontEndRuleTemplate: template.New("consul catalog frontend rule"),
}
provider.setupFrontEndTemplate()
services := []struct {
testCases := []struct {
desc string
service serviceUpdate
expected string
}{
{
desc: "Should return default host foo.localhost",
service: serviceUpdate{
ServiceName: "foo",
Attributes: []string{},
@@ -33,6 +26,7 @@ func TestConsulCatalogGetFrontendRule(t *testing.T) {
expected: "Host:foo.localhost",
},
{
desc: "Should return host *.example.com",
service: serviceUpdate{
ServiceName: "foo",
Attributes: []string{
@@ -42,6 +36,7 @@ func TestConsulCatalogGetFrontendRule(t *testing.T) {
expected: "Host:*.example.com",
},
{
desc: "Should return host foo.example.com",
service: serviceUpdate{
ServiceName: "foo",
Attributes: []string{
@@ -51,6 +46,7 @@ func TestConsulCatalogGetFrontendRule(t *testing.T) {
expected: "Host:foo.example.com",
},
{
desc: "Should return path prefix /bar",
service: serviceUpdate{
ServiceName: "foo",
Attributes: []string{
@@ -62,11 +58,22 @@ func TestConsulCatalogGetFrontendRule(t *testing.T) {
},
}
for _, e := range services {
actual := provider.getFrontendRule(e.service)
if actual != e.expected {
t.Fatalf("expected %s, got %s", e.expected, actual)
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
provider := &CatalogProvider{
Domain: "localhost",
Prefix: "traefik",
FrontEndRule: "Host:{{.ServiceName}}.{{.Domain}}",
frontEndRuleTemplate: template.New("consul catalog frontend rule"),
}
provider.setupFrontEndTemplate()
actual := provider.getFrontendRule(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
@@ -76,13 +83,15 @@ func TestConsulCatalogGetTag(t *testing.T) {
Prefix: "traefik",
}
services := []struct {
testCases := []struct {
desc string
tags []string
key string
defaultValue string
expected string
}{
{
desc: "Should return value of foo.bar key",
tags: []string{
"foo.bar=random",
"traefik.backend.weight=42",
@@ -94,21 +103,17 @@ func TestConsulCatalogGetTag(t *testing.T) {
},
}
actual := provider.hasTag("management", []string{"management"})
if !actual {
t.Fatalf("expected %v, got %v", true, actual)
}
assert.Equal(t, true, provider.hasTag("management", []string{"management"}))
assert.Equal(t, true, provider.hasTag("management", []string{"management=yes"}))
actual = provider.hasTag("management", []string{"management=yes"})
if !actual {
t.Fatalf("expected %v, got %v", true, actual)
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
for _, e := range services {
actual := provider.getTag(e.key, e.tags, e.defaultValue)
if actual != e.expected {
t.Fatalf("expected %s, got %s", e.expected, actual)
}
actual := provider.getTag(test.key, test.tags, test.defaultValue)
assert.Equal(t, test.expected, actual)
})
}
}
@@ -118,13 +123,15 @@ func TestConsulCatalogGetAttribute(t *testing.T) {
Prefix: "traefik",
}
services := []struct {
testCases := []struct {
desc string
tags []string
key string
defaultValue string
expected string
}{
{
desc: "Should return tag value 42",
tags: []string{
"foo.bar=ramdom",
"traefik.backend.weight=42",
@@ -134,6 +141,7 @@ func TestConsulCatalogGetAttribute(t *testing.T) {
expected: "42",
},
{
desc: "Should return tag default value 0",
tags: []string{
"foo.bar=ramdom",
"traefik.backend.wei=42",
@@ -144,17 +152,16 @@ func TestConsulCatalogGetAttribute(t *testing.T) {
},
}
expected := provider.Prefix + ".foo"
actual := provider.getPrefixedName("foo")
if actual != expected {
t.Fatalf("expected %s, got %s", expected, actual)
}
assert.Equal(t, provider.Prefix+".foo", provider.getPrefixedName("foo"))
for _, e := range services {
actual := provider.getAttribute(e.key, e.tags, e.defaultValue)
if actual != e.expected {
t.Fatalf("expected %s, got %s", e.expected, actual)
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := provider.getAttribute(test.key, test.tags, test.defaultValue)
assert.Equal(t, test.expected, actual)
})
}
}
@@ -164,13 +171,15 @@ func TestConsulCatalogGetAttributeWithEmptyPrefix(t *testing.T) {
Prefix: "",
}
services := []struct {
testCases := []struct {
desc string
tags []string
key string
defaultValue string
expected string
}{
{
desc: "Should return tag value 42",
tags: []string{
"foo.bar=ramdom",
"backend.weight=42",
@@ -180,6 +189,7 @@ func TestConsulCatalogGetAttributeWithEmptyPrefix(t *testing.T) {
expected: "42",
},
{
desc: "Should return default value 0",
tags: []string{
"foo.bar=ramdom",
"backend.wei=42",
@@ -189,6 +199,7 @@ func TestConsulCatalogGetAttributeWithEmptyPrefix(t *testing.T) {
expected: "0",
},
{
desc: "Should return for.bar key value random",
tags: []string{
"foo.bar=ramdom",
"backend.wei=42",
@@ -199,17 +210,16 @@ func TestConsulCatalogGetAttributeWithEmptyPrefix(t *testing.T) {
},
}
expected := "foo"
actual := provider.getPrefixedName("foo")
if actual != expected {
t.Fatalf("expected %s, got %s", expected, actual)
}
assert.Equal(t, "foo", provider.getPrefixedName("foo"))
for _, e := range services {
actual := provider.getAttribute(e.key, e.tags, e.defaultValue)
if actual != e.expected {
t.Fatalf("expected %s, got %s", e.expected, actual)
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := provider.getAttribute(test.key, test.tags, test.defaultValue)
assert.Equal(t, test.expected, actual)
})
}
}
@@ -219,11 +229,13 @@ func TestConsulCatalogGetBackendAddress(t *testing.T) {
Prefix: "traefik",
}
services := []struct {
testCases := []struct {
desc string
node *api.ServiceEntry
expected string
}{
{
desc: "Should return the address of the service",
node: &api.ServiceEntry{
Node: &api.Node{
Address: "10.1.0.1",
@@ -235,6 +247,7 @@ func TestConsulCatalogGetBackendAddress(t *testing.T) {
expected: "10.2.0.1",
},
{
desc: "Should return the address of the node",
node: &api.ServiceEntry{
Node: &api.Node{
Address: "10.1.0.1",
@@ -247,11 +260,14 @@ func TestConsulCatalogGetBackendAddress(t *testing.T) {
},
}
for _, e := range services {
actual := provider.getBackendAddress(e.node)
if actual != e.expected {
t.Fatalf("expected %s, got %s", e.expected, actual)
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := provider.getBackendAddress(test.node)
assert.Equal(t, test.expected, actual)
})
}
}
@@ -261,11 +277,13 @@ func TestConsulCatalogGetBackendName(t *testing.T) {
Prefix: "traefik",
}
services := []struct {
testCases := []struct {
desc string
node *api.ServiceEntry
expected string
}{
{
desc: "Should create backend name without tags",
node: &api.ServiceEntry{
Service: &api.AgentService{
Service: "api",
@@ -277,6 +295,7 @@ func TestConsulCatalogGetBackendName(t *testing.T) {
expected: "api--10-0-0-1--80--0",
},
{
desc: "Should create backend name with multiple tags",
node: &api.ServiceEntry{
Service: &api.AgentService{
Service: "api",
@@ -288,6 +307,7 @@ func TestConsulCatalogGetBackendName(t *testing.T) {
expected: "api--10-0-0-1--80--traefik-weight-42--traefik-enable-true--1",
},
{
desc: "Should create backend name with one tag",
node: &api.ServiceEntry{
Service: &api.AgentService{
Service: "api",
@@ -300,11 +320,15 @@ func TestConsulCatalogGetBackendName(t *testing.T) {
},
}
for i, e := range services {
actual := provider.getBackendName(e.node, i)
if actual != e.expected {
t.Fatalf("expected %s, got %s", e.expected, actual)
}
for i, test := range testCases {
test := test
i := i
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := provider.getBackendName(test.node, i)
assert.Equal(t, test.expected, actual)
})
}
}
@@ -317,17 +341,20 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
frontEndRuleTemplate: template.New("consul catalog frontend rule"),
}
cases := []struct {
testCases := []struct {
desc string
nodes []catalogUpdate
expectedFrontends map[string]*types.Frontend
expectedBackends map[string]*types.Backend
}{
{
desc: "Should build config of nothing",
nodes: []catalogUpdate{},
expectedFrontends: map[string]*types.Frontend{},
expectedBackends: map[string]*types.Backend{},
},
{
desc: "Should build config with no frontend and backend",
nodes: []catalogUpdate{
{
Service: &serviceUpdate{
@@ -339,6 +366,7 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
expectedBackends: map[string]*types.Backend{},
},
{
desc: "Should build config who contains one frontend and one backend",
nodes: []catalogUpdate{
{
Service: &serviceUpdate{
@@ -408,28 +436,31 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
},
}
for _, c := range cases {
actualConfig := provider.buildConfig(c.nodes)
if !reflect.DeepEqual(actualConfig.Backends, c.expectedBackends) {
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)
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actualConfig := provider.buildConfig(test.nodes)
assert.Equal(t, test.expectedBackends, actualConfig.Backends)
assert.Equal(t, test.expectedFrontends, actualConfig.Frontends)
})
}
}
func TestConsulCatalogNodeSorter(t *testing.T) {
cases := []struct {
testCases := []struct {
desc string
nodes []*api.ServiceEntry
expected []*api.ServiceEntry
}{
{
desc: "Should sort nothing",
nodes: []*api.ServiceEntry{},
expected: []*api.ServiceEntry{},
},
{
desc: "Should sort by node address",
nodes: []*api.ServiceEntry{
{
Service: &api.AgentService{
@@ -458,6 +489,7 @@ func TestConsulCatalogNodeSorter(t *testing.T) {
},
},
{
desc: "Should sort by service name",
nodes: []*api.ServiceEntry{
{
Service: &api.AgentService{
@@ -552,6 +584,7 @@ func TestConsulCatalogNodeSorter(t *testing.T) {
},
},
{
desc: "Should sort by node address",
nodes: []*api.ServiceEntry{
{
Service: &api.AgentService{
@@ -603,12 +636,15 @@ func TestConsulCatalogNodeSorter(t *testing.T) {
},
}
for _, c := range cases {
sort.Sort(nodeSorter(c.nodes))
actual := c.nodes
if !reflect.DeepEqual(actual, c.expected) {
t.Fatalf("expected %q, got %q", c.expected, actual)
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
sort.Sort(nodeSorter(test.nodes))
actual := test.nodes
assert.Equal(t, test.expected, actual)
})
}
}
@@ -623,11 +659,13 @@ func TestConsulCatalogGetChangedKeys(t *testing.T) {
removedKeys []string
}
cases := []struct {
testCases := []struct {
desc string
input Input
output Output
}{
{
desc: "Should add 0 services and removed 0",
input: Input{
currState: map[string]Service{
"foo-service": {Name: "v1"},
@@ -668,6 +706,7 @@ func TestConsulCatalogGetChangedKeys(t *testing.T) {
},
},
{
desc: "Should add 3 services and removed 0",
input: Input{
currState: map[string]Service{
"foo-service": {Name: "v1"},
@@ -705,6 +744,7 @@ func TestConsulCatalogGetChangedKeys(t *testing.T) {
},
},
{
desc: "Should add 2 services and removed 2",
input: Input{
currState: map[string]Service{
"foo-service": {Name: "v1"},
@@ -742,21 +782,20 @@ func TestConsulCatalogGetChangedKeys(t *testing.T) {
},
}
for _, c := range cases {
addedKeys, removedKeys := getChangedServiceKeys(c.input.currState, c.input.prevState)
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
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)
}
if !reflect.DeepEqual(fun.Set(removedKeys), fun.Set(c.output.removedKeys)) {
t.Fatalf("Removed keys comparison results: got %q, want %q", removedKeys, c.output.removedKeys)
}
addedKeys, removedKeys := getChangedServiceKeys(test.input.currState, test.input.prevState)
assert.Equal(t, fun.Set(test.output.addedKeys), fun.Set(addedKeys), "Added keys comparison results: got %q, want %q", addedKeys, test.output.addedKeys)
assert.Equal(t, fun.Set(test.output.removedKeys), fun.Set(removedKeys), "Removed keys comparison results: got %q, want %q", removedKeys, test.output.removedKeys)
})
}
}
func TestConsulCatalogFilterEnabled(t *testing.T) {
cases := []struct {
testCases := []struct {
desc string
exposedByDefault bool
node *api.ServiceEntry
@@ -842,24 +881,23 @@ func TestConsulCatalogFilterEnabled(t *testing.T) {
},
}
for _, c := range cases {
c := c
t.Run(c.desc, func(t *testing.T) {
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
provider := &CatalogProvider{
Domain: "localhost",
Prefix: "traefik",
ExposedByDefault: c.exposedByDefault,
}
if provider.nodeFilter("test", c.node) != c.expected {
t.Errorf("got unexpected filtering = %t", !c.expected)
ExposedByDefault: test.exposedByDefault,
}
actual := provider.nodeFilter("test", test.node)
assert.Equal(t, test.expected, actual)
})
}
}
func TestConsulCatalogGetBasicAuth(t *testing.T) {
cases := []struct {
testCases := []struct {
desc string
tags []string
expected []string
@@ -878,17 +916,15 @@ func TestConsulCatalogGetBasicAuth(t *testing.T) {
},
}
for _, c := range cases {
c := c
t.Run(c.desc, func(t *testing.T) {
for _, test := range testCases {
test := test
t.Run(test.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)
}
actual := provider.getBasicAuth(test.tags)
assert.Equal(t, test.expected, actual)
})
}
}
@@ -930,7 +966,276 @@ func TestConsulCatalogHasStickinessLabel(t *testing.T) {
t.Parallel()
actual := provider.hasStickinessLabel(test.tags)
assert.Equal(t, actual, test.expected)
assert.Equal(t, test.expected, actual)
})
}
}
func TestConsulCatalogGetChangedStringKeys(t *testing.T) {
testCases := []struct {
desc string
current []string
previous []string
expectedAdded []string
expectedRemoved []string
}{
{
desc: "1 element added, 0 removed",
current: []string{"chou"},
previous: []string{},
expectedAdded: []string{"chou"},
expectedRemoved: []string{},
}, {
desc: "0 element added, 0 removed",
current: []string{"chou"},
previous: []string{"chou"},
expectedAdded: []string{},
expectedRemoved: []string{},
},
{
desc: "0 element added, 1 removed",
current: []string{},
previous: []string{"chou"},
expectedAdded: []string{},
expectedRemoved: []string{"chou"},
},
{
desc: "1 element added, 1 removed",
current: []string{"carotte"},
previous: []string{"chou"},
expectedAdded: []string{"carotte"},
expectedRemoved: []string{"chou"},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actualAdded, actualRemoved := getChangedStringKeys(test.current, test.previous)
assert.Equal(t, test.expectedAdded, actualAdded)
assert.Equal(t, test.expectedRemoved, actualRemoved)
})
}
}
func TestConsulCatalogHasNodeOrTagschanged(t *testing.T) {
testCases := []struct {
desc string
current map[string]Service
previous map[string]Service
expected bool
}{
{
desc: "Change detected due to change of nodes",
current: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{},
},
},
previous: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node2"},
Tags: []string{},
},
},
expected: true,
},
{
desc: "No change missing current service",
current: make(map[string]Service),
previous: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{},
},
},
expected: false,
},
{
desc: "No change on nodes",
current: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{},
},
},
previous: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{},
},
},
expected: false,
},
{
desc: "No change on nodes and tags",
current: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{"foo=bar"},
},
},
previous: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{"foo=bar"},
},
},
expected: false,
},
{
desc: "Change detected con tags",
current: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{"foo=bar"},
},
},
previous: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{"foo"},
},
},
expected: true,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := hasNodeOrTagsChanged(test.current, test.previous)
assert.Equal(t, test.expected, actual)
})
}
}
func TestConsulCatalogHasChanged(t *testing.T) {
testCases := []struct {
desc string
current map[string]Service
previous map[string]Service
expected bool
}{
{
desc: "Change detected due to change new service",
current: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{},
},
},
previous: make(map[string]Service),
expected: true,
},
{
desc: "Change detected due to change service removed",
current: make(map[string]Service),
previous: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{},
},
},
expected: true,
},
{
desc: "Change detected due to change of nodes",
current: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{},
},
},
previous: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node2"},
Tags: []string{},
},
},
expected: true,
},
{
desc: "No change on nodes",
current: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{},
},
},
previous: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{},
},
},
expected: false,
},
{
desc: "No change on nodes and tags",
current: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{"foo=bar"},
},
},
previous: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{"foo=bar"},
},
},
expected: false,
},
{
desc: "Change detected on tags",
current: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{"foo=bar"},
},
},
previous: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{"foo"},
},
},
expected: true,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := hasChanged(test.current, test.previous)
assert.Equal(t, test.expected, actual)
})
}
}

View File

@@ -302,9 +302,15 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con
frontends := map[string][]dockerData{}
backends := map[string]dockerData{}
servers := map[string][]dockerData{}
serviceNames := make(map[string]struct{})
for idx, container := range filteredContainers {
frontendName := p.getFrontendName(container, idx)
frontends[frontendName] = append(frontends[frontendName], container)
if _, exists := serviceNames[container.ServiceName]; !exists {
frontendName := p.getFrontendName(container, idx)
frontends[frontendName] = append(frontends[frontendName], container)
if len(container.ServiceName) > 0 {
serviceNames[container.ServiceName] = struct{}{}
}
}
backendName := p.getBackend(container)
backends[backendName] = container
servers[backendName] = append(servers[backendName], container)
@@ -427,9 +433,9 @@ func (p *Provider) getServicePriority(container dockerData, serviceName string)
// Extract backend from labels for a given service and a given docker container
func (p *Provider) getServiceBackend(container dockerData, serviceName string) string {
if value, ok := getContainerServiceLabel(container, serviceName, "frontend.backend"); ok {
return value
return provider.Normalize(container.ServiceName + "-" + value)
}
return p.getBackend(container) + "-" + provider.Normalize(serviceName)
return provider.Normalize(container.ServiceName + "-" + p.getBackend(container) + "-" + serviceName)
}
// Extract rule from labels for a given service and a given docker container
@@ -829,8 +835,12 @@ func listContainers(ctx context.Context, dockerClient client.ContainerAPIClient)
if err != nil {
log.Warnf("Failed to inspect container %s, error: %s", container.ID, err)
} else {
dockerData := parseContainer(containerInspected)
containersInspected = append(containersInspected, dockerData)
// This condition is here to avoid to have empty IP https://github.com/containous/traefik/issues/2459
// We register only container which are running
if containerInspected.ContainerJSONBase != nil && containerInspected.ContainerJSONBase.State != nil && containerInspected.ContainerJSONBase.State.Running {
dockerData := parseContainer(containerInspected)
containersInspected = append(containersInspected, dockerData)
}
}
}
return containersInspected, nil

View File

@@ -171,19 +171,29 @@ func TestDockerGetServiceBackend(t *testing.T) {
}{
{
container: containerJSON(name("foo")),
expected: "foo-myservice",
expected: "foo-foo-myservice",
},
{
container: containerJSON(name("foo.bar")),
expected: "foo-bar-foo-bar-myservice",
},
{
container: containerJSON(labels(map[string]string{
types.LabelBackend: "another-backend",
})),
expected: "another-backend-myservice",
expected: "fake-another-backend-myservice",
},
{
container: containerJSON(labels(map[string]string{
types.LabelBackend: "another.backend",
})),
expected: "fake-another-backend-myservice",
},
{
container: containerJSON(labels(map[string]string{
"traefik.myservice.frontend.backend": "custom-backend",
})),
expected: "custom-backend",
expected: "fake-custom-backend",
},
}
@@ -341,8 +351,8 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
),
},
expectedFrontends: map[string]*types.Frontend{
"frontend-foo-service": {
Backend: "backend-foo-service",
"frontend-foo-foo-service": {
Backend: "backend-foo-foo-service",
PassHostHeader: true,
EntryPoints: []string{"http", "https"},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
@@ -354,7 +364,7 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
},
},
expectedBackends: map[string]*types.Backend{
"backend-foo-service": {
"backend-foo-foo-service": {
Servers: map[string]types.Server{
"service-0": {
URL: "http://127.0.0.1:2503",
@@ -399,8 +409,8 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
),
},
expectedFrontends: map[string]*types.Frontend{
"frontend-foobar": {
Backend: "backend-foobar",
"frontend-test1-foobar": {
Backend: "backend-test1-foobar",
PassHostHeader: false,
Priority: 5000,
EntryPoints: []string{"http", "https", "ws"},
@@ -411,8 +421,8 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
},
},
},
"frontend-test2-anotherservice": {
Backend: "backend-test2-anotherservice",
"frontend-test2-test2-anotherservice": {
Backend: "backend-test2-test2-anotherservice",
PassHostHeader: true,
EntryPoints: []string{},
BasicAuth: []string{},
@@ -424,7 +434,7 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
},
},
expectedBackends: map[string]*types.Backend{
"backend-foobar": {
"backend-test1-foobar": {
Servers: map[string]types.Server{
"service-0": {
URL: "https://127.0.0.1:2503",
@@ -433,7 +443,7 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
},
CircuitBreaker: nil,
},
"backend-test2-anotherservice": {
"backend-test2-test2-anotherservice": {
Servers: map[string]types.Server{
"service-0": {
URL: "http://127.0.0.1:8079",

View File

@@ -325,13 +325,13 @@ func getRuleForPath(pa v1beta1.HTTPIngressPath, i *v1beta1.Ingress) string {
ruleType = ruleTypePathPrefix
}
rule := ruleType + ":" + pa.Path
rules := []string{ruleType + ":" + pa.Path}
if rewriteTarget := i.Annotations[annotationKubernetesRewriteTarget]; rewriteTarget != "" {
rule = ruleTypeReplacePath + ":" + rewriteTarget
rules = append(rules, ruleTypeReplacePath+":"+rewriteTarget)
}
return rule
return strings.Join(rules, ";")
}
func (p *Provider) getPriority(path v1beta1.HTTPIngressPath, i *v1beta1.Ingress) int {

View File

@@ -1728,7 +1728,7 @@ func TestIngressAnnotations(t *testing.T) {
PassHostHeader: true,
Routes: map[string]types.Route{
"/api": {
Rule: "ReplacePath:/",
Rule: "PathPrefix:/api;ReplacePath:/",
},
"rewrite": {
Rule: "Host:rewrite",

View File

@@ -102,7 +102,7 @@ func (p *Provider) watchKv(configurationChan chan<- types.ConfigMessage, prefix
func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
p.Constraints = append(p.Constraints, constraints...)
operation := func() error {
if _, err := p.kvclient.Exists("qmslkjdfmqlskdjfmqlksjazçueznbvbwzlkajzebvkwjdcqmlsfj"); err != nil {
if _, err := p.kvclient.Exists(p.Prefix + "/qmslkjdfmqlskdjfmqlksjazçueznbvbwzlkajzebvkwjdcqmlsfj"); err != nil {
return fmt.Errorf("Failed to test KV store connection: %v", err)
}
if p.Watch {

View File

@@ -126,7 +126,7 @@ func (p *Provider) hasStickinessLabel(service rancherData) bool {
return errStickiness == nil && len(labelStickiness) > 0 && strings.EqualFold(strings.TrimSpace(labelStickiness), "true")
}
func (p *Provider) getStickinessCookieName(service rancherData, backendName string) string {
func (p *Provider) getStickinessCookieName(service rancherData) string {
if label, err := getServiceLabel(service, types.LabelBackendLoadbalancerStickinessCookieName); err == nil {
return label
}

View File

@@ -24,13 +24,17 @@ 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}"
# Build 386 amd64 binaries
# Build amd64 binaries
OS_PLATFORM_ARG=(linux windows darwin)
OS_ARCH_ARG=(amd64)
for OS in ${OS_PLATFORM_ARG[@]}; do
BIN_EXT=''
if [ "$OS" == "windows" ]; then
BIN_EXT='.exe'
fi
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}${BIN_EXT}" ./cmd/traefik/
done
done

View File

@@ -28,9 +28,13 @@ GO_BUILD_OPT="-s -w -X ${GIT_REPO_URL}.Version=${VERSION} -X ${GIT_REPO_URL}.Cod
OS_PLATFORM_ARG=(linux windows darwin)
OS_ARCH_ARG=(386)
for OS in ${OS_PLATFORM_ARG[@]}; do
BIN_EXT=''
if [ "$OS" == "windows" ]; then
BIN_EXT='.exe'
fi
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}${BIN_EXT}" ./cmd/traefik/
done
done

View File

@@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"io/ioutil"
stdlog "log"
"net"
"net/http"
"net/url"
@@ -19,6 +20,7 @@ import (
"sync"
"time"
"github.com/Sirupsen/logrus"
"github.com/armon/go-proxyproto"
"github.com/containous/mux"
"github.com/containous/traefik/cluster"
@@ -46,7 +48,8 @@ import (
)
var (
oxyLogger = &OxyLogger{}
oxyLogger = &OxyLogger{}
httpServerLogger = stdlog.New(log.WriterLevel(logrus.DebugLevel), "", 0)
)
// Server is the reverse-proxy/load-balancer engine
@@ -665,7 +668,7 @@ func (server *Server) prepareServer(entryPointName string, entryPoint *configura
SourceCheck: func(addr net.Addr) (bool, error) {
ip, ok := addr.(*net.TCPAddr)
if !ok {
return false, fmt.Errorf("Type error %v", addr)
return false, fmt.Errorf("type error %v", addr)
}
return IPs.ContainsIP(ip.IP)
},
@@ -679,6 +682,7 @@ func (server *Server) prepareServer(entryPointName string, entryPoint *configura
ReadTimeout: readTimeout,
WriteTimeout: writeTimeout,
IdleTimeout: idleTimeout,
ErrorLog: httpServerLogger,
},
listener,
nil

View File

@@ -150,7 +150,9 @@ func (w *GzipResponseWriter) startGzip() error {
// WriteHeader just saves the response code until close or GZIP effective writes.
func (w *GzipResponseWriter) WriteHeader(code int) {
w.code = code
if w.code == 0 {
w.code = code
}
}
// init graps a new gzip writer from the gzipWriterPool and writes the correct
@@ -190,10 +192,16 @@ func (w *GzipResponseWriter) Close() error {
// http.ResponseWriter if it is an http.Flusher. This makes GzipResponseWriter
// an http.Flusher.
func (w *GzipResponseWriter) Flush() {
if w.gw != nil {
w.gw.Flush()
if w.gw == nil {
// Only flush once startGzip has been called.
//
// Flush is thus a no-op until the written body
// exceeds minSize.
return
}
w.gw.Flush()
if fw, ok := w.ResponseWriter.(http.Flusher); ok {
fw.Flush()
}

View File

@@ -315,22 +315,29 @@ func (f *websocketForwarder) serveHTTP(w http.ResponseWriter, req *http.Request,
defer targetConn.Close()
errc := make(chan error, 2)
replicate := func(dst io.Writer, src io.Reader) {
_, err := io.Copy(dst, src)
replicateWebsocketConn := func(dst, src *websocket.Conn, dstName, srcName string) {
var err error
for {
msgType, msg, err := src.ReadMessage()
if err != nil {
ctx.log.Errorf("vulcand/oxy/forward/websocket: Error when copying from %s to %s using ReadMessage: %v", srcName, dstName, err)
break
}
err = dst.WriteMessage(msgType, msg)
if err != nil {
ctx.log.Errorf("vulcand/oxy/forward/websocket: Error when copying from %s to %s using WriteMessage: %v", srcName, dstName, err)
break
} else {
ctx.log.Infof("vulcand/oxy/forward/websocket: Copying from %s to %s completed without error.", srcName, dstName)
}
}
errc <- err
}
go replicate(targetConn.UnderlyingConn(), underlyingConn.UnderlyingConn())
go replicateWebsocketConn(underlyingConn, targetConn, "client", "backend")
go replicateWebsocketConn(targetConn, underlyingConn, "backend", "client")
// Try to read the first message
t, msg, err := targetConn.ReadMessage()
if err != nil {
ctx.log.Errorf("Couldn't read first message : %v", err)
} else {
underlyingConn.WriteMessage(t, msg)
}
go replicate(underlyingConn.UnderlyingConn(), targetConn.UnderlyingConn())
<-errc
}

View File

@@ -132,7 +132,7 @@ func RemoveHeaders(headers http.Header, names ...string) {
}
// Parse the MIME media type value of a header.
func GetHeaderMediaType(headers http.Header, name string) (string, error) {
func GetHeaderMediaType(headers http.Header, name string) (string, error) {
mediatype, _, err := mime.ParseMediaType(headers.Get(name))
return mediatype, err
}