Merge branch 'v1.5' into master
This commit is contained in:
commit
21e28ae848
30 changed files with 451 additions and 203 deletions
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
|
@ -22,7 +22,7 @@ If you intend to ask a support question: DO NOT FILE AN ISSUE.
|
||||||
|
|
||||||
HOW TO WRITE A GOOD ISSUE?
|
HOW TO WRITE A GOOD ISSUE?
|
||||||
|
|
||||||
- Respect the issue template as more as possible.
|
- Respect the issue template as much as possible.
|
||||||
- If it's possible use the command `traefik bug`. See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
- If it's possible use the command `traefik bug`. See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
||||||
- The title must be short and descriptive.
|
- The title must be short and descriptive.
|
||||||
- Explain the conditions which led you to write this issue: the context.
|
- Explain the conditions which led you to write this issue: the context.
|
||||||
|
|
68
.github/ISSUE_TEMPLATE/bugs.md
vendored
Normal file
68
.github/ISSUE_TEMPLATE/bugs.md
vendored
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
<!--
|
||||||
|
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||||
|
|
||||||
|
The issue tracker is for reporting bugs and feature requests only.
|
||||||
|
For end-user related support questions, refer to one of the following:
|
||||||
|
|
||||||
|
- Stack Overflow (using the "traefik" tag): https://stackoverflow.com/questions/tagged/traefik
|
||||||
|
- the Traefik community Slack channel: https://traefik.herokuapp.com
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
### Do you want to request a *feature* or report a *bug*?
|
||||||
|
|
||||||
|
Bug
|
||||||
|
|
||||||
|
### What did you do?
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|
HOW TO WRITE A GOOD ISSUE?
|
||||||
|
|
||||||
|
- Respect the issue template as much as possible.
|
||||||
|
- If it's possible use the command `traefik bug`. See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
||||||
|
- The title must be short and descriptive.
|
||||||
|
- Explain the conditions which led you to write this issue: the context.
|
||||||
|
- The context should lead to something, an idea or a problem that you’re facing.
|
||||||
|
- Remain clear and concise.
|
||||||
|
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
### What did you expect to see?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### What did you see instead?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Output of `traefik version`: (_What version of Traefik are you using?_)
|
||||||
|
|
||||||
|
<!--
|
||||||
|
For the Traefik Docker image:
|
||||||
|
docker run [IMAGE] version
|
||||||
|
ex: docker run traefik version
|
||||||
|
-->
|
||||||
|
|
||||||
|
```
|
||||||
|
(paste your output here)
|
||||||
|
```
|
||||||
|
|
||||||
|
### What is your environment & configuration (arguments, toml, provider, platform, ...)?
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# (paste your configuration here)
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Add more configuration information here.
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
### If applicable, please paste the log output in debug mode (`--debug` switch)
|
||||||
|
|
||||||
|
```
|
||||||
|
(paste your output here)
|
||||||
|
```
|
32
.github/ISSUE_TEMPLATE/features.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE/features.md
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<!--
|
||||||
|
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||||
|
|
||||||
|
The issue tracker is for reporting bugs and feature requests only.
|
||||||
|
For end-user related support questions, refer to one of the following:
|
||||||
|
|
||||||
|
- Stack Overflow (using the "traefik" tag): https://stackoverflow.com/questions/tagged/traefik
|
||||||
|
- the Traefik community Slack channel: https://traefik.herokuapp.com
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
### Do you want to request a *feature* or report a *bug*?
|
||||||
|
|
||||||
|
Feature
|
||||||
|
|
||||||
|
### What did you expect to see?
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|
HOW TO WRITE A GOOD ISSUE?
|
||||||
|
|
||||||
|
- Respect the issue template as much as possible.
|
||||||
|
- If it's possible use the command `traefik bug`. See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
||||||
|
- The title must be short and descriptive.
|
||||||
|
- Explain the conditions which led you to write this issue: the context.
|
||||||
|
- The context should lead to something, an idea or a problem that you’re facing.
|
||||||
|
- Remain clear and concise.
|
||||||
|
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
7
.github/PULL_REQUEST_TEMPLATE/mergeback.md
vendored
Normal file
7
.github/PULL_REQUEST_TEMPLATE/mergeback.md
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
### What does this PR do?
|
||||||
|
|
||||||
|
Merge v{{.Version}} into master
|
||||||
|
|
||||||
|
### Motivation
|
||||||
|
|
||||||
|
Be sync.
|
7
.github/PULL_REQUEST_TEMPLATE/release.md
vendored
Normal file
7
.github/PULL_REQUEST_TEMPLATE/release.md
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
### What does this PR do?
|
||||||
|
|
||||||
|
Prepare release v{{.Version}}.
|
||||||
|
|
||||||
|
### Motivation
|
||||||
|
|
||||||
|
Create a new release.
|
|
@ -12,7 +12,7 @@
|
||||||
[![Twitter](https://img.shields.io/twitter/follow/traefikproxy.svg?style=social)](https://twitter.com/intent/follow?screen_name=traefikproxy)
|
[![Twitter](https://img.shields.io/twitter/follow/traefikproxy.svg?style=social)](https://twitter.com/intent/follow?screen_name=traefikproxy)
|
||||||
|
|
||||||
|
|
||||||
Træfik (pronounced like [traffic](https://speak-ipa.bearbin.net/speak.cgi?speak=%CB%88tr%C3%A6f%C9%AAk)) is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
Træfik (pronounced like _traffic_) is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||||
It supports several backends ([Docker](https://www.docker.com/), [Swarm mode](https://docs.docker.com/engine/swarm/), [Kubernetes](https://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Rancher](https://rancher.com), [Amazon ECS](https://aws.amazon.com/ecs), and a lot more) to manage its configuration automatically and dynamically.
|
It supports several backends ([Docker](https://www.docker.com/), [Swarm mode](https://docs.docker.com/engine/swarm/), [Kubernetes](https://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Rancher](https://rancher.com), [Amazon ECS](https://aws.amazon.com/ecs), and a lot more) to manage its configuration automatically and dynamically.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
@ -29,11 +29,6 @@ func runHealthCheck(traefikConfiguration *TraefikConfiguration) func() error {
|
||||||
return func() error {
|
return func() error {
|
||||||
traefikConfiguration.GlobalConfiguration.SetEffectiveConfiguration(traefikConfiguration.ConfigFile)
|
traefikConfiguration.GlobalConfiguration.SetEffectiveConfiguration(traefikConfiguration.ConfigFile)
|
||||||
|
|
||||||
if traefikConfiguration.Ping == nil {
|
|
||||||
fmt.Println("Please enable `ping` to use healtcheck.")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, errPing := healthCheck(traefikConfiguration.GlobalConfiguration)
|
resp, errPing := healthCheck(traefikConfiguration.GlobalConfiguration)
|
||||||
if errPing != nil {
|
if errPing != nil {
|
||||||
fmt.Printf("Error calling healthcheck: %s\n", errPing)
|
fmt.Printf("Error calling healthcheck: %s\n", errPing)
|
||||||
|
@ -50,9 +45,13 @@ func runHealthCheck(traefikConfiguration *TraefikConfiguration) func() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func healthCheck(globalConfiguration configuration.GlobalConfiguration) (*http.Response, error) {
|
func healthCheck(globalConfiguration configuration.GlobalConfiguration) (*http.Response, error) {
|
||||||
|
if globalConfiguration.Ping == nil {
|
||||||
|
return nil, errors.New("please enable `ping` to use health check")
|
||||||
|
}
|
||||||
|
|
||||||
pingEntryPoint, ok := globalConfiguration.EntryPoints[globalConfiguration.Ping.EntryPoint]
|
pingEntryPoint, ok := globalConfiguration.EntryPoints[globalConfiguration.Ping.EntryPoint]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("missing ping entrypoint")
|
return nil, errors.New("missing `ping` entrypoint")
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{Timeout: 5 * time.Second}
|
client := &http.Client{Timeout: 5 * time.Second}
|
||||||
|
|
|
@ -135,6 +135,7 @@ entryPoint = "https"
|
||||||
#
|
#
|
||||||
# delayBeforeCheck = 0
|
# delayBeforeCheck = 0
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
Even if `TLS-SNI-01` challenge is [disabled](https://community.letsencrypt.org/t/2018-01-11-update-regarding-acme-tls-sni-and-shared-hosting-infrastructure/50188) for the moment, it stays the _by default_ ACME Challenge in Træfik.
|
Even if `TLS-SNI-01` challenge is [disabled](https://community.letsencrypt.org/t/2018-01-11-update-regarding-acme-tls-sni-and-shared-hosting-infrastructure/50188) for the moment, it stays the _by default_ ACME Challenge in Træfik.
|
||||||
If `TLS-SNI-01` challenge is not re-enabled in the future, it we will be removed from Træfik.
|
If `TLS-SNI-01` challenge is not re-enabled in the future, it we will be removed from Træfik.
|
||||||
|
@ -149,12 +150,13 @@ entryPoint = "https"
|
||||||
Let's Encrypt functionality will be limited until Træfik is restarted.
|
Let's Encrypt functionality will be limited until Træfik is restarted.
|
||||||
|
|
||||||
If Let's Encrypt is not reachable, these certificates will be used :
|
If Let's Encrypt is not reachable, these certificates will be used :
|
||||||
|
|
||||||
- ACME certificates already generated before downtime
|
- ACME certificates already generated before downtime
|
||||||
- Expired ACME certificates
|
- Expired ACME certificates
|
||||||
- Provided certificates
|
- Provided certificates
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
Default Træfik certificate will be used instead of ACME certificates for new (sub)domains (which need Let's Encrypt challenge).
|
Default Træfik certificate will be used instead of ACME certificates for new (sub)domains (which need Let's Encrypt challenge).
|
||||||
|
|
||||||
### `storage`
|
### `storage`
|
||||||
|
|
||||||
|
@ -168,6 +170,7 @@ storage = "acme.json"
|
||||||
The `storage` option sets where are stored your ACME certificates.
|
The `storage` option sets where are stored your ACME certificates.
|
||||||
|
|
||||||
There are two kind of `storage` :
|
There are two kind of `storage` :
|
||||||
|
|
||||||
- a JSON file,
|
- a JSON file,
|
||||||
- a KV store entry.
|
- a KV store entry.
|
||||||
|
|
||||||
|
@ -182,7 +185,7 @@ There are two kind of `storage` :
|
||||||
|
|
||||||
#### Store data in a file
|
#### Store data in a file
|
||||||
|
|
||||||
ACME certificates can be stored in a JSON file which with the `600` right mode.
|
ACME certificates can be stored in a JSON file which with the `600` right mode.
|
||||||
|
|
||||||
There are two ways to store ACME certificates in a file from Docker:
|
There are two ways to store ACME certificates in a file from Docker:
|
||||||
|
|
||||||
|
@ -240,6 +243,8 @@ entryPoint = "https"
|
||||||
Specify the entryPoint to use during the challenges.
|
Specify the entryPoint to use during the challenges.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
defaultEntryPoints = ["http", "http"]
|
||||||
|
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":80"
|
address = ":80"
|
||||||
|
@ -328,10 +333,10 @@ onDemand = true
|
||||||
|
|
||||||
Enable on demand certificate.
|
Enable on demand certificate.
|
||||||
|
|
||||||
This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate.
|
This will request a certificate from Let's Encrypt during the first TLS handshake for a host name that does not yet have a certificate.
|
||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
TLS handshakes will be slow when requesting a hostname certificate for the first time, this can lead to DoS attacks.
|
TLS handshakes will be slow when requesting a host name certificate for the first time, this can lead to DoS attacks.
|
||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
||||||
|
@ -345,7 +350,7 @@ onHostRule = true
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
Enable certificate generation on frontends Host rules.
|
Enable certificate generation on frontends `Host` rules (for frontends wired on the `acme.entryPoint`).
|
||||||
|
|
||||||
This will request a certificate from Let's Encrypt for each frontend with a Host rule.
|
This will request a certificate from Let's Encrypt for each frontend with a Host rule.
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# API Definition
|
# API Definition
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# API definition
|
# API definition
|
||||||
[api]
|
[api]
|
||||||
|
@ -28,6 +30,8 @@
|
||||||
debug = true
|
debug = true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For more customization, see [entry points](/configuration/entrypoints/) documentation and [examples](/user-guide/examples/#ping-health-check).
|
||||||
|
|
||||||
## Web UI
|
## Web UI
|
||||||
|
|
||||||
![Web UI Providers](/img/web.frontend.png)
|
![Web UI Providers](/img/web.frontend.png)
|
||||||
|
@ -42,7 +46,7 @@
|
||||||
| `/health` | `GET` | json health metrics |
|
| `/health` | `GET` | json health metrics |
|
||||||
| `/api` | `GET` | Configuration for all providers |
|
| `/api` | `GET` | Configuration for all providers |
|
||||||
| `/api/providers` | `GET` | Providers |
|
| `/api/providers` | `GET` | Providers |
|
||||||
| `/api/providers/{provider}` | `GET`, `PUT` | Get or update provider |
|
| `/api/providers/{provider}` | `GET`, `PUT` | Get or update provider (1) |
|
||||||
| `/api/providers/{provider}/backends` | `GET` | List backends |
|
| `/api/providers/{provider}/backends` | `GET` | List backends |
|
||||||
| `/api/providers/{provider}/backends/{backend}` | `GET` | Get backend |
|
| `/api/providers/{provider}/backends/{backend}` | `GET` | Get backend |
|
||||||
| `/api/providers/{provider}/backends/{backend}/servers` | `GET` | List servers in backend |
|
| `/api/providers/{provider}/backends/{backend}/servers` | `GET` | List servers in backend |
|
||||||
|
@ -52,6 +56,8 @@
|
||||||
| `/api/providers/{provider}/frontends/{frontend}/routes` | `GET` | List routes in a frontend |
|
| `/api/providers/{provider}/frontends/{frontend}/routes` | `GET` | List routes in a frontend |
|
||||||
| `/api/providers/{provider}/frontends/{frontend}/routes/{route}` | `GET` | Get a route in a frontend |
|
| `/api/providers/{provider}/frontends/{frontend}/routes/{route}` | `GET` | Get a route in a frontend |
|
||||||
|
|
||||||
|
<1> See [Rest](/configuration/backends/rest/#api) for more information.
|
||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
For compatibility reason, when you activate the rest provider, you can use `web` or `rest` as `provider` value.
|
For compatibility reason, when you activate the rest provider, you can use `web` or `rest` as `provider` value.
|
||||||
But be careful, in the configuration for all providers the key is still `web`.
|
But be careful, in the configuration for all providers the key is still `web`.
|
||||||
|
@ -185,6 +191,7 @@ curl -s "http://localhost:8080/health" | jq .
|
||||||
## Metrics
|
## Metrics
|
||||||
|
|
||||||
You can enable Traefik to export internal metrics to different monitoring systems.
|
You can enable Traefik to export internal metrics to different monitoring systems.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[api]
|
[api]
|
||||||
# ...
|
# ...
|
||||||
|
|
|
@ -36,7 +36,6 @@ address = ":8080"
|
||||||
#
|
#
|
||||||
readOnly = true
|
readOnly = true
|
||||||
|
|
||||||
|
|
||||||
# Set the root path for webui and API
|
# Set the root path for webui and API
|
||||||
#
|
#
|
||||||
# Deprecated
|
# Deprecated
|
||||||
|
@ -55,13 +54,13 @@ readOnly = true
|
||||||
### Authentication
|
### Authentication
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
The `/ping` path of the api is excluded from authentication (since 1.4).
|
The `/ping` path of the API is excluded from authentication (since 1.4).
|
||||||
|
|
||||||
#### Basic Authentication
|
#### Basic Authentication
|
||||||
|
|
||||||
Passwords can be encoded in MD5, SHA1 and BCrypt: you can use `htpasswd` to generate those ones.
|
Passwords can be encoded in MD5, SHA1 and BCrypt: you can use `htpasswd` to generate those ones.
|
||||||
|
|
||||||
Users can be specified directly in the toml file, or indirectly by referencing an external file;
|
Users can be specified directly in the TOML file, or indirectly by referencing an external file;
|
||||||
if both are provided, the two are merged, with external file contents having precedence.
|
if both are provided, the two are merged, with external file contents having precedence.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
@ -80,7 +79,7 @@ usersFile = "/path/to/.htpasswd"
|
||||||
|
|
||||||
You can use `htdigest` to generate those ones.
|
You can use `htdigest` to generate those ones.
|
||||||
|
|
||||||
Users can be specified directly in the toml file, or indirectly by referencing an external file;
|
Users can be specified directly in the TOML file, or indirectly by referencing an external file;
|
||||||
if both are provided, the two are merged, with external file contents having precedence
|
if both are provided, the two are merged, with external file contents having precedence
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
@ -98,7 +97,7 @@ usersFile = "/path/to/.htdigest"
|
||||||
|
|
||||||
## Metrics
|
## Metrics
|
||||||
|
|
||||||
You can enable Traefik to export internal metrics to different monitoring systems.
|
You can enable Træfik to export internal metrics to different monitoring systems.
|
||||||
|
|
||||||
### Prometheus
|
### Prometheus
|
||||||
|
|
||||||
|
@ -114,7 +113,7 @@ You can enable Traefik to export internal metrics to different monitoring system
|
||||||
# Optional
|
# Optional
|
||||||
# Default: [0.1, 0.3, 1.2, 5]
|
# Default: [0.1, 0.3, 1.2, 5]
|
||||||
buckets=[0.1,0.3,1.2,5.0]
|
buckets=[0.1,0.3,1.2,5.0]
|
||||||
|
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -221,7 +220,7 @@ recentErrors = 10
|
||||||
|-----------------------------------------------------------------|:-------------:|----------------------------------------------------------------------------------------------------|
|
|-----------------------------------------------------------------|:-------------:|----------------------------------------------------------------------------------------------------|
|
||||||
| `/` | `GET` | Provides a simple HTML frontend of Træfik |
|
| `/` | `GET` | Provides a simple HTML frontend of Træfik |
|
||||||
| `/ping` | `GET`, `HEAD` | A simple endpoint to check for Træfik process liveness. Return a code `200` with the content: `OK` |
|
| `/ping` | `GET`, `HEAD` | A simple endpoint to check for Træfik process liveness. Return a code `200` with the content: `OK` |
|
||||||
| `/health` | `GET` | json health metrics |
|
| `/health` | `GET` | JSON health metrics |
|
||||||
| `/api` | `GET` | Configuration for all providers |
|
| `/api` | `GET` | Configuration for all providers |
|
||||||
| `/api/providers` | `GET` | Providers |
|
| `/api/providers` | `GET` | Providers |
|
||||||
| `/api/providers/{provider}` | `GET`, `PUT` | Get or update provider |
|
| `/api/providers/{provider}` | `GET`, `PUT` | Get or update provider |
|
||||||
|
@ -244,7 +243,7 @@ curl -sv "http://localhost:8080/ping"
|
||||||
```
|
```
|
||||||
```shell
|
```shell
|
||||||
* Trying ::1...
|
* Trying ::1...
|
||||||
* Connected to localhost (::1) port 8080 (#0)
|
* Connected to localhost (::1) port 8080 (\#0)
|
||||||
> GET /ping HTTP/1.1
|
> GET /ping HTTP/1.1
|
||||||
> Host: localhost:8080
|
> Host: localhost:8080
|
||||||
> User-Agent: curl/7.43.0
|
> User-Agent: curl/7.43.0
|
||||||
|
@ -255,7 +254,7 @@ curl -sv "http://localhost:8080/ping"
|
||||||
< Content-Length: 2
|
< Content-Length: 2
|
||||||
< Content-Type: text/plain; charset=utf-8
|
< Content-Type: text/plain; charset=utf-8
|
||||||
<
|
<
|
||||||
* Connection #0 to host localhost left intact
|
* Connection \#0 to host localhost left intact
|
||||||
OK
|
OK
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -309,7 +308,7 @@ curl -s "http://localhost:8080/health" | jq .
|
||||||
"status": "Internal Server Error",
|
"status": "Internal Server Error",
|
||||||
// request HTTP method
|
// request HTTP method
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
// request hostname
|
// request host name
|
||||||
"host": "localhost",
|
"host": "localhost",
|
||||||
// request path
|
// request path
|
||||||
"path": "/path",
|
"path": "/path",
|
||||||
|
@ -385,23 +384,28 @@ curl -s "http://localhost:8080/api" | jq .
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Path
|
### Deprecation compatibility
|
||||||
|
|
||||||
As web is deprecated, you can handle the `Path` option like this
|
#### Path
|
||||||
|
|
||||||
|
As the web provider is deprecated, you can handle the `Path` option like this:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[entrypoints.http]
|
defaultEntryPoints = ["http"]
|
||||||
address=":80"
|
|
||||||
|
|
||||||
[entrypoints.dashboard]
|
[entryPoints]
|
||||||
address=":8080"
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
|
||||||
[entrypoints.api]
|
[entryPoints.dashboard]
|
||||||
address=":8081"
|
address = ":8080"
|
||||||
|
|
||||||
#Activate API and Dashboard
|
[entryPoints.api]
|
||||||
|
address = ":8081"
|
||||||
|
|
||||||
|
# Activate API and Dashboard
|
||||||
[api]
|
[api]
|
||||||
entrypoint="api"
|
entryPoint = "api"
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
[backends]
|
[backends]
|
||||||
|
@ -411,8 +415,67 @@ entrypoint="api"
|
||||||
|
|
||||||
[frontends]
|
[frontends]
|
||||||
[frontends.frontend1]
|
[frontends.frontend1]
|
||||||
entrypoints=["dashboard"]
|
entryPoints = ["dashboard"]
|
||||||
backend = "backend1"
|
backend = "backend1"
|
||||||
[frontends.frontend1.routes.test_1]
|
[frontends.frontend1.routes.test_1]
|
||||||
rule = "PathPrefixStrip:/yourprefix;PathPrefix:/yourprefix"
|
rule = "PathPrefixStrip:/yourprefix;PathPrefix:/yourprefix"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Address
|
||||||
|
|
||||||
|
As the web provider is deprecated, you can handle the `Address` option like this:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
defaultEntryPoints = ["http"]
|
||||||
|
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
|
||||||
|
[entryPoints.ping]
|
||||||
|
address = ":8082"
|
||||||
|
|
||||||
|
[entryPoints.api]
|
||||||
|
address = ":8083"
|
||||||
|
|
||||||
|
[ping]
|
||||||
|
entryPoint = "ping"
|
||||||
|
|
||||||
|
[api]
|
||||||
|
entryPoint = "api"
|
||||||
|
```
|
||||||
|
|
||||||
|
In the above example, you would access a regular path, administration panel, and health-check as follows:
|
||||||
|
|
||||||
|
* Regular path: `http://hostname:80/foo`
|
||||||
|
* Admin Panel: `http://hostname:8083/`
|
||||||
|
* Ping URL: `http://hostname:8082/ping`
|
||||||
|
|
||||||
|
In the above example, it is _very_ important to create a named dedicated entry point, and do **not** include it in `defaultEntryPoints`.
|
||||||
|
Otherwise, you are likely to expose _all_ services via that entry point.
|
||||||
|
|
||||||
|
#### Authentication
|
||||||
|
|
||||||
|
As the web provider is deprecated, you can handle the `auth` option like this:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
defaultEntryPoints = ["http"]
|
||||||
|
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
|
||||||
|
[entryPoints.api]
|
||||||
|
address=":8080"
|
||||||
|
[entryPoints.api.auth]
|
||||||
|
[entryPoints.api.auth.basic]
|
||||||
|
users = [
|
||||||
|
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||||
|
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[api]
|
||||||
|
entrypoint="api"
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information, see [entry points](/configuration/entrypoints/) .
|
||||||
|
|
|
@ -13,7 +13,10 @@
|
||||||
|
|
||||||
[entryPoints.http.tls]
|
[entryPoints.http.tls]
|
||||||
minVersion = "VersionTLS12"
|
minVersion = "VersionTLS12"
|
||||||
cipherSuites = ["TLS_RSA_WITH_AES_256_GCM_SHA384"]
|
cipherSuites = [
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||||
|
"TLS_RSA_WITH_AES_256_GCM_SHA384"
|
||||||
|
]
|
||||||
[[entryPoints.http.tls.certificates]]
|
[[entryPoints.http.tls.certificates]]
|
||||||
certFile = "path/to/my.cert"
|
certFile = "path/to/my.cert"
|
||||||
keyFile = "path/to/my.key"
|
keyFile = "path/to/my.key"
|
||||||
|
@ -246,9 +249,9 @@ In the example below both `snitest.com` and `snitest.org` will require client ce
|
||||||
|
|
||||||
### Basic Authentication
|
### Basic Authentication
|
||||||
|
|
||||||
Passwords can be encoded in MD5, SHA1 and BCrypt: you can use `htpasswd` to generate those ones.
|
Passwords can be encoded in MD5, SHA1 and BCrypt: you can use `htpasswd` to generate them.
|
||||||
|
|
||||||
Users can be specified directly in the toml file, or indirectly by referencing an external file;
|
Users can be specified directly in the TOML file, or indirectly by referencing an external file;
|
||||||
if both are provided, the two are merged, with external file contents having precedence.
|
if both are provided, the two are merged, with external file contents having precedence.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
@ -263,9 +266,9 @@ Users can be specified directly in the toml file, or indirectly by referencing a
|
||||||
|
|
||||||
### Digest Authentication
|
### Digest Authentication
|
||||||
|
|
||||||
You can use `htdigest` to generate those ones.
|
You can use `htdigest` to generate them.
|
||||||
|
|
||||||
Users can be specified directly in the toml file, or indirectly by referencing an external file;
|
Users can be specified directly in the TOML file, or indirectly by referencing an external file;
|
||||||
if both are provided, the two are merged, with external file contents having precedence
|
if both are provided, the two are merged, with external file contents having precedence
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
@ -283,7 +286,7 @@ Users can be specified directly in the toml file, or indirectly by referencing a
|
||||||
This configuration will first forward the request to `http://authserver.com/auth`.
|
This configuration will first forward the request to `http://authserver.com/auth`.
|
||||||
|
|
||||||
If the response code is 2XX, access is granted and the original request is performed.
|
If the response code is 2XX, access is granted and the original request is performed.
|
||||||
Otherwise, the response from the auth server is returned.
|
Otherwise, the response from the authentication server is returned.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
|
@ -320,7 +323,10 @@ To specify an https entry point with a minimum TLS version, and specifying an ar
|
||||||
address = ":443"
|
address = ":443"
|
||||||
[entryPoints.https.tls]
|
[entryPoints.https.tls]
|
||||||
minVersion = "VersionTLS12"
|
minVersion = "VersionTLS12"
|
||||||
cipherSuites = ["TLS_RSA_WITH_AES_256_GCM_SHA384"]
|
cipherSuites = [
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||||
|
"TLS_RSA_WITH_AES_256_GCM_SHA384"
|
||||||
|
]
|
||||||
[[entryPoints.https.tls.certificates]]
|
[[entryPoints.https.tls.certificates]]
|
||||||
certFile = "integration/fixtures/https/snitest.com.cert"
|
certFile = "integration/fixtures/https/snitest.com.cert"
|
||||||
keyFile = "integration/fixtures/https/snitest.com.key"
|
keyFile = "integration/fixtures/https/snitest.com.key"
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# Ping Definition
|
# Ping Definition
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# Ping definition
|
# Ping definition
|
||||||
[ping]
|
[ping]
|
||||||
|
@ -19,7 +21,7 @@
|
||||||
!!! warning
|
!!! warning
|
||||||
Even if you have authentication configured on entry point, the `/ping` path of the api is excluded from authentication.
|
Even if you have authentication configured on entry point, the `/ping` path of the api is excluded from authentication.
|
||||||
|
|
||||||
### Example
|
## Example
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
curl -sv "http://localhost:8080/ping"
|
curl -sv "http://localhost:8080/ping"
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
[![Twitter](https://img.shields.io/twitter/follow/traefikproxy.svg?style=social)](https://twitter.com/intent/follow?screen_name=traefikproxy)
|
[![Twitter](https://img.shields.io/twitter/follow/traefikproxy.svg?style=social)](https://twitter.com/intent/follow?screen_name=traefikproxy)
|
||||||
|
|
||||||
|
|
||||||
Træfik (pronounced like [traffic](https://speak-ipa.bearbin.net/speak.cgi?speak=%CB%88tr%C3%A6f%C9%AAk)) is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
Træfik (pronounced like _traffic_) is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||||
It supports several backends ([Docker](https://www.docker.com/), [Swarm mode](https://docs.docker.com/engine/swarm/), [Kubernetes](https://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Rancher](https://rancher.com), [Amazon ECS](https://aws.amazon.com/ecs), and a lot more) to manage its configuration automatically and dynamically.
|
It supports several backends ([Docker](https://www.docker.com/), [Swarm mode](https://docs.docker.com/engine/swarm/), [Kubernetes](https://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Rancher](https://rancher.com), [Amazon ECS](https://aws.amazon.com/ecs), and a lot more) to manage its configuration automatically and dynamically.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
|
@ -91,7 +91,7 @@ entryPoint = "https"
|
||||||
|
|
||||||
This configuration allows generating Let's Encrypt certificates (thanks to `HTTP-01` challenge) for the four domains `local[1-4].com` with described SANs.
|
This configuration allows generating Let's Encrypt certificates (thanks to `HTTP-01` challenge) for the four domains `local[1-4].com` with described SANs.
|
||||||
|
|
||||||
Traefik generates these certificates when it starts and it needs to be restart if new domains are added.
|
Træfik generates these certificates when it starts and it needs to be restart if new domains are added.
|
||||||
|
|
||||||
### OnHostRule option (with HTTP challenge)
|
### OnHostRule option (with HTTP challenge)
|
||||||
|
|
||||||
|
@ -126,9 +126,9 @@ entryPoint = "https"
|
||||||
|
|
||||||
This configuration allows generating Let's Encrypt certificates (thanks to `HTTP-01` challenge) for the four domains `local[1-4].com`.
|
This configuration allows generating Let's Encrypt certificates (thanks to `HTTP-01` challenge) for the four domains `local[1-4].com`.
|
||||||
|
|
||||||
Traefik generates these certificates when it starts.
|
Træfik generates these certificates when it starts.
|
||||||
|
|
||||||
If a backend is added with a `onHost` rule, Traefik will automatically generate the Let's Encrypt certificate for the new domain.
|
If a backend is added with a `onHost` rule, Træfik will automatically generate the Let's Encrypt certificate for the new domain (for frontends wired on the `acme.entryPoint`).
|
||||||
|
|
||||||
### OnDemand option (with HTTP challenge)
|
### OnDemand option (with HTTP challenge)
|
||||||
|
|
||||||
|
@ -152,11 +152,10 @@ entryPoint = "https"
|
||||||
|
|
||||||
This configuration allows generating a Let's Encrypt certificate (thanks to `HTTP-01` challenge) during the first HTTPS request on a new domain.
|
This configuration allows generating a Let's Encrypt certificate (thanks to `HTTP-01` challenge) during the first HTTPS request on a new domain.
|
||||||
|
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
This option simplifies the configuration but :
|
This option simplifies the configuration but :
|
||||||
|
|
||||||
* TLS handshakes will be slow when requesting a hostname certificate for the first time, this can leads to DDoS attacks.
|
* TLS handshakes will be slow when requesting a host name certificate for the first time, this can leads to DDoS attacks.
|
||||||
* Let's Encrypt have rate limiting: https://letsencrypt.org/docs/rate-limits
|
* Let's Encrypt have rate limiting: https://letsencrypt.org/docs/rate-limits
|
||||||
|
|
||||||
That's why, it's better to use the `onHostRule` option if possible.
|
That's why, it's better to use the `onHostRule` option if possible.
|
||||||
|
@ -191,7 +190,7 @@ entryPoint = "https"
|
||||||
```
|
```
|
||||||
|
|
||||||
DNS challenge needs environment variables to be executed.
|
DNS challenge needs environment variables to be executed.
|
||||||
This variables have to be set on the machine/container which host Traefik.
|
These variables have to be set on the machine/container which host Træfik.
|
||||||
|
|
||||||
These variables are described [in this section](/configuration/acme/#provider).
|
These variables are described [in this section](/configuration/acme/#provider).
|
||||||
|
|
||||||
|
@ -218,7 +217,7 @@ entryPoint = "https"
|
||||||
entryPoint = "http"
|
entryPoint = "http"
|
||||||
```
|
```
|
||||||
|
|
||||||
Traefik will only try to generate a Let's encrypt certificate (thanks to `HTTP-01` challenge) if the domain cannot be checked by the provided certificates.
|
Træfik will only try to generate a Let's encrypt certificate (thanks to `HTTP-01` challenge) if the domain cannot be checked by the provided certificates.
|
||||||
|
|
||||||
### Cluster mode
|
### Cluster mode
|
||||||
|
|
||||||
|
@ -292,14 +291,14 @@ The `consul` provider contains the configuration.
|
||||||
rule = "Path:/test"
|
rule = "Path:/test"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Enable Basic authentication in an entrypoint
|
## Enable Basic authentication in an entry point
|
||||||
|
|
||||||
With two user/pass:
|
With two user/pass:
|
||||||
|
|
||||||
- `test`:`test`
|
- `test`:`test`
|
||||||
- `test2`:`test2`
|
- `test2`:`test2`
|
||||||
|
|
||||||
Passwords are encoded in MD5: you can use htpasswd to generate those ones.
|
Passwords are encoded in MD5: you can use `htpasswd` to generate them.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
defaultEntryPoints = ["http"]
|
defaultEntryPoints = ["http"]
|
||||||
|
@ -337,7 +336,7 @@ providersThrottleDuration = "5s"
|
||||||
idleTimeout = "360s"
|
idleTimeout = "360s"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Securing Ping Health Check
|
## Ping Health Check
|
||||||
|
|
||||||
The `/ping` health-check URL is enabled with the command-line `--ping` or config file option `[ping]`.
|
The `/ping` health-check URL is enabled with the command-line `--ping` or config file option `[ping]`.
|
||||||
Thus, if you have a regular path for `/foo` and an entrypoint on `:80`, you would access them as follows:
|
Thus, if you have a regular path for `/foo` and an entrypoint on `:80`, you would access them as follows:
|
||||||
|
@ -346,40 +345,36 @@ Thus, if you have a regular path for `/foo` and an entrypoint on `:80`, you woul
|
||||||
* Admin panel: `http://hostname:8080/`
|
* Admin panel: `http://hostname:8080/`
|
||||||
* Ping URL: `http://hostname:8080/ping`
|
* Ping URL: `http://hostname:8080/ping`
|
||||||
|
|
||||||
However, for security reasons, you may want to be able to expose the `/ping` health-check URL to outside health-checkers, e.g. an Internet service or cloud load-balancer, _without_ exposing your admin panel's port.
|
However, for security reasons, you may want to be able to expose the `/ping` health-check URL to outside health-checkers, e.g. an Internet service or cloud load-balancer, _without_ exposing your administration panel's port.
|
||||||
In many environments, the security staff may not _allow_ you to expose it.
|
In many environments, the security staff may not _allow_ you to expose it.
|
||||||
|
|
||||||
You have two options:
|
You have two options:
|
||||||
|
|
||||||
* Enable `/ping` on a regular entrypoint
|
* Enable `/ping` on a regular entry point
|
||||||
* Enable `/ping` on a dedicated port
|
* Enable `/ping` on a dedicated port
|
||||||
|
|
||||||
### Enable ping health check on a regular entrypoint
|
### Enable ping health check on a regular entry point
|
||||||
|
|
||||||
To proxy `/ping` from a regular entrypoint to the admin one without exposing the panel, do the following:
|
To proxy `/ping` from a regular entry point to the administration one without exposing the panel, do the following:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[backends]
|
defaultEntryPoints = ["http"]
|
||||||
[backends.traefik]
|
|
||||||
[backends.traefik.servers.server1]
|
[entryPoints]
|
||||||
url = "http://localhost:8080"
|
[entryPoints.http]
|
||||||
weight = 10
|
address = ":80"
|
||||||
|
|
||||||
|
[ping]
|
||||||
|
entryPoint = "http"
|
||||||
|
|
||||||
[frontends]
|
|
||||||
[frontends.traefikadmin]
|
|
||||||
backend = "traefik"
|
|
||||||
[frontends.traefikadmin.routes.ping]
|
|
||||||
rule = "Path:/ping"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The above creates a new backend called `traefik`, listening on `http://localhost:8080`, i.e. the local admin port.
|
The above link `ping` on the `http` entry point and then expose it on port `80`
|
||||||
We only expose the admin panel via the `frontend` named `traefikadmin`, and only expose the `/ping` Path.
|
|
||||||
Be careful with the `traefikadmin` frontend. If you do _not_ specify a `Path:` rule, you would expose the entire dashboard.
|
|
||||||
|
|
||||||
### Enable ping health check on dedicated port
|
### Enable ping health check on dedicated port
|
||||||
|
|
||||||
If you do not want to or cannot expose the health-check on a regular entrypoint - e.g. your security rules do not allow it, or you have a conflicting path - then you can enable health-check on its own entrypoint.
|
If you do not want to or cannot expose the health-check on a regular entry point - e.g. your security rules do not allow it, or you have a conflicting path - then you can enable health-check on its own entry point.
|
||||||
Use the following config:
|
Use the following configuration:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
defaultEntryPoints = ["http"]
|
defaultEntryPoints = ["http"]
|
||||||
|
@ -390,32 +385,18 @@ defaultEntryPoints = ["http"]
|
||||||
[entryPoints.ping]
|
[entryPoints.ping]
|
||||||
address = ":8082"
|
address = ":8082"
|
||||||
|
|
||||||
[backends]
|
[ping]
|
||||||
[backends.traefik]
|
entryPoint = "ping"
|
||||||
[backends.traefik.servers.server1]
|
|
||||||
url = "http://localhost:8080"
|
|
||||||
weight = 10
|
|
||||||
|
|
||||||
[frontends]
|
|
||||||
[frontends.traefikadmin]
|
|
||||||
backend = "traefik"
|
|
||||||
entrypoints = ["ping"]
|
|
||||||
[frontends.traefikadmin.routes.ping]
|
|
||||||
rule = "Path:/ping"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The above is similar to the previous example, but instead of enabling `/ping` on the _default_ entrypoint, we enable it on a _dedicated_ entrypoint.
|
The above is similar to the previous example, but instead of enabling `/ping` on the _default_ entry point, we enable it on a _dedicated_ entry point.
|
||||||
|
|
||||||
In the above example, you would access a regular path, admin panel and health-check as follows:
|
In the above example, you would access a regular path and health-check as follows:
|
||||||
|
|
||||||
* Regular path: `http://hostname:80/foo`
|
* Regular path: `http://hostname:80/foo`
|
||||||
* Admin panel: `http://hostname:8080/`
|
|
||||||
* Ping URL: `http://hostname:8082/ping`
|
* Ping URL: `http://hostname:8082/ping`
|
||||||
|
|
||||||
Note the dedicated port `:8082` for `/ping`.
|
Note the dedicated port `:8082` for `/ping`.
|
||||||
|
|
||||||
In the above example, it is _very_ important to create a named dedicated entrypoint, and do **not** include it in `defaultEntryPoints`.
|
In the above example, it is _very_ important to create a named dedicated entry point, and do **not** include it in `defaultEntryPoints`.
|
||||||
Otherwise, you are likely to expose _all_ services via that entrypoint.
|
Otherwise, you are likely to expose _all_ services via this entry point.
|
||||||
|
|
||||||
In the above example, we have two entrypoints, `http` and `ping`, but we only included `http` in `defaultEntryPoints`, while explicitly tying `frontend.traefikadmin` to the `ping` entrypoint.
|
|
||||||
This ensures that all the "normal" frontends will be exposed via entrypoint `http` and _not_ via entrypoint `ping`.
|
|
||||||
|
|
|
@ -358,3 +358,31 @@ func (s *SimpleSuite) TestMetricsPrometheusDefaultEntrypoint(c *check.C) {
|
||||||
err = try.GetRequest("http://127.0.0.1:8080/metrics", 1*time.Second, try.StatusCodeIs(http.StatusOK))
|
err = try.GetRequest("http://127.0.0.1:8080/metrics", 1*time.Second, try.StatusCodeIs(http.StatusOK))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SimpleSuite) TestMultipleProviderSameBackendName(c *check.C) {
|
||||||
|
|
||||||
|
s.createComposeProject(c, "base")
|
||||||
|
s.composeProject.Start(c)
|
||||||
|
ipWhoami01 := s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress
|
||||||
|
ipWhoami02 := s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress
|
||||||
|
file := s.adaptFile(c, "fixtures/multiple_provider.toml", struct{ IP string }{
|
||||||
|
IP: ipWhoami02,
|
||||||
|
})
|
||||||
|
defer os.Remove(file)
|
||||||
|
cmd, output := s.traefikCmd(withConfigFile(file))
|
||||||
|
defer output(c)
|
||||||
|
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("PathPrefix"))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.BodyContains(ipWhoami01))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8000/file", 1*time.Second, try.BodyContains(ipWhoami02))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -91,11 +91,11 @@ func (s *ConstraintSuite) TestMatchConstraintGlobal(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")
|
whoami := s.composeProject.Container(c, "whoami")
|
||||||
|
|
||||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api"})
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api"})
|
||||||
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", whoami.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
@ -117,11 +117,11 @@ func (s *ConstraintSuite) TestDoesNotMatchConstraintGlobal(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")
|
whoami := s.composeProject.Container(c, "whoami")
|
||||||
|
|
||||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{})
|
||||||
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", whoami.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
@ -143,11 +143,11 @@ func (s *ConstraintSuite) TestMatchConstraintProvider(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")
|
whoami := s.composeProject.Container(c, "whoami")
|
||||||
|
|
||||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api"})
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api"})
|
||||||
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", whoami.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
@ -169,11 +169,11 @@ func (s *ConstraintSuite) TestDoesNotMatchConstraintProvider(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")
|
whoami := s.composeProject.Container(c, "whoami")
|
||||||
|
|
||||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{})
|
||||||
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", whoami.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
@ -196,11 +196,11 @@ func (s *ConstraintSuite) TestMatchMultipleConstraint(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")
|
whoami := s.composeProject.Container(c, "whoami")
|
||||||
|
|
||||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api", "traefik.tags=eu-1"})
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api", "traefik.tags=eu-1"})
|
||||||
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", whoami.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
@ -223,11 +223,11 @@ func (s *ConstraintSuite) TestDoesNotMatchMultipleConstraint(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")
|
whoami := s.composeProject.Container(c, "whoami")
|
||||||
|
|
||||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api", "traefik.tags=us-1"})
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api", "traefik.tags=us-1"})
|
||||||
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", whoami.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
|
@ -145,9 +145,9 @@ func (s *ConsulCatalogSuite) TestSingleService(c *check.C) {
|
||||||
err = try.GetRequest("http://127.0.0.1:8000/", 2*time.Second, try.StatusCodeIs(http.StatusNotFound))
|
err = try.GetRequest("http://127.0.0.1:8000/", 2*time.Second, try.StatusCodeIs(http.StatusNotFound))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
nginx := s.composeProject.Container(c, "nginx1")
|
whoami := s.composeProject.Container(c, "whoami1")
|
||||||
|
|
||||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{})
|
||||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||||
|
@ -157,7 +157,7 @@ func (s *ConsulCatalogSuite) TestSingleService(c *check.C) {
|
||||||
err = try.Request(req, 10*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)
|
||||||
|
|
||||||
s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
||||||
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody())
|
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody())
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
@ -175,11 +175,11 @@ 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, "nginx1")
|
whoami := s.composeProject.Container(c, "whoami1")
|
||||||
|
|
||||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{})
|
||||||
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", whoami.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
@ -201,16 +201,16 @@ 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, "nginx1")
|
whoami := s.composeProject.Container(c, "whoami1")
|
||||||
nginx2 := s.composeProject.Container(c, "nginx2")
|
whoami2 := s.composeProject.Container(c, "whoami2")
|
||||||
|
|
||||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{})
|
||||||
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", whoami.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
err = s.registerService("test", nginx2.NetworkSettings.IPAddress, 80, []string{"traefik.enable=true"})
|
err = s.registerService("test", whoami2.NetworkSettings.IPAddress, 80, []string{"traefik.enable=true"})
|
||||||
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", whoami2.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
@ -232,16 +232,16 @@ 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, "nginx1")
|
whoami := s.composeProject.Container(c, "whoami1")
|
||||||
nginx2 := s.composeProject.Container(c, "nginx2")
|
whoami2 := s.composeProject.Container(c, "whoami2")
|
||||||
|
|
||||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"name=nginx1"})
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1"})
|
||||||
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", whoami.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
err = s.registerService("test", nginx2.NetworkSettings.IPAddress, 80, []string{"name=nginx2"})
|
err = s.registerService("test", whoami2.NetworkSettings.IPAddress, 80, []string{"name=whoami2"})
|
||||||
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", whoami2.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
@ -251,7 +251,7 @@ func (s *ConsulCatalogSuite) TestExposedByDefaultTrueSimpleServiceMultipleNode(c
|
||||||
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,
|
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 60*time.Second,
|
||||||
try.BodyContains(nginx.NetworkSettings.IPAddress, nginx2.NetworkSettings.IPAddress))
|
try.BodyContains(whoami.NetworkSettings.IPAddress, whoami2.NetworkSettings.IPAddress))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,16 +267,16 @@ func (s *ConsulCatalogSuite) TestRefreshConfigWithMultipleNodeWithoutHealthCheck
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
defer cmd.Process.Kill()
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
nginx := s.composeProject.Container(c, "nginx1")
|
whoami := s.composeProject.Container(c, "whoami1")
|
||||||
nginx2 := s.composeProject.Container(c, "nginx2")
|
whoami2 := s.composeProject.Container(c, "whoami2")
|
||||||
|
|
||||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"name=nginx1"})
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1"})
|
||||||
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", whoami.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
err = s.registerAgentService("test", nginx.NetworkSettings.IPAddress, 80, []string{"name=nginx1"})
|
err = s.registerAgentService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1"})
|
||||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering agent service"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering agent service"))
|
||||||
defer s.deregisterAgentService(nginx.NetworkSettings.IPAddress)
|
defer s.deregisterAgentService(whoami.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
@ -286,28 +286,28 @@ func (s *ConsulCatalogSuite) TestRefreshConfigWithMultipleNodeWithoutHealthCheck
|
||||||
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,
|
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 60*time.Second,
|
||||||
try.BodyContains(nginx.NetworkSettings.IPAddress))
|
try.BodyContains(whoami.NetworkSettings.IPAddress))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
err = s.registerService("test", nginx2.NetworkSettings.IPAddress, 80, []string{"name=nginx2"})
|
err = s.registerService("test", whoami2.NetworkSettings.IPAddress, 80, []string{"name=whoami2"})
|
||||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
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,
|
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 60*time.Second,
|
||||||
try.BodyContains(nginx.NetworkSettings.IPAddress, nginx2.NetworkSettings.IPAddress))
|
try.BodyContains(whoami.NetworkSettings.IPAddress, whoami2.NetworkSettings.IPAddress))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
s.deregisterService("test", nginx2.NetworkSettings.IPAddress)
|
s.deregisterService("test", whoami2.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 60*time.Second,
|
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 60*time.Second,
|
||||||
try.BodyContains(nginx.NetworkSettings.IPAddress))
|
try.BodyContains(whoami.NetworkSettings.IPAddress))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
err = s.registerService("test", nginx2.NetworkSettings.IPAddress, 80, []string{"name=nginx2"})
|
err = s.registerService("test", whoami2.NetworkSettings.IPAddress, 80, []string{"name=whoami2"})
|
||||||
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", whoami2.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 60*time.Second,
|
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 60*time.Second,
|
||||||
try.BodyContains(nginx.NetworkSettings.IPAddress, nginx2.NetworkSettings.IPAddress))
|
try.BodyContains(whoami.NetworkSettings.IPAddress, whoami2.NetworkSettings.IPAddress))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,13 +323,13 @@ func (s *ConsulCatalogSuite) TestBasicAuthSimpleService(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, "nginx1")
|
whoami := s.composeProject.Container(c, "whoami1")
|
||||||
|
|
||||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{
|
||||||
"traefik.frontend.auth.basic=test:$2a$06$O5NksJPAcgrC9MuANkSoE.Xe9DSg7KcLLFYNr1Lj6hPcMmvgwxhme,test2:$2y$10$xP1SZ70QbZ4K2bTGKJOhpujkpcLxQcB3kEPF6XAV19IdcqsZTyDEe",
|
"traefik.frontend.auth.basic=test:$2a$06$O5NksJPAcgrC9MuANkSoE.Xe9DSg7KcLLFYNr1Lj6hPcMmvgwxhme,test2:$2y$10$xP1SZ70QbZ4K2bTGKJOhpujkpcLxQcB3kEPF6XAV19IdcqsZTyDEe",
|
||||||
})
|
})
|
||||||
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", whoami.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
@ -360,17 +360,17 @@ func (s *ConsulCatalogSuite) TestRefreshConfigTagChange(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, "nginx1")
|
whoami := s.composeProject.Container(c, "whoami1")
|
||||||
|
|
||||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"name=nginx1", "traefik.enable=false", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1", "traefik.enable=false", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
|
||||||
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", whoami.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 5*time.Second,
|
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 5*time.Second,
|
||||||
try.BodyContains(nginx.NetworkSettings.IPAddress))
|
try.BodyContains(whoami.NetworkSettings.IPAddress))
|
||||||
c.Assert(err, checker.NotNil)
|
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"})
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1", "traefik.enable=true", "traefik.backend.circuitbreaker=ResponseCodeRatio(500, 600, 0, 600) > 0.5"})
|
||||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||||
|
@ -381,7 +381,7 @@ func (s *ConsulCatalogSuite) TestRefreshConfigTagChange(c *check.C) {
|
||||||
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,
|
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 60*time.Second,
|
||||||
try.BodyContains(nginx.NetworkSettings.IPAddress))
|
try.BodyContains(whoami.NetworkSettings.IPAddress))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -402,19 +402,19 @@ func (s *ConsulCatalogSuite) TestCircuitBreaker(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, "nginx1")
|
whoami := s.composeProject.Container(c, "whoami1")
|
||||||
nginx2 := s.composeProject.Container(c, "nginx2")
|
whoami2 := s.composeProject.Container(c, "whoami2")
|
||||||
nginx3 := s.composeProject.Container(c, "nginx3")
|
whoami3 := s.composeProject.Container(c, "whoami3")
|
||||||
|
|
||||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"name=nginx1", "traefik.enable=true", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1", "traefik.enable=true", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
|
||||||
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", whoami.NetworkSettings.IPAddress)
|
||||||
err = s.registerService("test", nginx2.NetworkSettings.IPAddress, 42, []string{"name=nginx2", "traefik.enable=true", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
|
err = s.registerService("test", whoami2.NetworkSettings.IPAddress, 42, []string{"name=whoami2", "traefik.enable=true", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
|
||||||
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", whoami2.NetworkSettings.IPAddress)
|
||||||
err = s.registerService("test", nginx3.NetworkSettings.IPAddress, 42, []string{"name=nginx3", "traefik.enable=true", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
|
err = s.registerService("test", whoami3.NetworkSettings.IPAddress, 42, []string{"name=whoami3", "traefik.enable=true", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
|
||||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
defer s.deregisterService("test", nginx3.NetworkSettings.IPAddress)
|
defer s.deregisterService("test", whoami3.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
@ -437,9 +437,9 @@ func (s *ConsulCatalogSuite) TestRefreshConfigPortChange(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, "nginx1")
|
whoami := s.composeProject.Container(c, "whoami1")
|
||||||
|
|
||||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 81, []string{"name=nginx1", "traefik.enable=true"})
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 81, []string{"name=whoami1", "traefik.enable=true"})
|
||||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||||
|
@ -449,15 +449,15 @@ func (s *ConsulCatalogSuite) TestRefreshConfigPortChange(c *check.C) {
|
||||||
err = try.Request(req, 20*time.Second, try.StatusCodeIs(http.StatusBadGateway))
|
err = try.Request(req, 20*time.Second, try.StatusCodeIs(http.StatusBadGateway))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 5*time.Second, try.BodyContains(nginx.NetworkSettings.IPAddress))
|
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 5*time.Second, try.BodyContains(whoami.NetworkSettings.IPAddress))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"name=nginx1", "traefik.enable=true"})
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1", "traefik.enable=true"})
|
||||||
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", whoami.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 60*time.Second, try.BodyContains(nginx.NetworkSettings.IPAddress))
|
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 60*time.Second, try.BodyContains(whoami.NetworkSettings.IPAddress))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
err = try.Request(req, 20*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
err = try.Request(req, 20*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||||
|
@ -496,9 +496,9 @@ func (s *ConsulCatalogSuite) TestRetryWithConsulServer(c *check.C) {
|
||||||
s.composeProject.Scale(c, "consul", 1)
|
s.composeProject.Scale(c, "consul", 1)
|
||||||
s.waitToElectConsulLeader()
|
s.waitToElectConsulLeader()
|
||||||
|
|
||||||
nginx := s.composeProject.Container(c, "nginx1")
|
whoami := s.composeProject.Container(c, "whoami1")
|
||||||
// Register service
|
// Register service
|
||||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{})
|
||||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
|
||||||
// Provider consul catalog should be present
|
// Provider consul catalog should be present
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
"github.com/docker/docker/pkg/namesgenerator"
|
"github.com/docker/docker/pkg/namesgenerator"
|
||||||
"github.com/go-check/check"
|
"github.com/go-check/check"
|
||||||
d "github.com/libkermit/docker"
|
d "github.com/libkermit/docker"
|
||||||
docker "github.com/libkermit/docker-check"
|
"github.com/libkermit/docker-check"
|
||||||
checker "github.com/vdemeester/shakers"
|
checker "github.com/vdemeester/shakers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,8 +25,8 @@ var (
|
||||||
// Images to have or pull before the build in order to make it work
|
// Images to have or pull before the build in order to make it work
|
||||||
// FIXME handle this offline but loading them before build
|
// FIXME handle this offline but loading them before build
|
||||||
RequiredImages = map[string]string{
|
RequiredImages = map[string]string{
|
||||||
"swarm": "1.0.0",
|
"swarm": "1.0.0",
|
||||||
"nginx": "1",
|
"emilevauge/whoami": "latest",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
25
integration/fixtures/multiple_provider.toml
Normal file
25
integration/fixtures/multiple_provider.toml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
defaultEntryPoints = ["http"]
|
||||||
|
|
||||||
|
debug=true
|
||||||
|
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":8000"
|
||||||
|
|
||||||
|
[api]
|
||||||
|
|
||||||
|
[docker]
|
||||||
|
endpoint = "unix:///var/run/docker.sock"
|
||||||
|
watch = true
|
||||||
|
exposedbydefault = false
|
||||||
|
|
||||||
|
[file]
|
||||||
|
[frontends]
|
||||||
|
[frontends.frontend-1]
|
||||||
|
backend = "backend-test"
|
||||||
|
[frontends.frontend-1.routes.test_1]
|
||||||
|
rule = "PathPrefix:/file"
|
||||||
|
[backends]
|
||||||
|
[backends.backend-test]
|
||||||
|
[backends.backend-test.servers.website]
|
||||||
|
url = "http://{{ .IP }}"
|
|
@ -19,7 +19,7 @@ func (s *RateLimitSuite) SetUpSuite(c *check.C) {
|
||||||
s.createComposeProject(c, "ratelimit")
|
s.createComposeProject(c, "ratelimit")
|
||||||
s.composeProject.Start(c)
|
s.composeProject.Start(c)
|
||||||
|
|
||||||
s.ServerIP = s.composeProject.Container(c, "nginx1").NetworkSettings.IPAddress
|
s.ServerIP = s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RateLimitSuite) TestSimpleConfiguration(c *check.C) {
|
func (s *RateLimitSuite) TestSimpleConfiguration(c *check.C) {
|
||||||
|
|
|
@ -3,3 +3,9 @@ whoami1:
|
||||||
labels:
|
labels:
|
||||||
- traefik.enable=true
|
- traefik.enable=true
|
||||||
- traefik.frontend.rule=PathPrefix:/whoami
|
- traefik.frontend.rule=PathPrefix:/whoami
|
||||||
|
- traefik.backend="test"
|
||||||
|
|
||||||
|
whoami2:
|
||||||
|
image: emilevauge/whoami
|
||||||
|
labels:
|
||||||
|
- traefik.enable=false
|
|
@ -11,7 +11,7 @@ consul:
|
||||||
- "8301/udp"
|
- "8301/udp"
|
||||||
- "8302"
|
- "8302"
|
||||||
- "8302/udp"
|
- "8302/udp"
|
||||||
nginx:
|
whoami:
|
||||||
image: nginx:alpine
|
image: emilevauge/whoami
|
||||||
ports:
|
ports:
|
||||||
- "8881:80"
|
- "8881:80"
|
||||||
|
|
|
@ -11,9 +11,9 @@ consul:
|
||||||
- "8301/udp"
|
- "8301/udp"
|
||||||
- "8302"
|
- "8302"
|
||||||
- "8302/udp"
|
- "8302/udp"
|
||||||
nginx1:
|
whoami1:
|
||||||
image: nginx:alpine
|
image: emilevauge/whoami
|
||||||
nginx2:
|
whoami2:
|
||||||
image: nginx:alpine
|
image: emilevauge/whoami
|
||||||
nginx3:
|
whoami3:
|
||||||
image: nginx:alpine
|
image: emilevauge/whoami
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
nginx1:
|
nginx1:
|
||||||
image: nginx:alpine
|
image: nginx:1.13.8-alpine
|
||||||
nginx2:
|
nginx2:
|
||||||
image: nginx:alpine
|
image: nginx:1.13.8-alpine
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
nginx1:
|
whoami1:
|
||||||
image: nginx:alpine
|
image: emilevauge/whoami
|
||||||
ports:
|
ports:
|
||||||
- "8881:80"
|
- "8881:80"
|
||||||
nginx2:
|
whoami2:
|
||||||
image: nginx:alpine
|
image: emilevauge/whoami
|
||||||
ports:
|
ports:
|
||||||
- "8882:80"
|
- "8882:80"
|
||||||
nginx3:
|
whoami3:
|
||||||
image: nginx:alpine
|
image: emilevauge/whoami
|
||||||
ports:
|
ports:
|
||||||
- "8883:80"
|
- "8883:80"
|
||||||
nginx4:
|
whoami4:
|
||||||
image: nginx:alpine
|
image: emilevauge/whoami
|
||||||
ports:
|
ports:
|
||||||
- "8884:80"
|
- "8884:80"
|
||||||
nginx5:
|
whoami5:
|
||||||
image: nginx:alpine
|
image: emilevauge/whoami
|
||||||
ports:
|
ports:
|
||||||
- "8885:80"
|
- "8885:80"
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
nginx1:
|
whoami1:
|
||||||
image: nginx:alpine
|
image: emilevauge/whoami
|
||||||
|
|
|
@ -873,7 +873,7 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura
|
||||||
backendsHealthCheck := map[string]*healthcheck.BackendHealthCheck{}
|
backendsHealthCheck := map[string]*healthcheck.BackendHealthCheck{}
|
||||||
errorHandler := NewRecordingErrorHandler(middlewares.DefaultNetErrorRecorder{})
|
errorHandler := NewRecordingErrorHandler(middlewares.DefaultNetErrorRecorder{})
|
||||||
|
|
||||||
for _, config := range configurations {
|
for providerName, config := range configurations {
|
||||||
frontendNames := sortedFrontendNamesForConfig(config)
|
frontendNames := sortedFrontendNamesForConfig(config)
|
||||||
frontend:
|
frontend:
|
||||||
for _, frontendName := range frontendNames {
|
for _, frontendName := range frontendNames {
|
||||||
|
@ -925,7 +925,7 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura
|
||||||
redirectHandlers[entryPointName] = handlerToUse
|
redirectHandlers[entryPointName] = handlerToUse
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if backends[entryPointName+frontend.Backend] == nil {
|
if backends[entryPointName+providerName+frontend.Backend] == nil {
|
||||||
log.Debugf("Creating backend %s", frontend.Backend)
|
log.Debugf("Creating backend %s", frontend.Backend)
|
||||||
|
|
||||||
roundTripper, err := s.getRoundTripper(entryPointName, globalConfiguration, frontend.PassTLSCert, entryPoint.TLS)
|
roundTripper, err := s.getRoundTripper(entryPointName, globalConfiguration, frontend.PassTLSCert, entryPoint.TLS)
|
||||||
|
@ -1172,14 +1172,14 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura
|
||||||
} else {
|
} else {
|
||||||
n.UseHandler(lb)
|
n.UseHandler(lb)
|
||||||
}
|
}
|
||||||
backends[entryPointName+frontend.Backend] = n
|
backends[entryPointName+providerName+frontend.Backend] = n
|
||||||
} else {
|
} else {
|
||||||
log.Debugf("Reusing backend %s", frontend.Backend)
|
log.Debugf("Reusing backend %s", frontend.Backend)
|
||||||
}
|
}
|
||||||
if frontend.Priority > 0 {
|
if frontend.Priority > 0 {
|
||||||
newServerRoute.route.Priority(frontend.Priority)
|
newServerRoute.route.Priority(frontend.Priority)
|
||||||
}
|
}
|
||||||
s.wireFrontendBackend(newServerRoute, backends[entryPointName+frontend.Backend])
|
s.wireFrontendBackend(newServerRoute, backends[entryPointName+providerName+frontend.Backend])
|
||||||
|
|
||||||
err := newServerRoute.route.GetError()
|
err := newServerRoute.route.GetError()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
"animate.css": "^3.4.0",
|
"animate.css": "^3.4.0",
|
||||||
"bootstrap": "^3.3.6",
|
"bootstrap": "^3.3.6",
|
||||||
"http-status-codes": "^1.3.0",
|
"http-status-codes": "^1.3.0",
|
||||||
|
"lodash": "^4.17.5",
|
||||||
"moment": "^2.14.1",
|
"moment": "^2.14.1",
|
||||||
"nvd3": "^1.8.4"
|
"nvd3": "^1.8.4"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var _ = require('lodash');
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
function ProvidersController($scope, $interval, $log, Providers) {
|
function ProvidersController($scope, $interval, $log, Providers) {
|
||||||
const vm = this;
|
const vm = this;
|
||||||
|
@ -7,7 +9,12 @@ function ProvidersController($scope, $interval, $log, Providers) {
|
||||||
function loadProviders() {
|
function loadProviders() {
|
||||||
Providers
|
Providers
|
||||||
.get()
|
.get()
|
||||||
.then(providers => vm.providers = providers)
|
.then(providers => {
|
||||||
|
if (!_.isEqual(vm.previousProviders, providers)) {
|
||||||
|
vm.providers = providers;
|
||||||
|
vm.previousProviders = _.cloneDeep(providers);
|
||||||
|
}
|
||||||
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
vm.providers = {};
|
vm.providers = {};
|
||||||
$log.error(error);
|
$log.error(error);
|
||||||
|
|
|
@ -1105,7 +1105,7 @@ camelcase@^1.0.2, camelcase@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39"
|
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39"
|
||||||
|
|
||||||
camelcase@^2.0.0, camelcase@^2.0.1:
|
camelcase@^2.0.0:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
|
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
|
||||||
|
|
||||||
|
@ -3929,6 +3929,10 @@ lodash@^4.17.2:
|
||||||
version "4.17.4"
|
version "4.17.4"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
|
||||||
|
|
||||||
|
lodash@^4.17.5:
|
||||||
|
version "4.17.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
|
||||||
|
|
||||||
lodash@~4.16.4:
|
lodash@~4.16.4:
|
||||||
version "4.16.6"
|
version "4.16.6"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.16.6.tgz#d22c9ac660288f3843e16ba7d2b5d06cca27d777"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.16.6.tgz#d22c9ac660288f3843e16ba7d2b5d06cca27d777"
|
||||||
|
|
Loading…
Reference in a new issue