diff --git a/.golangci.toml b/.golangci.toml index 35e627aa0..995580a8b 100644 --- a/.golangci.toml +++ b/.golangci.toml @@ -23,6 +23,10 @@ [linters-settings.misspell] locale = "US" + [linters-settings.funlen] + lines = 230 # default 60 + statements = 120 # default 40 + [linters] enable-all = true disable = [ @@ -37,7 +41,6 @@ "gochecknoinits", "gochecknoglobals", "bodyclose", # Too many false-positive and panics. - "typecheck", # v1.17.1 and Go1.13 => bug ] [issues] @@ -50,8 +53,8 @@ "should have a package comment, unless it's in another file for this package", ] [[issues.exclude-rules]] - path = ".+_test.go" - linters = ["goconst"] + path = "(.+)_test.go" + linters = ["goconst", "funlen"] [[issues.exclude-rules]] path = "integration/.+_test.go" text = "Error return value of `cmd\\.Process\\.Kill` is not checked" diff --git a/.semaphoreci/setup.sh b/.semaphoreci/setup.sh index 568eea2fc..c3cb1ebc9 100755 --- a/.semaphoreci/setup.sh +++ b/.semaphoreci/setup.sh @@ -18,10 +18,9 @@ echo ${SHOULD_TEST} #if [ -n "$SHOULD_TEST" ]; then sudo -E apt-get -yq update; fi #if [ -n "$SHOULD_TEST" ]; then sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install docker-ce=${DOCKER_VERSION}*; fi if [ -n "$SHOULD_TEST" ]; then docker version; fi - export GO_VERSION=1.12 if [ -f "./go.mod" ]; then GO_VERSION="$(grep '^go .*' go.mod | awk '{print $2}')"; export GO_VERSION; fi -if [ "${GO_VERSION}" == '1.13' ]; then export GO_VERSION=1.13rc1; fi +#if [ "${GO_VERSION}" == '1.13' ]; then export GO_VERSION=1.13rc2; fi echo "Selected Go version: ${GO_VERSION}" if [ -f "./.semaphoreci/golang.sh" ]; then ./.semaphoreci/golang.sh; fi @@ -34,5 +33,3 @@ if [ -f "./go.mod" ]; then export GOPROXY=https://proxy.golang.org; fi if [ -f "./go.mod" ]; then go mod download; fi df - - diff --git a/CHANGELOG.md b/CHANGELOG.md index 39faf0a9d..47e896fac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,54 @@ # Change Log +## [v2.0.0-rc3](https://github.com/containous/traefik/tree/v2.0.0-rc3) (2019-09-10) +[All Commits](https://github.com/containous/traefik/compare/v2.0.0-rc2...v2.0.0-rc3) + +**Enhancements:** +- **[acme,api,tracing]** New API security ([#5311](https://github.com/containous/traefik/pull/5311) by [juliens](https://github.com/juliens)) +- **[authentication,middleware,k8s,k8s/crd]** Auth middlewares in kubernetes CRD use secrets ([#5299](https://github.com/containous/traefik/pull/5299) by [juliens](https://github.com/juliens)) +- **[logs]** Default to CLF when accesslog format is unsupported ([#5314](https://github.com/containous/traefik/pull/5314) by [mpl](https://github.com/mpl)) +- **[middleware,k8s,k8s/crd]** k8s ErrorPage middleware now uses k8s service ([#5339](https://github.com/containous/traefik/pull/5339) by [juliens](https://github.com/juliens)) +- **[webui]** Add more pages in the WebUI ([#5278](https://github.com/containous/traefik/pull/5278) by [Basgrani](https://github.com/Basgrani)) + +**Bug fixes:** +- **[api]** Add provider in middleware chain ([#5334](https://github.com/containous/traefik/pull/5334) by [juliens](https://github.com/juliens)) +- **[k8s,k8s/crd]** fix: TLS domains with IngressRoute. ([#5327](https://github.com/containous/traefik/pull/5327) by [ldez](https://github.com/ldez)) +- **[middleware]** Improve rate limiter tests ([#5310](https://github.com/containous/traefik/pull/5310) by [mpl](https://github.com/mpl)) +- **[server]** Write HTTP server logs into the global logger. ([#5329](https://github.com/containous/traefik/pull/5329) by [ldez](https://github.com/ldez)) + +**Documentation:** +- Misc documentation fixes ([#5307](https://github.com/containous/traefik/pull/5307) by [ldez](https://github.com/ldez)) +- misc documentation fixes ([#5302](https://github.com/containous/traefik/pull/5302) by [mpl](https://github.com/mpl)) +- Enhance the Retry Middleware Documentation ([#5298](https://github.com/containous/traefik/pull/5298) by [jbdoumenjou](https://github.com/jbdoumenjou)) + +**Misc:** +- Cherry pick v1.7 into v2.0 ([#5341](https://github.com/containous/traefik/pull/5341) by [jbdoumenjou](https://github.com/jbdoumenjou)) + +## [v2.0.0-rc2](https://github.com/containous/traefik/tree/v2.0.0-rc2) (2019-09-03) +[All Commits](https://github.com/containous/traefik/compare/v2.0.0-rc1...v2.0.0-rc2) + +**Enhancements:** +- **[api]** Improve API for the web UI ([#5267](https://github.com/containous/traefik/pull/5267) by [ldez](https://github.com/ldez)) +- **[middleware,tracing]** Re enable ratelimit integration tests ([#5288](https://github.com/containous/traefik/pull/5288) by [mmatur](https://github.com/mmatur)) +- **[tracing]** Update Zipkin OpenTracing driver to latest 0.4.3 release ([#5283](https://github.com/containous/traefik/pull/5283) by [basvanbeek](https://github.com/basvanbeek)) + +**Bug fixes:** +- **[api]** Add errors about unknown entryPoint in runtime api ([#5265](https://github.com/containous/traefik/pull/5265) by [juliens](https://github.com/juliens)) +- **[metrics,tracing]** fix: Datadog case. ([#5272](https://github.com/containous/traefik/pull/5272) by [ldez](https://github.com/ldez)) +- **[middleware,k8s,k8s/crd]** The chain middleware in k8s use middlewareRef ([#5290](https://github.com/containous/traefik/pull/5290) by [juliens](https://github.com/juliens)) +- **[middleware]** Don't panic with undefined middleware ([#5289](https://github.com/containous/traefik/pull/5289) by [ldez](https://github.com/ldez)) +- **[middleware]** fix buffering middleware ([#5281](https://github.com/containous/traefik/pull/5281) by [ldez](https://github.com/ldez)) +- **[middleware]** fix: stripPrefix and stripPrefixRegex. ([#5291](https://github.com/containous/traefik/pull/5291) by [ldez](https://github.com/ldez)) +- **[service,websocket]** Fix recovered panic when websocket is mirrored ([#5255](https://github.com/containous/traefik/pull/5255) by [juliens](https://github.com/juliens)) +- **[webui]** Rest provider icon in the webui ([#5261](https://github.com/containous/traefik/pull/5261) by [mmatur](https://github.com/mmatur)) +- Fix trailing slash with check new version ([#5266](https://github.com/containous/traefik/pull/5266) by [mmatur](https://github.com/mmatur)) + +**Documentation:** +- **[middleware]** fix: stripPrefixRegex documentation. ([#5273](https://github.com/containous/traefik/pull/5273) by [ldez](https://github.com/ldez)) +- Fix some documentation issues ([#5286](https://github.com/containous/traefik/pull/5286) by [jbdoumenjou](https://github.com/jbdoumenjou)) +- Update restrictions in the documentation. ([#5270](https://github.com/containous/traefik/pull/5270) by [ldez](https://github.com/ldez)) +- Base of the migration guide ([#5263](https://github.com/containous/traefik/pull/5263) by [jbdoumenjou](https://github.com/jbdoumenjou)) + ## [v2.0.0-rc1](https://github.com/containous/traefik/tree/v2.0.0-rc1) (2019-08-26) [All Commits](https://github.com/containous/traefik/compare/v2.0.0-beta1...v2.0.0-rc1) @@ -37,6 +86,13 @@ **Misc:** - Cherry pick v1.7 into v2.0 ([#5192](https://github.com/containous/traefik/pull/5192) by [ldez](https://github.com/ldez)) +## [v1.7.14](https://github.com/containous/traefik/tree/v1.7.14) (2019-08-14) +[All Commits](https://github.com/containous/traefik/compare/v1.7.13...v1.7.14) + +**Bug fixes:** +- Update to go1.12.8 ([#5201](https://github.com/containous/traefik/pull/5201) by [ldez](https://github.com/ldez)). HTTP/2 Denial of Service [CVE-2019-9512](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9512) and [CVE-2019-9514](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9514) +- **[server]** Make hijackConnectionTracker.Close thread safe ([#5194](https://github.com/containous/traefik/pull/5194) by [jlevesy](https://github.com/jlevesy)) + ## [v1.7.13](https://github.com/containous/traefik/tree/v1.7.13) (2019-08-07) [All Commits](https://github.com/containous/traefik/compare/v1.7.12...v1.7.13) @@ -217,7 +273,7 @@ - **[api,authentication]** Remove authentication hashes from API ([#4918](https://github.com/containous/traefik/pull/4918) by [ldez](https://github.com/ldez)) - **[consul]** Enhance KV logs. ([#4877](https://github.com/containous/traefik/pull/4877) by [ldez](https://github.com/ldez)) - **[k8s]** Fix kubernetes template for backend responseforwarding flushinterval setting ([#4901](https://github.com/containous/traefik/pull/4901) by [ravilr](https://github.com/ravilr)) -- **[metrics]** Upgraded DataDog tracing library to 1.13.0 ([#4878](https://github.com/containous/traefik/pull/4878) by [aantono](https://github.com/aantono)) +- **[metrics]** Upgraded Datadog tracing library to 1.13.0 ([#4878](https://github.com/containous/traefik/pull/4878) by [aantono](https://github.com/aantono)) - **[server]** Add missing callback on close of hijacked connections ([#4900](https://github.com/containous/traefik/pull/4900) by [ravilr](https://github.com/ravilr)) **Documentation:** @@ -419,7 +475,7 @@ - **[k8s/ingress]** Loop through service ports for global backend ([#4486](https://github.com/containous/traefik/pull/4486) by [dtomcej](https://github.com/dtomcej)) - **[k8s]** Add entrypoints prefix in kubernetes frontend/backend id ([#4679](https://github.com/containous/traefik/pull/4679) by [juliens](https://github.com/juliens)) - **[websocket]** Exclude websocket connections from Average Response Time ([#4313](https://github.com/containous/traefik/pull/4313) by [siyu6974](https://github.com/siyu6974)) -- **[middleware]** Added support for configuring trace headers for DataDog tracing ([#4516](https://github.com/containous/traefik/pull/4516) by [aantono](https://github.com/aantono)) +- **[middleware]** Added support for configuring trace headers for Datadog tracing ([#4516](https://github.com/containous/traefik/pull/4516) by [aantono](https://github.com/aantono)) **Documentation:** - **[acme]** Add _FILE Environment Variable Documentation ([#4643](https://github.com/containous/traefik/pull/4643) by [dargmuesli](https://github.com/dargmuesli)) @@ -670,7 +726,7 @@ - **[metrics]** Metrics: Add support for InfluxDB Database / RetentionPolicy and HTTP client ([#3391](https://github.com/containous/traefik/pull/3391) by [drewkerrigan](https://github.com/drewkerrigan)) - **[middleware,consulcatalog,docker,ecs,kv,marathon,mesos,rancher]** Pass the TLS Cert infos in headers ([#3826](https://github.com/containous/traefik/pull/3826) by [jbdoumenjou](https://github.com/jbdoumenjou)) - **[middleware,server]** Extreme Makeover: server refactoring ([#3461](https://github.com/containous/traefik/pull/3461) by [ldez](https://github.com/ldez)) -- **[middleware,tracing]** Added integration support for DataDog APM Tracing ([#3517](https://github.com/containous/traefik/pull/3517) by [aantono](https://github.com/aantono)) +- **[middleware,tracing]** Added integration support for Datadog APM Tracing ([#3517](https://github.com/containous/traefik/pull/3517) by [aantono](https://github.com/aantono)) - **[middleware,tracing]** Create a custom logger for jaeger ([#3541](https://github.com/containous/traefik/pull/3541) by [mmatur](https://github.com/mmatur)) - **[middleware]** Performance enhancements for the rules matchers. ([#3563](https://github.com/containous/traefik/pull/3563) by [ShaneSaww](https://github.com/ShaneSaww)) - **[middleware]** Extract internal router creation from server ([#3204](https://github.com/containous/traefik/pull/3204) by [Juliens](https://github.com/Juliens)) @@ -731,7 +787,7 @@ - **[oxy]** Handle Te header when http2 ([#3824](https://github.com/containous/traefik/pull/3824) by [Juliens](https://github.com/Juliens)) - **[server]** Avoid goroutine leak in server ([#3851](https://github.com/containous/traefik/pull/3851) by [nmengin](https://github.com/nmengin)) - **[server]** Avoid panic during stop ([#3898](https://github.com/containous/traefik/pull/3898) by [nmengin](https://github.com/nmengin)) -- **[tracing]** Added default configuration for DataDog APM Tracer ([#3655](https://github.com/containous/traefik/pull/3655) by [aantono](https://github.com/aantono)) +- **[tracing]** Added default configuration for Datadog APM Tracer ([#3655](https://github.com/containous/traefik/pull/3655) by [aantono](https://github.com/aantono)) - **[tracing]** Added support for Trace name truncation for traces ([#3689](https://github.com/containous/traefik/pull/3689) by [aantono](https://github.com/aantono)) - **[websocket]** Handle shutdown of Hijacked connections ([#3636](https://github.com/containous/traefik/pull/3636) by [Juliens](https://github.com/Juliens)) - **[webui]** Added Dashboard table item for Rate Limits ([#3893](https://github.com/containous/traefik/pull/3893) by [codecyclist](https://github.com/codecyclist)) @@ -868,7 +924,7 @@ - **[docker]** Uses both binded HostIP and HostPort when useBindPortIP=true ([#3638](https://github.com/containous/traefik/pull/3638) by [geraldcroes](https://github.com/geraldcroes)) - **[k8s]** Fix Rewrite-target regex ([#3699](https://github.com/containous/traefik/pull/3699) by [dtomcej](https://github.com/dtomcej)) - **[middleware]** Correct Entrypoint Redirect with Stripped or Added Path ([#3631](https://github.com/containous/traefik/pull/3631) by [dtomcej](https://github.com/dtomcej)) -- **[tracing]** Added default configuration for DataDog APM Tracer ([#3655](https://github.com/containous/traefik/pull/3655) by [aantono](https://github.com/aantono)) +- **[tracing]** Added default configuration for Datadog APM Tracer ([#3655](https://github.com/containous/traefik/pull/3655) by [aantono](https://github.com/aantono)) - **[tracing]** Added support for Trace name truncation for traces ([#3689](https://github.com/containous/traefik/pull/3689) by [aantono](https://github.com/aantono)) - **[websocket]** Handle shutdown of Hijacked connections ([#3636](https://github.com/containous/traefik/pull/3636) by [Juliens](https://github.com/Juliens)) - H2C: Remove buggy line in init to make verbose switch working ([#3701](https://github.com/containous/traefik/pull/3701) by [dduportal](https://github.com/dduportal)) @@ -964,7 +1020,7 @@ - **[mesos]** Segments Labels: Mesos ([#3383](https://github.com/containous/traefik/pull/3383) by [drewkerrigan](https://github.com/drewkerrigan)) - **[metrics]** Metrics: Add support for InfluxDB Database / RetentionPolicy and HTTP client ([#3391](https://github.com/containous/traefik/pull/3391) by [drewkerrigan](https://github.com/drewkerrigan)) - **[middleware,server]** Extreme Makeover: server refactoring ([#3461](https://github.com/containous/traefik/pull/3461) by [ldez](https://github.com/ldez)) -- **[middleware,tracing]** Added integration support for DataDog APM Tracing ([#3517](https://github.com/containous/traefik/pull/3517) by [aantono](https://github.com/aantono)) +- **[middleware,tracing]** Added integration support for Datadog APM Tracing ([#3517](https://github.com/containous/traefik/pull/3517) by [aantono](https://github.com/aantono)) - **[middleware,tracing]** Create a custom logger for jaeger ([#3541](https://github.com/containous/traefik/pull/3541) by [mmatur](https://github.com/mmatur)) - **[middleware]** Performance enhancements for the rules matchers. ([#3563](https://github.com/containous/traefik/pull/3563) by [ShaneSaww](https://github.com/ShaneSaww)) - **[middleware]** Extract internal router creation from server ([#3204](https://github.com/containous/traefik/pull/3204) by [Juliens](https://github.com/Juliens)) @@ -1142,7 +1198,7 @@ - **[metrics]** Added entrypoint metrics to influxdb ([#2992](https://github.com/containous/traefik/pull/2992) by [adityacs](https://github.com/adityacs)) - **[metrics]** Remove unnecessary conversion ([#2850](https://github.com/containous/traefik/pull/2850) by [ferhatelmas](https://github.com/ferhatelmas)) - **[metrics]** Extend metrics and rebuild prometheus exporting logic ([#2567](https://github.com/containous/traefik/pull/2567) by [marco-jantke](https://github.com/marco-jantke)) -- **[metrics]** Added missing metrics to registry for DataDog and StatsD ([#2890](https://github.com/containous/traefik/pull/2890) by [aantono](https://github.com/aantono)) +- **[metrics]** Added missing metrics to registry for Datadog and StatsD ([#2890](https://github.com/containous/traefik/pull/2890) by [aantono](https://github.com/aantono)) - **[middleware,consul,consulcatalog,docker,ecs,k8s,marathon,mesos,rancher]** New option in secure middleware ([#2958](https://github.com/containous/traefik/pull/2958) by [mmatur](https://github.com/mmatur)) - **[middleware,consulcatalog,docker,ecs,k8s,kv,marathon,mesos,rancher]** Ability to use "X-Forwarded-For" as a source of IP for white list. ([#3070](https://github.com/containous/traefik/pull/3070) by [ldez](https://github.com/ldez)) - **[middleware,docker]** Use pointer of error pages ([#2607](https://github.com/containous/traefik/pull/2607) by [ldez](https://github.com/ldez)) @@ -1394,7 +1450,7 @@ - **[mesos]** Add all available labels to Mesos Backend ([#2687](https://github.com/containous/traefik/pull/2687) by [ldez](https://github.com/ldez)) - **[metrics]** Added entrypoint metrics to influxdb ([#2992](https://github.com/containous/traefik/pull/2992) by [adityacs](https://github.com/adityacs)) - **[metrics]** Extend metrics and rebuild prometheus exporting logic ([#2567](https://github.com/containous/traefik/pull/2567) by [marco-jantke](https://github.com/marco-jantke)) -- **[metrics]** Added missing metrics to registry for DataDog and StatsD ([#2890](https://github.com/containous/traefik/pull/2890) by [aantono](https://github.com/aantono)) +- **[metrics]** Added missing metrics to registry for Datadog and StatsD ([#2890](https://github.com/containous/traefik/pull/2890) by [aantono](https://github.com/aantono)) - **[metrics]** Remove unnecessary conversion ([#2850](https://github.com/containous/traefik/pull/2850) by [ferhatelmas](https://github.com/ferhatelmas)) - **[middleware,consul,consulcatalog,docker,ecs,k8s,marathon,mesos,rancher]** New option in secure middleware ([#2958](https://github.com/containous/traefik/pull/2958) by [mmatur](https://github.com/mmatur)) - **[middleware,consulcatalog,docker,ecs,k8s,kv,marathon,mesos,rancher]** Ability to use "X-Forwarded-For" as a source of IP for white list. ([#3070](https://github.com/containous/traefik/pull/3070) by [ldez](https://github.com/ldez)) @@ -2015,12 +2071,12 @@ - **[marathon]** Add support for readiness checks. ([#1883](https://github.com/containous/traefik/pull/1883) by [timoreimann](https://github.com/timoreimann)) - **[marathon]** Move marathon mock ([#1732](https://github.com/containous/traefik/pull/1732) by [ldez](https://github.com/ldez)) - **[marathon]** Use single API call to fetch Marathon resources. ([#1815](https://github.com/containous/traefik/pull/1815) by [timoreimann](https://github.com/timoreimann)) -- **[metrics]** Added RetryMetrics to DataDog and StatsD providers ([#1884](https://github.com/containous/traefik/pull/1884) by [aantono](https://github.com/aantono)) +- **[metrics]** Added RetryMetrics to Datadog and StatsD providers ([#1884](https://github.com/containous/traefik/pull/1884) by [aantono](https://github.com/aantono)) - **[metrics]** Extract metrics to own package and refactor implementations ([#1968](https://github.com/containous/traefik/pull/1968) by [marco-jantke](https://github.com/marco-jantke)) - **[metrics]** Add metrics for backend_retries_total ([#1504](https://github.com/containous/traefik/pull/1504) by [marco-jantke](https://github.com/marco-jantke)) - **[metrics]** Add status code to request duration metric ([#1755](https://github.com/containous/traefik/pull/1755) by [marco-jantke](https://github.com/marco-jantke)) - **[middleware]** Add trusted whitelist proxy protocol ([#2234](https://github.com/containous/traefik/pull/2234) by [emilevauge](https://github.com/emilevauge))) -- **[metrics]** DataDog and StatsD Metrics Support ([#1701](https://github.com/containous/traefik/pull/1701) by [aantono](https://github.com/aantono)) +- **[metrics]** Datadog and StatsD Metrics Support ([#1701](https://github.com/containous/traefik/pull/1701) by [aantono](https://github.com/aantono)) - **[middleware]** Create Header Middleware ([#1236](https://github.com/containous/traefik/pull/1236) by [dtomcej](https://github.com/dtomcej)) - **[middleware]** Add configurable timeouts and curate default timeout settings ([#1873](https://github.com/containous/traefik/pull/1873) by [marco-jantke](https://github.com/marco-jantke)) - **[middleware]** Fix command bug content. ([#2002](https://github.com/containous/traefik/pull/2002) by [ldez](https://github.com/ldez)) @@ -2326,11 +2382,11 @@ - **[marathon]** Move marathon mock ([#1732](https://github.com/containous/traefik/pull/1732) by [ldez](https://github.com/ldez)) - **[marathon]** Support multi-port service routing for containers running on Marathon ([#1742](https://github.com/containous/traefik/pull/1742) by [aantono](https://github.com/aantono)) - **[marathon]** Use test builder. ([#1871](https://github.com/containous/traefik/pull/1871) by [timoreimann](https://github.com/timoreimann)) -- **[metrics]** DataDog and StatsD Metrics Support ([#1701](https://github.com/containous/traefik/pull/1701) by [aantono](https://github.com/aantono)) +- **[metrics]** Datadog and StatsD Metrics Support ([#1701](https://github.com/containous/traefik/pull/1701) by [aantono](https://github.com/aantono)) - **[metrics]** Add status code to request duration metric ([#1755](https://github.com/containous/traefik/pull/1755) by [marco-jantke](https://github.com/marco-jantke)) - **[metrics]** Add metrics for backend_retries_total ([#1504](https://github.com/containous/traefik/pull/1504) by [marco-jantke](https://github.com/marco-jantke)) - **[metrics]** Extract metrics to own package and refactor implementations ([#1968](https://github.com/containous/traefik/pull/1968) by [marco-jantke](https://github.com/marco-jantke)) -- **[metrics]** Added RetryMetrics to DataDog and StatsD providers ([#1884](https://github.com/containous/traefik/pull/1884) by [aantono](https://github.com/aantono)) +- **[metrics]** Added RetryMetrics to Datadog and StatsD providers ([#1884](https://github.com/containous/traefik/pull/1884) by [aantono](https://github.com/aantono)) - **[middleware]** Return 503 on empty backend ([#1748](https://github.com/containous/traefik/pull/1748) by [marco-jantke](https://github.com/marco-jantke)) - **[middleware]** Add configurable timeouts and curate default timeout settings ([#1873](https://github.com/containous/traefik/pull/1873) by [marco-jantke](https://github.com/marco-jantke)) - **[middleware]** Custom Error Pages ([#1675](https://github.com/containous/traefik/pull/1675) by [bparli](https://github.com/bparli)) diff --git a/README.md b/README.md index e55fa5880..517a88b55 100644 --- a/README.md +++ b/README.md @@ -90,8 +90,7 @@ To get your hands on Traefik, you can use the [5-Minute Quickstart](http://docs. You can access the simple HTML frontend of Traefik. -![Web UI Providers](docs/content/assets/img/dashboard-main.png) -![Web UI Health](docs/content/assets/img/dashboard-health.png) +![Web UI Providers](docs/content/assets/img/webui-dashboard.png) ## Documentation diff --git a/build.Dockerfile b/build.Dockerfile index 27c308562..542617644 100644 --- a/build.Dockerfile +++ b/build.Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.13rc1-alpine +FROM golang:1.13-alpine RUN apk --update upgrade \ && apk --no-cache --no-progress add git mercurial bash gcc musl-dev curl tar ca-certificates tzdata \ @@ -19,7 +19,7 @@ RUN mkdir -p /usr/local/bin \ && chmod +x /usr/local/bin/go-bindata # Download golangci-lint binary to bin folder in $GOPATH -RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.17.1 +RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.18.0 # Download golangci-lint and misspell binary to bin folder in $GOPATH RUN GO111MODULE=off go get github.com/client9/misspell/cmd/misspell diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index 6c46fef78..8702855b9 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -299,14 +299,14 @@ You haven't specified the sendAnonymousUsage option, it will be enabled by defau Stats collection is enabled. Many thanks for contributing to Traefik's improvement by allowing us to receive anonymous information from your configuration. Help us improve Traefik by leaving this feature on :) -More details on: https://docs.traefik.io/basics/#collected-data +More details on: https://docs.traefik.io/v2.0/contributing/data-collection/ `) collect(staticConfiguration) } else { log.WithoutContext().Info(` Stats collection is disabled. Help us improve Traefik by turning this feature on :) -More details on: https://docs.traefik.io/basics/#collected-data +More details on: https://docs.traefik.io/v2.0/contributing/data-collection/ `) } } diff --git a/docs/content/assets/img/dashboard-health.png b/docs/content/assets/img/dashboard-health.png deleted file mode 100644 index cf3160975..000000000 Binary files a/docs/content/assets/img/dashboard-health.png and /dev/null differ diff --git a/docs/content/assets/img/dashboard-main.png b/docs/content/assets/img/dashboard-main.png deleted file mode 100644 index 62a90e5e9..000000000 Binary files a/docs/content/assets/img/dashboard-main.png and /dev/null differ diff --git a/docs/content/assets/img/middleware/ratelimit.png b/docs/content/assets/img/middleware/ratelimit.png deleted file mode 100644 index 049af712e..000000000 Binary files a/docs/content/assets/img/middleware/ratelimit.png and /dev/null differ diff --git a/docs/content/assets/img/webui-dashboard.png b/docs/content/assets/img/webui-dashboard.png new file mode 100644 index 000000000..80a5178b0 Binary files /dev/null and b/docs/content/assets/img/webui-dashboard.png differ diff --git a/docs/content/contributing/building-testing.md b/docs/content/contributing/building-testing.md index 95b713490..65240f216 100644 --- a/docs/content/contributing/building-testing.md +++ b/docs/content/contributing/building-testing.md @@ -28,7 +28,7 @@ Successfully tagged traefik-webui:latest [...] docker build -t "traefik-dev:4475--feature-documentation" -f build.Dockerfile . Sending build context to Docker daemon 279MB -Step 1/10 : FROM golang:1.13rc1-alpine +Step 1/10 : FROM golang:1.13-alpine ---> f4bfb3d22bda [...] Successfully built 5c3c1a911277 diff --git a/docs/content/contributing/data-collection.md b/docs/content/contributing/data-collection.md index 03a6d90bc..4442fc78e 100644 --- a/docs/content/contributing/data-collection.md +++ b/docs/content/contributing/data-collection.md @@ -9,7 +9,7 @@ Understanding how you use Traefik is very important to us: it helps us improve t For this very reason, the sendAnonymousUsage option is mandatory: we want you to take time to consider whether or not you wish to share anonymous data with us so we can benefit from your experience and use cases. !!! warning - During the beta stage only, leaving this option unset will not prevent Traefik from running but will generate an error log indicating that it enables data collection by default. + Before the GA, leaving this option unset will not prevent Traefik from running but will generate an error log indicating that it enables data collection by default. !!! example "Enabling Data Collection" diff --git a/docs/content/https/tls.md b/docs/content/https/tls.md index d9dd7b850..9dcf660cc 100644 --- a/docs/content/https/tls.md +++ b/docs/content/https/tls.md @@ -35,7 +35,7 @@ tls: !!! important "File Provider Only" In the above example, we've used the [file provider](../providers/file.md) to handle these definitions. - In its current beta version, it is the only available method to configure the certificates (as well as the options and the stores). + It is the only available method to configure the certificates (as well as the options and the stores). ## Certificates Stores @@ -52,9 +52,9 @@ tls: default: {} ``` -!!! important "Beta restriction" +!!! important "Restriction" - During the beta version, any store definition other than the default one (named `default`) will be ignored, + Any store definition other than the default one (named `default`) will be ignored, and there is thefore only one globally available TLS store. In the `tls.certificates` section, a list of stores can then be specified to indicate where the certificates should be stored: @@ -85,9 +85,9 @@ tls: keyFile: /path/to/other-domain.key ``` -!!! important "Beta restriction" +!!! important "Restriction" - During the beta version, the `stores` list will actually be ignored and automatically set to `["default"]`. + The `stores` list will actually be ignored and automatically set to `["default"]`. ### Default Certificate @@ -141,17 +141,17 @@ tls: ### Client Authentication (mTLS) -Traefik supports mutual authentication, through the `ClientAuth` section. +Traefik supports mutual authentication, through the `clientAuth` section. -For authentication policies that require verification of the client certificate, the certificate authority for the certificate should be set in `ClientAuth.caFiles`. +For authentication policies that require verification of the client certificate, the certificate authority for the certificate should be set in `clientAuth.caFiles`. -The `ClientAuth.clientAuthType` option governs the behaviour as follows: +The `clientAuth.clientAuthType` option governs the behaviour as follows: - `NoClientCert`: disregards any client certificate. - `RequestClientCert`: asks for a certificate but proceeds anyway if none is provided. -- `RequireAnyClientCert`: requires a certificate but does not verify if it is signed by a CA listed in `ClientAuth.caFiles`. -- `VerifyClientCertIfGiven`: if a certificate is provided, verifies if it is signed by a CA listed in `ClientAuth.caFiles`. Otherwise proceeds without any certificate. -- `RequireAndVerifyClientCert`: requires a certificate, which must be signed by a CA listed in `ClientAuth.caFiles`. +- `RequireAnyClientCert`: requires a certificate but does not verify if it is signed by a CA listed in `clientAuth.caFiles`. +- `VerifyClientCertIfGiven`: if a certificate is provided, verifies if it is signed by a CA listed in `clientAuth.caFiles`. Otherwise proceeds without any certificate. +- `RequireAndVerifyClientCert`: requires a certificate, which must be signed by a CA listed in `clientAuth.caFiles`. ```toml tab="TOML" [tls.options] diff --git a/docs/content/middlewares/basicauth.md b/docs/content/middlewares/basicauth.md index 83f97d0cb..2e44eb076 100644 --- a/docs/content/middlewares/basicauth.md +++ b/docs/content/middlewares/basicauth.md @@ -16,7 +16,7 @@ The BasicAuth middleware is a quick way to restrict access to your services to k # To create user:password pair, it's possible to use this command: # echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g labels: - - "traefik.http.middlewares.test-auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0" +- "traefik.http.middlewares.test-auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0" ``` ```yaml tab="Kubernetes" @@ -27,9 +27,7 @@ metadata: name: test-auth spec: basicAuth: - users: - - test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/ - - test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 + secret: secretName ``` ```json tab="Marathon" @@ -41,7 +39,7 @@ spec: ```yaml tab="Rancher" # Declaring the user list labels: - - "traefik.http.middlewares.test-auth.basicauth.users=test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" +- "traefik.http.middlewares.test-auth.basicauth.users=test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" ``` ```toml tab="File (TOML)" @@ -81,7 +79,74 @@ The `users` option is an array of authorized users. Each user will be declared u !!! Note - If both `users` and `usersFile` are provided, the two are merged. The content of `usersFile` has precedence over `users`. + - If both `users` and `usersFile` are provided, the two are merged. The contents of `usersFile` have precedence over the values in `users`. + - For security reasons, the field `users` doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead. + +```yaml tab="Docker" +# Declaring the user list +# +# Note: all dollar signs in the hash need to be doubled for escaping. +# To create user:password pair, it's possible to use this command: +# echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g +labels: +- "traefik.http.middlewares.test-auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0" +``` + +```yaml tab="Kubernetes" +# Declaring the user list +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + basicAuth: + secret: authsecret + +--- +apiVersion: v1 +kind: Secret +metadata: + name: authsecret + namespace: default + +data: + users: |2 + dGVzdDokYXByMSRINnVza2trVyRJZ1hMUDZld1RyU3VCa1RycUU4d2ovCnRlc3QyOiRhcHIxJGQ5 + aHI5SEJCJDRIeHdnVWlyM0hQNEVzZ2dQL1FObzAK +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.basicauth.users": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" +} +``` + +```yaml tab="Rancher" +# Declaring the user list +labels: +- "traefik.http.middlewares.test-auth.basicauth.users=test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" +``` + +```toml tab="File (TOML)" +# Declaring the user list +[http.middlewares] + [http.middlewares.test-auth.basicAuth] + users = [ + "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", + "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + ] +``` + +```yaml tab="File (YAML)" +# Declaring the user list +http: + middlewares: + test-auth: + basicAuth: + users: + - "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/" + - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" +``` ### `usersFile` @@ -89,6 +154,63 @@ The `usersFile` option is the path to an external file that contains the authori The file content is a list of `name:encoded-password`. +!!! Note + + - If both `users` and `usersFile` are provided, the two are merged. The contents of `usersFile` have precedence over the values in `users`. + - Because it does not make much sense to refer to a file path on Kubernetes, the `usersFile` field doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead. + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.basicauth.usersfile=/path/to/my/usersfile" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + basicAuth: + secret: authsecret + +--- +apiVersion: v1 +kind: Secret +metadata: + name: authsecret + namespace: default + +data: + users: |2 + dGVzdDokYXByMSRINnVza2trVyRJZ1hMUDZld1RyU3VCa1RycUU4d2ovCnRlc3QyOiRhcHIxJGQ5 + aHI5SEJCJDRIeHdnVWlyM0hQNEVzZ2dQL1FObzAK +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.basicauth.usersfile": "/path/to/my/usersfile" +} +``` + +```yaml tab="Rancher" +labels: + - "traefik.http.middlewares.test-auth.basicauth.usersfile=/path/to/my/usersfile" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.basicAuth] + usersFile = "/path/to/my/usersfile" +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + basicAuth: + usersFile: "/path/to/my/usersfile" +``` + ??? example "A file containing test/test and test2/test2" ```txt @@ -96,21 +218,57 @@ The file content is a list of `name:encoded-password`. test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 ``` -!!! Note - - If both `users` and `usersFile` are provided, the two are merged. The content of `usersFile` has precedence over `users`. - ### `realm` You can customize the realm for the authentication with the `realm` option. The default value is `traefik`. +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.basicauth.realm=MyRealm" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + basicAuth: + realm: MyRealm +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.basicauth.realm": "MyRealm" +} +``` + +```yaml tab="Rancher" +labels: + - "traefik.http.middlewares.test-auth.basicauth.realm=MyRealm" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.basicAuth] + realm = "MyRealm" +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + basicAuth: + realm: "MyRealm" +``` + ### `headerField` -You can customize the header field for the authenticated user using the `headerField`option. +You can define a header field to store the authenticated user using the `headerField`option. ```yaml tab="Docker" labels: - - "traefik.http.middlewares.my-auth.basicauth.headerField=X-WebAuth-User" +- "traefik.http.middlewares.my-auth.basicauth.headerField=X-WebAuth-User" ``` ```yaml tab="Kubernetes" @@ -148,3 +306,43 @@ http: ### `removeHeader` Set the `removeHeader` option to `true` to remove the authorization header before forwarding the request to your service. (Default value is `false`.) + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.basicauth.removeheader=true" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + basicAuth: + removeHeader: true +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.basicauth.removeheader": "true" +} +``` + +```yaml tab="Rancher" +labels: +- "traefik.http.middlewares.test-auth.basicauth.removeheader=true" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.basicAuth] + removeHeader = true +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + basicAuth: + removeHeader: true +``` diff --git a/docs/content/middlewares/chain.md b/docs/content/middlewares/chain.md index 8ad38f7dd..3d7b6d53c 100644 --- a/docs/content/middlewares/chain.md +++ b/docs/content/middlewares/chain.md @@ -51,9 +51,9 @@ metadata: spec: chain: middlewares: - - https-only - - known-ips - - auth-users + - name: https-only + - name: known-ips + - name: auth-users --- apiVersion: traefik.containo.us/v1alpha1 kind: Middleware diff --git a/docs/content/middlewares/circuitbreaker.md b/docs/content/middlewares/circuitbreaker.md index 03f2c0c19..1f4c928ed 100644 --- a/docs/content/middlewares/circuitbreaker.md +++ b/docs/content/middlewares/circuitbreaker.md @@ -132,7 +132,7 @@ For example, the expression `LatencyAtQuantileMS(50.0) > 100` will trigger the c !!! Note - You must provide a float number (with the leading .0) for the quantile value + You must provide a float number (with the trailing .0) for the quantile value #### Using multiple metrics @@ -153,7 +153,6 @@ Here is the list of supported operators: - Greater or equal than (`>=`) - Lesser than (`<`) - Lesser or equal than (`<=`) -- Not (`!`) - Equal (`==`) - Not Equal (`!=`) diff --git a/docs/content/middlewares/compress.md b/docs/content/middlewares/compress.md index a1b07b84e..8137899f2 100644 --- a/docs/content/middlewares/compress.md +++ b/docs/content/middlewares/compress.md @@ -55,6 +55,6 @@ http: Responses are compressed when: -* The response body is larger than `512` bytes. +* The response body is larger than `1400` bytes. * The `Accept-Encoding` request header contains `gzip`. * The response is not already compressed, i.e. the `Content-Encoding` response header is not already set. diff --git a/docs/content/middlewares/digestauth.md b/docs/content/middlewares/digestauth.md index 7af3a5390..0462599bd 100644 --- a/docs/content/middlewares/digestauth.md +++ b/docs/content/middlewares/digestauth.md @@ -10,6 +10,7 @@ The DigestAuth middleware is a quick way to restrict access to your services to ## Configuration Examples ```yaml tab="Docker" +# Declaring the user list labels: - "traefik.http.middlewares.test-auth.digestauth.users=test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e" ``` @@ -22,9 +23,82 @@ metadata: name: test-auth spec: digestAuth: - users: - - test:traefik:a2688e031edb4be6a3797f3882655c05 - - test2:traefik:518845800f9e2bfb1f1f740ec24f074e + secret: userssecret +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.digestauth.users": "test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e" +} +``` + +```yaml tab="Rancher" +# Declaring the user list +labels: +- "traefik.http.middlewares.test-auth.digestauth.users=test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e" +``` + +```toml tab="File (TOML)" +# Declaring the user list +[http.middlewares] + [http.middlewares.test-auth.digestAuth] + users = [ + "test:traefik:a2688e031edb4be6a3797f3882655c05", + "test2:traefik:518845800f9e2bfb1f1f740ec24f074e", + ] +``` + +```yaml tab="File (YAML)" +# Declaring the user list +http: + middlewares: + test-auth: + digestAuth: + users: + - "test:traefik:a2688e031edb4be6a3797f3882655c05" + - "test2:traefik:518845800f9e2bfb1f1f740ec24f074e" +``` + +## Configuration Options + +!!! tip + + Use `htdigest` to generate passwords. + +### `users` + +The `users` option is an array of authorized users. Each user will be declared using the `name:realm:encoded-password` format. + +!!! Note + + - If both `users` and `usersFile` are provided, the two are merged. The contents of `usersFile` have precedence over the values in `users`. + - For security reasons, the field `users` doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead. + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.digestauth.users=test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + digestAuth: + secret: authsecret + +--- +apiVersion: v1 +kind: Secret +metadata: + name: authsecret + namespace: default + +data: + users: |2 + dGVzdDokYXByMSRINnVza2trVyRJZ1hMUDZld1RyU3VCa1RycUU4d2ovCnRlc3QyOiRhcHIxJGQ5 + aHI5SEJCJDRIeHdnVWlyM0hQNEVzZ2dQL1FObzAK ``` ```json tab="Marathon" @@ -57,26 +131,69 @@ http: - "test2:traefik:518845800f9e2bfb1f1f740ec24f074e" ``` -!!! tip - - Use `htdigest` to generate passwords. - -## Configuration Options - -### `users` - -The `users` option is an array of authorized users. Each user will be declared using the `name:realm:encoded-password` format. - -!!! Note - - If both `users` and `usersFile` are provided, the two are merged. The content of `usersFile` has precedence over `users`. - ### `usersFile` The `usersFile` option is the path to an external file that contains the authorized users for the middleware. The file content is a list of `name:realm:encoded-password`. +!!! Note + + - If both `users` and `usersFile` are provided, the two are merged. The contents of `usersFile` have precedence over the values in `users`. + - Because it does not make much sense to refer to a file path on Kubernetes, the `usersFile` field doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead. + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.digestauth.usersfile=/path/to/my/usersfile" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + digestAuth: + secret: authsecret + +--- +apiVersion: v1 +kind: Secret +metadata: + name: authsecret + namespace: default + +data: + users: |2 + dGVzdDokYXByMSRINnVza2trVyRJZ1hMUDZld1RyU3VCa1RycUU4d2ovCnRlc3QyOiRhcHIxJGQ5 + aHI5SEJCJDRIeHdnVWlyM0hQNEVzZ2dQL1FObzAK +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.digestauth.usersfile": "/path/to/my/usersfile" +} +``` + +```yaml tab="Rancher" +labels: + - "traefik.http.middlewares.test-auth.digestauth.usersfile=/path/to/my/usersfile" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.digestAuth] + usersFile = "/path/to/my/usersfile" +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + digestAuth: + usersFile: "/path/to/my/usersfile" +``` + ??? example "A file containing test/test and test2/test2" ```txt @@ -84,20 +201,54 @@ The file content is a list of `name:realm:encoded-password`. test2:traefik:518845800f9e2bfb1f1f740ec24f074e ``` -!!! Note - - If both `users` and `usersFile` are provided, the two are merged. The content of `usersFile` has precedence over `users`. - ### `realm` You can customize the realm for the authentication with the `realm` option. The default value is `traefik`. +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.digestauth.realm=MyRealm" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + digestAuth: + realm: MyRealm +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.digestauth.realm": "MyRealm" +} +``` + +```yaml tab="Rancher" +labels: + - "traefik.http.middlewares.test-auth.digestauth.realm=MyRealm" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.digestAuth] + realm = "MyRealm" +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + digestAuth: + realm: "MyRealm" +``` + ### `headerField` You can customize the header field for the authenticated user using the `headerField`option. -Example "File -- Passing Authenticated User to Services Via Headers" - ```yaml tab="Docker" labels: - "traefik.http.middlewares.my-auth.digestauth.headerField=X-WebAuth-User" @@ -143,3 +294,43 @@ http: ### `removeHeader` Set the `removeHeader` option to `true` to remove the authorization header before forwarding the request to your service. (Default value is `false`.) + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.digestauth.removeheader=true" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + digestAuth: + removeHeader: true +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.digestauth.removeheader": "true" +} +``` + +```yaml tab="Rancher" +labels: + - "traefik.http.middlewares.test-auth.digestauth.removeheader=true" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.digestAuth] + removeHeader = true +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + digestAuth: + removeHeader: true +``` diff --git a/docs/content/middlewares/errorpages.md b/docs/content/middlewares/errorpages.md index 29ee280ba..f1a56dd2c 100644 --- a/docs/content/middlewares/errorpages.md +++ b/docs/content/middlewares/errorpages.md @@ -29,8 +29,10 @@ spec: errors: status: - 500-599 - service: serviceError query: /{status}.html + service: + name: whoami + port: 80 ``` ```json tab="Marathon" @@ -77,7 +79,7 @@ http: ``` !!! note - In this example, the error page URL is based on the status code (`query=/{status}.html)`. + In this example, the error page URL is based on the status code (`query=/{status}.html`). ## Configuration Options @@ -95,6 +97,9 @@ The status code ranges are inclusive (`500-599` will trigger with every code bet The service that will serve the new requested error page. +!!! Note + In kubernetes, you need to reference a kubernetes service instead of a traefik service. + ### `query` The URL for the error page (hosted by `service`). You can use `{status}` in the query, that will be replaced by the received status code. diff --git a/docs/content/middlewares/forwardauth.md b/docs/content/middlewares/forwardauth.md index 29c02b7a8..9b9f7678e 100644 --- a/docs/content/middlewares/forwardauth.md +++ b/docs/content/middlewares/forwardauth.md @@ -15,12 +15,99 @@ Otherwise, the response from the authentication server is returned. # Forward authentication to authserver.com labels: - "traefik.http.middlewares.test-auth.forwardauth.address=https://authserver.com/auth" -- "traefik.http.middlewares.test-auth.forwardauth.authResponseHeaders=X-Auth-User, X-Secret" -- "traefik.http.middlewares.test-auth.forwardauth.tls.ca=path/to/local.crt" -- "traefik.http.middlewares.test-auth.forwardauth.tls.caOptional=true" -- "traefik.http.middlewares.test-auth.forwardauth.tls.cert=path/to/foo.cert" -- "traefik.http.middlewares.test-auth.forwardauth.tls.insecureSkipVerify=true" -- "traefik.http.middlewares.test-auth.forwardauth.tls.key=path/to/foo.key" +``` + +```yaml tab="Kubernetes" +# Forward authentication to authserver.com +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + forwardAuth: + address: https://authserver.com/auth +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.forwardauth.address": "https://authserver.com/auth" +} +``` + +```yaml tab="Rancher" +# Forward authentication to authserver.com +labels: +- "traefik.http.middlewares.test-auth.forwardauth.address=https://authserver.com/auth" +``` + +```toml tab="File (TOML)" +# Forward authentication to authserver.com +[http.middlewares] + [http.middlewares.test-auth.forwardAuth] + address = "https://authserver.com/auth" +``` + +```yaml tab="File (YAML)" +# Forward authentication to authserver.com +http: + middlewares: + test-auth: + forwardAuth: + address: "https://authserver.com/auth" +``` + +## Configuration Options + +### `address` + +The `address` option defines the authentication server address. + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.address=https://authserver.com/auth" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + forwardAuth: + address: https://authserver.com/auth +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.forwardauth.address": "https://authserver.com/auth" +} +``` + +```yaml tab="Rancher" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.address=https://authserver.com/auth" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.forwardAuth] + address = "https://authserver.com/auth" +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + forwardAuth: + address: "https://authserver.com/auth" +``` + +### `trustForwardHeader` + +Set the `trustForwardHeader` option to `true` to trust all the existing `X-Forwarded-*` headers. + +```yaml tab="Docker" +labels: - "traefik.http.middlewares.test-auth.forwardauth.trustForwardHeader=true" ``` @@ -33,89 +120,381 @@ spec: forwardAuth: address: https://authserver.com/auth trustForwardHeader: true - authResponseHeaders: - - X-Auth-User - - X-Secret - tls: - ca: path/to/local.crt - caOptional: true - cert: path/to/foo.cert - key: path/to/foo.key ``` ```json tab="Marathon" "labels": { - "traefik.http.middlewares.test-auth.forwardauth.address": "https://authserver.com/auth", - "traefik.http.middlewares.test-auth.forwardauth.authResponseHeaders": "X-Auth-User,X-Secret", - "traefik.http.middlewares.test-auth.forwardauth.tls.ca": "path/to/local.crt", - "traefik.http.middlewares.test-auth.forwardauth.tls.caOptional": "true", - "traefik.http.middlewares.test-auth.forwardauth.tls.cert": "path/to/foo.cert", - "traefik.http.middlewares.test-auth.forwardauth.tls.insecureSkipVerify": "true", - "traefik.http.middlewares.test-auth.forwardauth.tls.key": "path/to/foo.key", "traefik.http.middlewares.test-auth.forwardauth.trustForwardHeader": "true" } ``` ```yaml tab="Rancher" -# Forward authentication to authserver.com labels: -- "traefik.http.middlewares.test-auth.forwardauth.address=https://authserver.com/auth" -- "traefik.http.middlewares.test-auth.forwardauth.authResponseHeaders=X-Auth-User, X-Secret" -- "traefik.http.middlewares.test-auth.forwardauth.tls.ca=path/to/local.crt" -- "traefik.http.middlewares.test-auth.forwardauth.tls.caOptional=true" -- "traefik.http.middlewares.test-auth.forwardauth.tls.cert=path/to/foo.cert" -- "traefik.http.middlewares.test-auth.forwardauth.tls.InisecureSkipVerify=true" -- "traefik.http.middlewares.test-auth.forwardauth.tls.key=path/to/foo.key" - "traefik.http.middlewares.test-auth.forwardauth.trustForwardHeader=true" ``` ```toml tab="File (TOML)" -# Forward authentication to authserver.com [http.middlewares] [http.middlewares.test-auth.forwardAuth] address = "https://authserver.com/auth" trustForwardHeader = true - authResponseHeaders = ["X-Auth-User", "X-Secret"] - - [http.middlewares.test-auth.forwardAuth.tls] - ca = "path/to/local.crt" - caOptional = true - cert = "path/to/foo.cert" - key = "path/to/foo.key" ``` ```yaml tab="File (YAML)" -# Forward authentication to authserver.com http: middlewares: test-auth: forwardAuth: address: "https://authserver.com/auth" trustForwardHeader: true - authResponseHeaders: - - "X-Auth-User" - - "X-Secret" - tls: - ca: "path/to/local.crt" - caOptional: true - cert: "path/to/foo.cert" - key: "path/to/foo.key" ``` -## Configuration Options - -### `address` - -The `address` option defines the authentication server address. - -### `trustForwardHeader` - -Set the `trustForwardHeader` option to `true` to trust all the existing `X-Forwarded-*` headers. - ### `authResponseHeaders` The `authResponseHeaders` option is the list of the headers to copy from the authentication server to the request. +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.authResponseHeaders=X-Auth-User, X-Secret" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + forwardAuth: + address: https://authserver.com/auth + authResponseHeaders: + - X-Auth-User + - X-Secret +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.forwardauth.authResponseHeaders": "X-Auth-User,X-Secret" +} +``` + +```yaml tab="Rancher" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.authResponseHeaders=X-Auth-User, X-Secret" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.forwardAuth] + address = "https://authserver.com/auth" + authResponseHeaders = ["X-Auth-User", "X-Secret"] +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + forwardAuth: + address: "https://authserver.com/auth" + authResponseHeaders: + - "X-Auth-User" + - "X-Secret" +``` + ### `tls` The `tls` option is the TLS configuration from Traefik to the authentication server. + +#### `tls.ca` + +TODO add description. + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.tls.ca=path/to/local.crt" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + forwardAuth: + address: https://authserver.com/auth + tls: + caSecret: mycasercret + +--- +apiVersion: v1 +kind: Secret +metadata: + name: mycasercret + namespace: default + +data: + ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.forwardauth.tls.ca": "path/to/local.crt" +} +``` + +```yaml tab="Rancher" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.tls.ca=path/to/local.crt" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.forwardAuth] + address = "https://authserver.com/auth" + [http.middlewares.test-auth.forwardAuth.tls] + ca = "path/to/local.crt" +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + forwardAuth: + address: "https://authserver.com/auth" + tls: + ca: "path/to/local.crt" +``` + +#### `tls.caOptional` + +TODO add description. + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.tls.caOptional=true" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + forwardAuth: + address: https://authserver.com/auth + tls: + caOptional: true +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.forwardauth.tls.caOptional": "true" +} +``` + +```yaml tab="Rancher" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.tls.caOptional=true" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.forwardAuth] + address = "https://authserver.com/auth" + [http.middlewares.test-auth.forwardAuth.tls] + caOptional = true +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + forwardAuth: + address: "https://authserver.com/auth" + tls: + caOptional: true +``` + +#### `tls.cert` + +TODO add description. + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.tls.cert=path/to/foo.cert" +- "traefik.http.middlewares.test-auth.forwardauth.tls.key=path/to/foo.key" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + forwardAuth: + address: https://authserver.com/auth + tls: + certSecret: mytlscert + +--- +apiVersion: v1 +kind: Secret +metadata: + name: mytlscert + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.forwardauth.tls.cert": "path/to/foo.cert", + "traefik.http.middlewares.test-auth.forwardauth.tls.key": "path/to/foo.key" +} +``` + +```yaml tab="Rancher" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.tls.cert=path/to/foo.cert" +- "traefik.http.middlewares.test-auth.forwardauth.tls.key=path/to/foo.key" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.forwardAuth] + address = "https://authserver.com/auth" + [http.middlewares.test-auth.forwardAuth.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + forwardAuth: + address: "https://authserver.com/auth" + tls: + cert: "path/to/foo.cert" + key: "path/to/foo.key" +``` + +!!! Note + For security reasons, the field doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead. + +#### `tls.key` + +TODO add description. + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.tls.cert=path/to/foo.cert" +- "traefik.http.middlewares.test-auth.forwardauth.tls.key=path/to/foo.key" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + forwardAuth: + address: https://authserver.com/auth + tls: + certSecret: mytlscert + +--- +apiVersion: v1 +kind: Secret +metadata: + name: mytlscert + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.forwardauth.tls.cert": "path/to/foo.cert", + "traefik.http.middlewares.test-auth.forwardauth.tls.key": "path/to/foo.key" +} +``` + +```yaml tab="Rancher" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.tls.cert=path/to/foo.cert" +- "traefik.http.middlewares.test-auth.forwardauth.tls.key=path/to/foo.key" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.forwardAuth] + address = "https://authserver.com/auth" + [http.middlewares.test-auth.forwardAuth.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + forwardAuth: + address: "https://authserver.com/auth" + tls: + cert: "path/to/foo.cert" + key: "path/to/foo.key" +``` + +!!! Note + For security reasons, the field doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead. + +#### `tls.insecureSkipVerify` + +TODO add description. + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.tls.insecureSkipVerify=true" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + forwardAuth: + address: https://authserver.com/auth + insecureSkipVerify: true +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.forwardauth.tls.insecureSkipVerify": "true" +} +``` + +```yaml tab="Rancher" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.tls.InsecureSkipVerify=true" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.forwardAuth] + address = "https://authserver.com/auth" + insecureSkipVerify: true +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + forwardAuth: + address: "https://authserver.com/auth" + insecureSkipVerify: true +``` diff --git a/docs/content/middlewares/headers.md b/docs/content/middlewares/headers.md index a81be1a52..9ba57f386 100644 --- a/docs/content/middlewares/headers.md +++ b/docs/content/middlewares/headers.md @@ -70,7 +70,12 @@ http: `X-Script-Name` header added to the proxied request, the `X-Custom-Request-Header` header removed from the request, and the `X-Custom-Response-Header` header removed from the response. -Please note that is not possible to remove headers through the use of labels (Docker, Rancher, Marathon, ...) for now. +Please note that it is not possible to remove headers through the use of labels (Docker, Rancher, Marathon, ...) for now. + +```yaml tab="Docker" +labels: + - "traefik.http.middlewares.testheader.headers.customrequestheaders.X-Script-Name=test" +``` ```yaml tab="Kubernetes" apiVersion: traefik.containo.us/v1alpha1 @@ -86,17 +91,17 @@ spec: X-Custom-Response-Header: "" # Removes ``` -```yaml tab="Rancher" -labels: - - "traefik.http.middlewares.testheader.headers.customrequestheaders.X-Script-Name=test" -``` - ```json tab="Marathon" "labels": { "traefik.http.middlewares.testheader.headers.customrequestheaders.X-Script-Name": "test", } ``` +```yaml tab="Rancher" +labels: + - "traefik.http.middlewares.testheader.headers.customrequestheaders.X-Script-Name=test" +``` + ```toml tab="File (TOML)" [http.middlewares] [http.middlewares.testHeader.headers] @@ -121,7 +126,7 @@ http: ### Using Security Headers -Security related headers (HSTS headers, SSL redirection, Browser XSS filter, etc) can be added and configured per frontend in a similar manner to the custom headers above. +Security related headers (HSTS headers, SSL redirection, Browser XSS filter, etc) can be added and configured in a manner similar to the custom headers above. This functionality allows for some easy security features to quickly be set. ```yaml tab="Docker" @@ -141,12 +146,6 @@ spec: sslRedirect: "true" ``` -```yaml tab="Rancher" -labels: - - "traefik.http.middlewares.testheader.headers.framedeny=true" - - "traefik.http.middlewares.testheader.headers.sslredirect=true" -``` - ```json tab="Marathon" "labels": { "traefik.http.middlewares.testheader.headers.framedeny": "true", @@ -154,6 +153,12 @@ labels: } ``` +```yaml tab="Rancher" +labels: + - "traefik.http.middlewares.testheader.headers.framedeny=true" + - "traefik.http.middlewares.testheader.headers.sslredirect=true" +``` + ```toml tab="File (TOML)" [http.middlewares] [http.middlewares.testHeader.headers] @@ -172,7 +177,7 @@ http: ### CORS Headers -CORS (Cross-Origin Resource Sharing) headers can be added and configured per frontend in a similar manner to the custom headers above. +CORS (Cross-Origin Resource Sharing) headers can be added and configured in a manner similar to the custom headers above. This functionality allows for more advanced security features to quickly be set. ```yaml tab="Docker" @@ -199,14 +204,6 @@ spec: addVaryHeader: "true" ``` -```yaml tab="Rancher" -labels: - - "traefik.http.middlewares.testheader.headers.accesscontrolallowmethods=GET,OPTIONS,PUT" - - "traefik.http.middlewares.testheader.headers.accesscontrolalloworigin=origin-list-or-null" - - "traefik.http.middlewares.testheader.headers.accesscontrolmaxage=100" - - "traefik.http.middlewares.testheader.headers.addvaryheader=true" -``` - ```json tab="Marathon" "labels": { "traefik.http.middlewares.testheader.headers.accesscontrolallowmethods": "GET,OPTIONS,PUT", @@ -216,6 +213,14 @@ labels: } ``` +```yaml tab="Rancher" +labels: + - "traefik.http.middlewares.testheader.headers.accesscontrolallowmethods=GET,OPTIONS,PUT" + - "traefik.http.middlewares.testheader.headers.accesscontrolalloworigin=origin-list-or-null" + - "traefik.http.middlewares.testheader.headers.accesscontrolmaxage=100" + - "traefik.http.middlewares.testheader.headers.addvaryheader=true" +``` + ```toml tab="File (TOML)" [http.middlewares] [http.middlewares.testHeader.headers] diff --git a/docs/content/middlewares/inflightreq.md b/docs/content/middlewares/inflightreq.md index 9b07b7a82..770a3463d 100644 --- a/docs/content/middlewares/inflightreq.md +++ b/docs/content/middlewares/inflightreq.md @@ -74,7 +74,7 @@ The `ipStrategy` option defines two parameters that sets how Traefik will determ The `depth` option tells Traefik to use the `X-Forwarded-For` header and take the IP located at the `depth` position (starting from the right). - If `depth` is greater than the total number of IPs in `X-Forwarded-For`, then the client IP will be empty. -- `depth` is ignored if its value is is lesser than or equal to 0. +- `depth` is ignored if its value is lesser than or equal to 0. !!! note "Example of Depth & X-Forwarded-For" diff --git a/docs/content/middlewares/ipwhitelist.md b/docs/content/middlewares/ipwhitelist.md index 869ba0af8..1594c13cc 100644 --- a/docs/content/middlewares/ipwhitelist.md +++ b/docs/content/middlewares/ipwhitelist.md @@ -144,7 +144,7 @@ The `depth` option tells Traefik to use the `X-Forwarded-For` header and take th !!! note - If `depth` is greater than the total number of IPs in `X-Forwarded-For`, then the client IP will be empty. - - `depth` is ignored if its value is is lesser than or equal to 0. + - `depth` is ignored if its value is lesser than or equal to 0. #### `ipStrategy.excludedIPs` diff --git a/docs/content/middlewares/overview.md b/docs/content/middlewares/overview.md index a69ccddca..fc90847dd 100644 --- a/docs/content/middlewares/overview.md +++ b/docs/content/middlewares/overview.md @@ -66,7 +66,7 @@ spec: ```json tab="Marathon" "labels": { "traefik.http.middlewares.foo-add-prefix.addprefix.prefix": "/foo", - "traefik.http.router.router1.middlewares": "foo-add-prefix@marathon" + "traefik.http.routers.router1.middlewares": "foo-add-prefix@marathon" } ``` @@ -76,7 +76,7 @@ labels: # Create a middleware named `foo-add-prefix` - "traefik.http.middlewares.foo-add-prefix.addprefix.prefix=/foo" # Apply the middleware named `foo-add-prefix` to the router named `router1` - - "traefik.http.router.router1.middlewares=foo-add-prefix@rancher" + - "traefik.http.routers.router1.middlewares=foo-add-prefix@rancher" ``` ```toml tab="File (TOML)" diff --git a/docs/content/middlewares/passtlsclientcert.md b/docs/content/middlewares/passtlsclientcert.md index 46b6aec36..ff53b05cc 100644 --- a/docs/content/middlewares/passtlsclientcert.md +++ b/docs/content/middlewares/passtlsclientcert.md @@ -3,7 +3,9 @@ Adding Client Certificates in a Header {: .subtitle } -`TODO add schema` + PassTLSClientCert adds in header the selected data from the passed client tls certificate. @@ -219,7 +221,7 @@ PassTLSClientCert can add two headers to the request: !!! note The headers are filled with escaped string so it can be safely placed inside a URL query. -In the following example, you can see a complete certificate. We will use each part of it to explains the middleware options. +In the following example, you can see a complete certificate. We will use each part of it to explain the middleware options. ??? example "A complete client tls certificate" diff --git a/docs/content/middlewares/ratelimit.md b/docs/content/middlewares/ratelimit.md index 058c67b27..fcd0d4eb4 100644 --- a/docs/content/middlewares/ratelimit.md +++ b/docs/content/middlewares/ratelimit.md @@ -3,8 +3,6 @@ To Control the Number of Requests Going to a Service {: .subtitle } -![RateLimit](../assets/img/middleware/ratelimit.png) - The RateLimit middleware ensures that services will receive a _fair_ number of requests, and allows you define what is fair. ## Configuration Example @@ -173,7 +171,7 @@ The `ipStrategy` option defines two parameters that sets how Traefik will determ The `depth` option tells Traefik to use the `X-Forwarded-For` header and take the IP located at the `depth` position (starting from the right). - If `depth` is greater than the total number of IPs in `X-Forwarded-For`, then the client IP will be empty. -- `depth` is ignored if its value is is lesser than or equal to 0. +- `depth` is ignored if its value is lesser than or equal to 0. !!! note "Example of Depth & X-Forwarded-For" diff --git a/docs/content/middlewares/redirectregex.md b/docs/content/middlewares/redirectregex.md index 2d1a19d76..6dd1ae3cf 100644 --- a/docs/content/middlewares/redirectregex.md +++ b/docs/content/middlewares/redirectregex.md @@ -3,7 +3,9 @@ Redirecting the Client to a Different Location {: .subtitle } -`TODO: add schema` + RegexRedirect redirect a request from an url to another with regex matching and replacement. @@ -11,6 +13,7 @@ RegexRedirect redirect a request from an url to another with regex matching and ```yaml tab="Docker" # Redirect with domain replacement +# Note: all dollar signs need to be doubled for escaping. labels: - "traefik.http.middlewares.test-redirectregex.redirectregex.regex=^http://localhost/(.*)" - "traefik.http.middlewares.test-redirectregex.redirectregex.replacement=http://mydomain/$${1}" @@ -37,9 +40,10 @@ spec: ```yaml tab="Rancher" # Redirect with domain replacement +# Note: all dollar signs need to be doubled for escaping. labels: - "traefik.http.middlewares.test-redirectregex.redirectregex.regex=^http://localhost/(.*)" -- "traefik.http.middlewares.test-redirectregex.redirectregex.replacement=http://mydomain/${1}" +- "traefik.http.middlewares.test-redirectregex.redirectregex.replacement=http://mydomain/$${1}" ``` ```toml tab="File (TOML)" diff --git a/docs/content/middlewares/redirectscheme.md b/docs/content/middlewares/redirectscheme.md index 65a01c823..f4af47a16 100644 --- a/docs/content/middlewares/redirectscheme.md +++ b/docs/content/middlewares/redirectscheme.md @@ -3,7 +3,9 @@ Redirecting the Client to a Different Scheme/Port {: .subtitle } -`TODO: add schema` + RegexRedirect redirect request from a scheme to another. diff --git a/docs/content/middlewares/replacepath.md b/docs/content/middlewares/replacepath.md index b491c8033..f2e03d552 100644 --- a/docs/content/middlewares/replacepath.md +++ b/docs/content/middlewares/replacepath.md @@ -3,7 +3,9 @@ Updating the Path Before Forwarding the Request {: .subtitle } -`TODO: add schema` + Replace the path of the request url. diff --git a/docs/content/middlewares/replacepathregex.md b/docs/content/middlewares/replacepathregex.md index fbdb47f0d..a58e302a9 100644 --- a/docs/content/middlewares/replacepathregex.md +++ b/docs/content/middlewares/replacepathregex.md @@ -3,7 +3,9 @@ Updating the Path Before Forwarding the Request (Using a Regex) {: .subtitle } -`TODO: add schema` + The ReplaceRegex replace a path from an url to another with regex matching and replacement. diff --git a/docs/content/middlewares/retry.md b/docs/content/middlewares/retry.md index 439cb9cbf..b61b362f2 100644 --- a/docs/content/middlewares/retry.md +++ b/docs/content/middlewares/retry.md @@ -3,9 +3,12 @@ Retrying until it Succeeds {: .subtitle } -`TODO: add schema` + -Retry to send request on attempt failure. +The Retry middleware is in charge of reissuing a request a given number of times to a backend server if that server does not reply. +To be clear, as soon as the server answers, the middleware stops retrying, regardless of the response status. ## Configuration Examples @@ -60,4 +63,4 @@ http: _mandatory_ -The `attempts` option defines how many times to try sending the request. \ No newline at end of file +The `attempts` option defines how many times the request should be retried. \ No newline at end of file diff --git a/docs/content/middlewares/stripprefix.md b/docs/content/middlewares/stripprefix.md index 8b2fe0ab2..2b3515ccb 100644 --- a/docs/content/middlewares/stripprefix.md +++ b/docs/content/middlewares/stripprefix.md @@ -3,7 +3,9 @@ Removing Prefixes From the Path Before Forwarding the Request {: .subtitle } -`TODO: add schema` + Remove the specified prefixes from the URL path. @@ -12,7 +14,7 @@ Remove the specified prefixes from the URL path. ```yaml tab="Docker" # Strip prefix /foobar and /fiibar labels: -- "traefik.http.middlewares.test-stripprefix.stripprefix.prefixes=/foobar, /fiibar" +- "traefik.http.middlewares.test-stripprefix.stripprefix.prefixes=/foobar,/fiibar" ``` ```yaml tab="Kubernetes" @@ -30,14 +32,14 @@ spec: ```json tab="Marathon" "labels": { - "traefik.http.middlewares.test-stripprefix.stripprefix.prefixes": "/foobar, /fiibar" + "traefik.http.middlewares.test-stripprefix.stripprefix.prefixes": "/foobar,/fiibar" } ``` ```yaml tab="Rancher" # Strip prefix /foobar and /fiibar labels: -- "traefik.http.middlewares.test-stripprefix.stripprefix.prefixes=/foobar, /fiibar" +- "traefik.http.middlewares.test-stripprefix.stripprefix.prefixes=/foobar,/fiibar" ``` ```toml tab="File (TOML)" diff --git a/docs/content/middlewares/stripprefixregex.md b/docs/content/middlewares/stripprefixregex.md index f6a6451aa..f73184650 100644 --- a/docs/content/middlewares/stripprefixregex.md +++ b/docs/content/middlewares/stripprefixregex.md @@ -3,55 +3,50 @@ Removing Prefixes From the Path Before Forwarding the Request (Using a Regex) {: .subtitle } -`TODO: add schema` - Remove the matching prefixes from the URL path. ## Configuration Examples ```yaml tab="Docker" -# Replace the path by /foo labels: -- "traefik.http.middlewares.test-stripprefixregex.stripprefixregex.regex=^/foo/(.*)", +- "traefik.http.middlewares.test-stripprefixregex.stripprefixregex.regex=/foo/[a-z0-9]+/[0-9]+/" ``` ```yaml tab="Kubernetes" -# Replace the path by /foo apiVersion: traefik.containo.us/v1alpha1 kind: Middleware metadata: name: test-stripprefixregex spec: stripPrefixRegex: - regex: "^/foo/(.*)" + regex: + - "/foo/[a-z0-9]+/[0-9]+/" ``` ```json tab="Marathon" "labels": { - "traefik.http.middlewares.test-stripprefixregex.stripprefixregex.regex": "^/foo/(.*)" + "traefik.http.middlewares.test-stripprefixregex.stripprefixregex.regex": "/foo/[a-z0-9]+/[0-9]+/" } ``` ```yaml tab="Rancher" -# Replace the path by /foo labels: -- "traefik.http.middlewares.test-stripprefixregex.stripprefixregex.regex=^/foo/(.*)", +- "traefik.http.middlewares.test-stripprefixregex.stripprefixregex.regex=/foo/[a-z0-9]+/[0-9]+/" ``` ```toml tab="File (TOML)" -# Replace the path by /foo [http.middlewares] [http.middlewares.test-stripprefixregex.stripPrefixRegex] - regex = "^/foo/(.*)" + regex = ["/foo/[a-z0-9]+/[0-9]+/"] ``` ```yaml tab="File (YAML)" -# Replace the path by /foo http: middlewares: test-stripprefixregex: stripPrefixRegex: - regex: "^/foo/(.*)" + regex: + - "/foo/[a-z0-9]+/[0-9]+/" ``` ## Configuration Options diff --git a/docs/content/migration/v1-to-v2.md b/docs/content/migration/v1-to-v2.md new file mode 100644 index 000000000..ba0742c0f --- /dev/null +++ b/docs/content/migration/v1-to-v2.md @@ -0,0 +1,353 @@ +# Migration Guide: From v1 to v2 + +How to Migrate from Traefik v1 to Traefik v2. +{: .subtitle } + +The version 2 of Traefik introduces a number of breaking changes, +which require one to update their configuration when they migrate from v1 to v2. +The goal of this page is to recapitulate all of these changes, and in particular to give examples, +feature by feature, of how the configuration looked like in v1, and how it now looks like in v2. + +!!! Note "Migration Helper" + + We created a tool to help during the migration: [traefik-migration-tool](https://github.com/containous/traefik-migration-tool) + + This tool allows to: + + - convert `Ingress` to Traefik `IngressRoute` resources. + - convert `acme.json` file from v1 to v2 format. + +## Frontends and Backends Are Dead... ... Long Live Routers, Middlewares, and Services + +During the transition from v1 to v2, a number of internal pieces and components of Traefik were rewritten and reorganized. +As such, the combination of core notions such as frontends and backends has been replaced with the combination of routers, services, and middlewares. + +Typically, a router replaces a frontend, and a service assumes the role of a backend, with each router referring to a service. +However, even though a backend was in charge of applying any desired modification on the fly to the incoming request, +the router defers that responsibility to another component. +Instead, a dedicated middleware is now defined for each kind of such modification. +Then any router can refer to an instance of the wanted middleware. + +!!! example "One frontend with basic auth and one backend, become one router, one service, and one basic auth middleware." + + ### v1 + + ```yaml tab="Docker" + labels: + - "traefik.frontend.rule=Host:test.localhost;PathPrefix:/test" + - "traefik.frontend.auth.basic.users=test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" + ``` + + ```yaml tab="K8s Ingress" + apiVersion: extensions/v1beta1 + kind: Ingress + metadata: + name: traefik + namespace: kube-system + annotations: + kubernetes.io/ingress.class: traefik + traefik.ingress.kubernetes.io/rule-type: PathPrefix + spec: + rules: + - host: test.locahost + http: + paths: + - path: /test + backend: + serviceName: server0 + servicePort: 80 + - path: /test + backend: + serviceName: server1 + servicePort: 80 + ``` + + ```toml tab="File (TOML)" + [frontends] + [frontends.frontend1] + entryPoints = ["http"] + backend = "backend1" + + [frontends.frontend1.routes] + [frontends.frontend1.routes.route0] + rule = "Host:test.localhost" + [frontends.frontend1.routes.route0] + rule = "PathPrefix:/test" + + [frontends.frontend1.auth] + [frontends.frontend1.auth.basic] + users = [ + "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", + "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + ] + + [backends] + [backends.backend1] + [backends.backend1.servers.server0] + url = "http://10.10.10.1:80" + [backends.backend1.servers.server1] + url = "http://10.10.10.2:80" + + [backends.backend1.loadBalancer] + method = "wrr" + ``` + + ### v2 + + ```yaml tab="Docker" + labels: + - "traefik.http.routers.router0.rule=Host(`bar.com`) && PathPrefix(`/test`)" + - "traefik.http.routers.router0.middlewares=auth" + - "traefik.http.middlewares.auth.basicauth.users=test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" + ``` + + ```yaml tab="K8s IngressRoute" + # The definitions below require the definitions for the Middleware and IngressRoute kinds. + # https://docs.traefik.io/v2.0/providers/kubernetes-crd/#traefik-ingressroute-definition + apiVersion: traefik.containo.us/v1alpha1 + kind: Middleware + metadata: + name: basicauth + namespace: foo + + spec: + basicAuth: + users: + - test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/ + - test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 + + --- + apiVersion: traefik.containo.us/v1alpha1 + kind: IngressRoute + metadata: + name: ingressroutebar + + spec: + entryPoints: + - http + routes: + - match: Host(`test.localhost`) && PathPrefix(`/test`) + kind: Rule + services: + - name: server0 + port: 80 + - name: server1 + port: 80 + middlewares: + - name: basicauth + namespace: foo + ``` + + ```toml tab="File (TOML)" + [http.routers] + [http.routers.router0] + rule = "Host(`test.localhost`) && PathPrefix(`/test`)" + middlewares = ["auth"] + service = "my-service" + + [http.services] + [[http.services.my-service.loadBalancer.servers]] + url = "http://10.10.10.1:80" + [[http.services.my-service.loadBalancer.servers]] + url = "http://10.10.10.2:80" + + [http.middlewares] + [http.middlewares.auth.basicAuth] + users = [ + "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", + "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + ] + ``` + + ```yaml tab="File (YAML)" + http: + routers: + router0: + rule: "Host(`test.localhost`) && PathPrefix(`/test`)" + service: my-service + middlewares: + - auth + + services: + my-service: + loadBalancer: + servers: + - url: http://10.10.10.1:80 + - url: http://10.10.10.2:80 + + middlewares: + auth: + basicAuth: + users: + - "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/" + - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" + ``` + +## TLS configuration is now dynamic, per router. + +TLS parameters used to be specified in the static configuration, as an entryPoint field. +With Traefik v2, a new dynamic TLS section at the root contains all the desired TLS configurations. +Then, a router's TLS field can refer to one of the TLS configurations defined at the root, hence defining the TLS configuration for that router. + +!!! example "TLS on web-secure entryPoint becomes TLS option on Router-1" + + ### v1 + + ```toml tab="File (TOML)" + # static configuration + [entryPoints] + [entryPoints.web-secure] + address = ":443" + + [entryPoints.web-secure.tls] + minVersion = "VersionTLS12" + cipherSuites = [ + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384" + ] + [[entryPoints.web-secure.tls.certificates]] + certFile = "path/to/my.cert" + keyFile = "path/to/my.key" + ``` + + ```bash tab="CLI" + --entryPoints='Name:web-secure Address::443 TLS:path/to/my.cert,path/to/my.key TLS.MinVersion:VersionTLS12 TLS.CipherSuites:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384' + ``` + + ### v2 + + ```toml tab="File (TOML)" + # dynamic configuration + [http.routers] + [http.routers.Router-1] + rule = "Host(`bar.com`)" + service = "service-id" + # will terminate the TLS request + [http.routers.Router-1.tls] + options = "myTLSOptions" + + [[tls.certificates]] + certFile = "/path/to/domain.cert" + keyFile = "/path/to/domain.key" + + [tls.options] + [tls.options.default] + minVersion = "VersionTLS12" + + [tls.options.myTLSOptions] + minVersion = "VersionTLS13" + cipherSuites = [ + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384" + ] + ``` + + ```yaml tab="File (YAML)" + http: + routers: + Router-1: + rule: "Host(`bar.com`)" + service: service-id + # will terminate the TLS request + tls: + options: myTLSOptions + + tls: + certificates: + - certFile: /path/to/domain.cert + keyFile: /path/to/domain.key + options: + myTLSOptions: + minVersion: VersionTLS13 + cipherSuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + ``` + + ```yaml tab="K8s IngressRoute" + # The definitions below require the definitions for the TLSOption and IngressRoute kinds. + # https://docs.traefik.io/v2.0/providers/kubernetes-crd/#traefik-ingressroute-definition + apiVersion: traefik.containo.us/v1alpha1 + kind: TLSOption + metadata: + name: mytlsoption + namespace: default + + spec: + minVersion: VersionTLS13 + cipherSuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + + --- + apiVersion: traefik.containo.us/v1alpha1 + kind: IngressRoute + metadata: + name: ingressroutebar + + spec: + entryPoints: + - web + routes: + - match: Host(`bar.com`) + kind: Rule + services: + - name: whoami + port: 80 + tls: + options: + name: mytlsoption + namespace: default + ``` + + ```yaml tab="Docker" + labels: + # myTLSOptions must be defined by another provider, in this instance in the File Provider. + # see the cross provider section + - "traefik.http.routers.router0.tls.options=myTLSOptions@file" + ``` + +## HTTP -> HTTPS Redirection + + TODO + +## ACME (let's encrypt) + + TODO + +## Traefik Logs + + TODO + +## Tracing + + TODO + +## Metrics + + TODO + +## No more root level key/values + + TODO + +## Providers + +Supported providers, for now: + +- [ ] Azure Service Fabric +- [ ] BoltDB +- [ ] Consul +- [ ] Consul Catalog +- [x] Docker +- [ ] DynamoDB +- [ ] ECS +- [ ] Etcd +- [ ] Eureka +- [x] File +- [x] Kubernetes Ingress (without annotations) +- [x] Kubernetes IngressRoute +- [x] Marathon +- [ ] Mesos +- [x] Rest +- [ ] Zookeeper diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md index 00a29cf79..5e6551a51 100644 --- a/docs/content/observability/access-logs.md +++ b/docs/content/observability/access-logs.md @@ -26,12 +26,11 @@ accessLog: {} By default access logs are written to the standard output. To write the logs into a log file, use the `filePath` option. -in the Common Log Format (CLF), extended with additional fields. - ### `format` By default, logs are written using the Common Log Format (CLF). To write logs in JSON, use `json` in the `format` option. +If the given format is unsupported, the default (CLF) is used instead. !!! note "Common Log Format" @@ -152,15 +151,14 @@ accessLog: format: json fields: defaultMode: keep - fields: + names: + ClientUsername: drop + headers: + defaultMode: keep names: - ClientUsername: drop - headers: - defaultMode: keep - names: - - User-Agent: redact - - Authorization: drop - - Content-Type: keep + User-Agent: redact + Authorization: drop + Content-Type: keep ``` ```bash tab="CLI" diff --git a/docs/content/observability/metrics/datadog.md b/docs/content/observability/metrics/datadog.md index 00af98c58..cb412a383 100644 --- a/docs/content/observability/metrics/datadog.md +++ b/docs/content/observability/metrics/datadog.md @@ -1,15 +1,15 @@ -# DataDog +# Datadog -To enable the DataDog: +To enable the Datadog: ```toml tab="File (TOML)" [metrics] - [metrics.dataDog] + [metrics.datadog] ``` ```yaml tab="File (YAML)" metrics: - dataDog: {} + datadog: {} ``` ```bash tab="CLI" @@ -24,13 +24,13 @@ Address instructs exporter to send metrics to datadog-agent at this address. ```toml tab="File (TOML)" [metrics] - [metrics.dataDog] + [metrics.datadog] address = "127.0.0.1:8125" ``` ```yaml tab="File (YAML)" metrics: - dataDog: + datadog: address: 127.0.0.1:8125 ``` @@ -46,13 +46,13 @@ Enable metrics on entry points. ```toml tab="File (TOML)" [metrics] - [metrics.dataDog] + [metrics.datadog] addEntryPointsLabels = true ``` ```yaml tab="File (YAML)" metrics: - dataDog: + datadog: addEntryPointsLabels: true ``` @@ -68,13 +68,13 @@ Enable metrics on services. ```toml tab="File (TOML)" [metrics] - [metrics.dataDog] + [metrics.datadog] addServicesLabels = true ``` ```yaml tab="File (YAML)" metrics: - dataDog: + datadog: addServicesLabels: true ``` @@ -90,13 +90,13 @@ The interval used by the exporter to push metrics to datadog-agent. ```toml tab="File (TOML)" [metrics] - [metrics.dataDog] + [metrics.datadog] pushInterval = 10s ``` ```yaml tab="File (YAML)" metrics: - dataDog: + datadog: pushInterval: 10s ``` diff --git a/docs/content/observability/metrics/overview.md b/docs/content/observability/metrics/overview.md index f7d148fbf..07fbb9667 100644 --- a/docs/content/observability/metrics/overview.md +++ b/docs/content/observability/metrics/overview.md @@ -4,7 +4,7 @@ Metrics system Traefik supports 4 metrics backends: -- [DataDog](./datadog.md) +- [Datadog](./datadog.md) - [InfluxDB](./influxdb.md) - [Prometheus](./prometheus.md) - [StatsD](./statsd.md) diff --git a/docs/content/observability/metrics/prometheus.md b/docs/content/observability/metrics/prometheus.md index ac8a43dc3..13c3b17b8 100644 --- a/docs/content/observability/metrics/prometheus.md +++ b/docs/content/observability/metrics/prometheus.md @@ -85,3 +85,34 @@ metrics: ```bash tab="CLI" --metrics.prometheus.addServicesLabels=true ``` + +#### `entryPoint` + +_Optional, Default=traefik_ + +Entry point used to expose metrics. + +```toml tab="File (TOML)" +[entryPoints] + [entryPoints.metrics] + address = ":8082" + +[metrics] + [metrics.prometheus] + entryPoint = "metrics" +``` + +```yaml tab="File (YAML)" +entryPoints: + metrics: + address: ":8082" + +metrics: + prometheus: + entryPoint: metrics +``` + +```bash tab="CLI" +--entryPoints.metrics.address=":8082" +--metrics.prometheus..entryPoint="metrics" +``` diff --git a/docs/content/observability/tracing/datadog.md b/docs/content/observability/tracing/datadog.md index 5f3ed46b5..844f4fc70 100644 --- a/docs/content/observability/tracing/datadog.md +++ b/docs/content/observability/tracing/datadog.md @@ -1,15 +1,15 @@ -# DataDog +# Datadog -To enable the DataDog: +To enable the Datadog: ```toml tab="File (TOML)" [tracing] - [tracing.dataDog] + [tracing.datadog] ``` ```yaml tab="File (YAML)" tracing: - dataDog: {} + datadog: {} ``` ```bash tab="CLI" @@ -24,13 +24,13 @@ Local Agent Host Port instructs reporter to send spans to datadog-tracing-agent ```toml tab="File (TOML)" [tracing] - [tracing.dataDog] + [tracing.datadog] localAgentHostPort = "127.0.0.1:8126" ``` ```yaml tab="File (YAML)" tracing: - dataDog: + datadog: localAgentHostPort: 127.0.0.1:8126 ``` @@ -42,17 +42,17 @@ tracing: _Optional, Default=false_ -Enable DataDog debug. +Enable Datadog debug. ```toml tab="File (TOML)" [tracing] - [tracing.dataDog] + [tracing.datadog] debug = true ``` ```yaml tab="File (YAML)" tracing: - dataDog: + datadog: debug: true ``` @@ -68,13 +68,13 @@ Apply shared tag in a form of Key:Value to all the traces. ```toml tab="File (TOML)" [tracing] - [tracing.dataDog] + [tracing.datadog] globalTag = "sample" ``` ```yaml tab="File (YAML)" tracing: - dataDog: + datadog: globalTag: sample ``` @@ -91,13 +91,13 @@ this option must be enabled in order to get all the parts of a distributed trace ```toml tab="File (TOML)" [tracing] - [tracing.dataDog] + [tracing.datadog] prioritySampling = true ``` ```yaml tab="File (YAML)" tracing: - dataDog: + datadog: prioritySampling: true ``` diff --git a/docs/content/observability/tracing/overview.md b/docs/content/observability/tracing/overview.md index 543f0a354..a6cb8dbd8 100644 --- a/docs/content/observability/tracing/overview.md +++ b/docs/content/observability/tracing/overview.md @@ -11,7 +11,7 @@ Traefik supports five tracing backends: - [Jaeger](./jaeger.md) - [Zipkin](./zipkin.md) -- [DataDog](./datadog.md) +- [Datadog](./datadog.md) - [Instana](./instana.md) - [Haystack](./haystack.md) diff --git a/docs/content/observability/tracing/zipkin.md b/docs/content/observability/tracing/zipkin.md index 101040566..8aa1c0283 100644 --- a/docs/content/observability/tracing/zipkin.md +++ b/docs/content/observability/tracing/zipkin.md @@ -18,46 +18,24 @@ tracing: #### `httpEndpoint` -_Required, Default="http://localhost:9411/api/v1/spans"_ +_Required, Default="http://localhost:9411/api/v2/spans"_ Zipkin HTTP endpoint used to send data. ```toml tab="File (TOML)" [tracing] [tracing.zipkin] - httpEndpoint = "http://localhost:9411/api/v1/spans" + httpEndpoint = "http://localhost:9411/api/v2/spans" ``` ```yaml tab="File (YAML)" tracing: zipkin: - httpEndpoint: http://localhost:9411/api/v1/spans + httpEndpoint: http://localhost:9411/api/v2/spans ``` ```bash tab="CLI" ---tracing.zipkin.httpEndpoint="http://localhost:9411/api/v1/spans" -``` - -#### `debug` - -_Optional, Default=false_ - -Enable Zipkin debug. - -```toml tab="File (TOML)" -[tracing] - [tracing.zipkin] - debug = true -``` - -```yaml tab="File (YAML)" -tracing: - zipkin: - debug: true -``` - -```bash tab="CLI" ---tracing.zipkin.debug=true +--tracing.zipkin.httpEndpoint="http://localhost:9411/api/v2/spans" ``` #### `sameSpan` @@ -86,7 +64,7 @@ tracing: _Optional, Default=true_ -Use Zipkin 128 bit root span IDs. +Use Zipkin 128 bit trace IDs. ```toml tab="File (TOML)" [tracing] @@ -124,4 +102,4 @@ tracing: ```bash tab="CLI" --tracing.zipkin.sampleRate="0.2" -``` \ No newline at end of file +``` diff --git a/docs/content/operations/api.md b/docs/content/operations/api.md index 1cae400b5..61141ad3d 100644 --- a/docs/content/operations/api.md +++ b/docs/content/operations/api.md @@ -1,8 +1,5 @@ # API -!!! important - In the beta version, you can't configure middlewares (basic authentication or white listing) anymore, but as security is important, this will change before the RC version. - Traefik exposes a number of information through an API handler, such as the configuration of all routers, services, middlewares, etc. As with all features of Traefik, this handler can be enabled with the [static configuration](../getting-started/configuration-overview.md#the-static-configuration). @@ -22,11 +19,10 @@ would be to apply the following protection mechanisms: keeping it restricted to internal networks (as in the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege), applied to networks). -!!! important - In the beta version, you can't configure middlewares (basic authentication or white listing) anymore, but as security is important, this will change before the RC version. - ## Configuration +If you enable the API, a new special `service` named `api@internal` is created and then can be reference in a router. + To enable the API handler: ```toml tab="File (TOML)" @@ -41,6 +37,83 @@ api: {} --api=true ``` +And then you will able to reference it like this. + +```yaml tab="Docker" + - "traefik.http.routers.api.rule=PathPrefix(`/api`) || PathPrefix(`/dashboard`)" + - "traefik.http.routers.api.service=api@internal" + - "traefik.http.routers.api.middlewares=auth" + - "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0" +``` + +```json tab="Marathon" +"labels": { + "traefik.http.routers.api.rule": "PathPrefix(`/api`) || PathPrefix(`/dashboard`)" + "traefik.http.routers.api.service": "api@internal" + "traefik.http.routers.api.middlewares": "auth" + "traefik.http.middlewares.auth.basicauth.users": "test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0" +} +``` + +```yaml tab="Rancher" +# Declaring the user list +labels: + - "traefik.http.routers.api.rule=PathPrefix(`/api`) || PathPrefix(`/dashboard`)" + - "traefik.http.routers.api.service=api@internal" + - "traefik.http.routers.api.middlewares=auth" + - "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0" +``` + +```toml tab="File (TOML)" +[http.routers.my-api] + rule="PathPrefix(`/api`) || PathPrefix(`/dashboard`)" + service="api@internal" + middlewares=["auth"] + +[http.middlewares.auth.basicAuth] + users = [ + "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", + "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + ] +``` + +```yaml tab="File (YAML)" +http: + routers: + api: + rule: PathPrefix(`/api`) || PathPrefix(`/dashboard`) + service: api@internal + middlewares: + - auth + middlewares: + auth: + basicAuth: + users: + - "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/" + - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" +``` + +### `insecure` + +Enable the API in `insecure` mode, which means that the API will be available directly on the entryPoint named `traefik`. + +!!! Note + If the entryPoint named `traefik` is not configured, it will be automatically created on port 8080. + +```toml tab="File (TOML)" +[api] + insecure = true +``` + +```yaml tab="File (YAML)" +api: + insecure: true +``` + +```bash tab="CLI" +--api.insecure=true +``` + ### `dashboard` _Optional, Default=true_ diff --git a/docs/content/operations/dashboard.md b/docs/content/operations/dashboard.md index c0443c199..a73a52ee2 100644 --- a/docs/content/operations/dashboard.md +++ b/docs/content/operations/dashboard.md @@ -5,25 +5,16 @@ See What's Going On The dashboard is the central place that shows you the current active routes handled by Traefik. -!!! warning "Dashboard WIP" - Currently, the dashboard is in a Work In Progress State while being reconstructed for v2. - Therefore, the dashboard is currently not working. - - - The dashboard in action with Traefik listening to 3 different providers - - - - - The dashboard shows the health of the system. + + The dashboard in action By default, the dashboard is available on `/` on port `:8080`. !!! tip "Did You Know?" It is possible to customize the dashboard endpoint. - To learn how, refer to the `Traefik's API documentation`(TODO: add doc and link). + To learn how, refer to the [API documentation](./api.md) ## Enabling the Dashboard @@ -64,4 +55,4 @@ api: !!! tip "Did You Know?" The API provides more features than the Dashboard. - To learn more about it, refer to the `Traefik's API documentation`(TODO: add doc and link). + To learn more about it, refer to the [API documentation](./api.md) diff --git a/docs/content/operations/ping.md b/docs/content/operations/ping.md index deee3d641..26c03fca4 100644 --- a/docs/content/operations/ping.md +++ b/docs/content/operations/ping.md @@ -5,7 +5,7 @@ Checking the Health of Your Traefik Instances ## Configuration Examples -??? example "Enabling /ping" +To enable the API handler: ```toml tab="File (TOML)" [ping] @@ -19,10 +19,39 @@ ping: {} --ping=true ``` +## Configuration Options + +The `/ping` health-check URL is enabled with the command-line `--ping` or config file option `[ping]`. + +You can customize the `entryPoint` where the `/ping` is active with the `entryPoint` option (default value: `traefik`) + | Path | Method | Description | |---------|---------------|-----------------------------------------------------------------------------------------------------| | `/ping` | `GET`, `HEAD` | A simple endpoint to check for Traefik process liveness. Return a code `200` with the content: `OK` | -## Configuration Options +### `entryPoint` -The `/ping` health-check URL is enabled with the command-line `--ping` or config file option `[ping]`. \ No newline at end of file +Enabling /ping on a dedicated EntryPoint. + +```toml tab="File (TOML)" +[entryPoints] + [entryPoints.ping] + address = ":8082" + +[ping] + entryPoint = "ping" +``` + +```yaml tab="File (YAML)" +entryPoints: + ping: + address: ":8082" + +ping: + entryPoint: "ping" +``` + +```bash tab="CLI" +--entryPoints.ping.address=":8082" +--ping.entryPoint="ping" +``` diff --git a/docs/content/providers/docker.md b/docs/content/providers/docker.md index 6e20e5e4b..a7e2fab9b 100644 --- a/docs/content/providers/docker.md +++ b/docs/content/providers/docker.md @@ -77,6 +77,7 @@ Attach labels to your containers and let Traefik do the rest! deploy: labels: - traefik.http.routers.my-container.rule=Host(`my-domain`) + - traefik.http.services.my-container-service.loadbalancer.server.port=8080 ``` !!! important "Labels in Docker Swarm Mode" @@ -387,7 +388,7 @@ Constraints is an expression that Traefik matches against the container's labels That is to say, if none of the container's labels match the expression, no route for the container is created. If the expression is empty, all detected containers are included. -The expression syntax is based on the `Label("key", "value")`, and `LabelRegexp("key", "value")` functions, as well as the usual boolean logic, as shown in examples below. +The expression syntax is based on the `Label("key", "value")`, and `LabelRegex("key", "value")` functions, as well as the usual boolean logic, as shown in examples below. ??? example "Constraints Expression Examples" @@ -418,11 +419,121 @@ The expression syntax is based on the `Label("key", "value")`, and `LabelRegexp( ```toml # Includes only containers having a label with key `a.label.name` and a value matching the `a.+` regular expression. - constraints = "LabelRegexp(`a.label.name`, `a.+`)" + constraints = "LabelRegex(`a.label.name`, `a.+`)" ``` See also [Restrict the Scope of Service Discovery](./overview.md#restrict-the-scope-of-service-discovery). +### `tls` + +_Optional_ + +#### `tls.ca` + +TODO add description. + +```toml tab="File (TOML)" +[providers.docker.tls] + ca = "path/to/ca.crt" +``` + +```yaml tab="File (YAML)" +providers: + docker: + tls: + ca: path/to/ca.crt +``` + +```bash tab="CLI" +--providers.docker.tls.ca=path/to/ca.crt +``` + +#### `tls.caOptional` + +TODO add description. + +```toml tab="File (TOML)" +[providers.docker.tls] + caOptional = true +``` + +```yaml tab="File (YAML)" +providers: + docker: + tls: + caOptional: true +``` + +```bash tab="CLI" +--providers.docker.tls.caOptional=true +``` + +#### `tls.cert` + +TODO add description. + +```toml tab="File (TOML)" +[providers.docker.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```yaml tab="File (YAML)" +providers: + docker: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```bash tab="CLI" +--providers.docker.tls.cert=path/to/foo.cert +--providers.docker.tls.key=path/to/foo.key +``` + +#### `tls.key` + +TODO add description. + +```toml tab="File (TOML)" +[providers.docker.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```yaml tab="File (YAML)" +providers: + docker: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```bash tab="CLI" +--providers.docker.tls.cert=path/to/foo.cert +--providers.docker.tls.key=path/to/foo.key +``` + +#### `tls.insecureSkipVerify` + +TODO add description. + +```toml tab="File (TOML)" +[providers.docker.tls] + insecureSkipVerify = true +``` + +```yaml tab="File (YAML)" +providers: + docker: + tls: + insecureSkipVerify: true +``` + +```bash tab="CLI" +--providers.docker.tls.insecureSkipVerify=true +``` + ## Routing Configuration Options ### General @@ -477,7 +588,7 @@ You can declare TCP Routers and/or Services using labels. # ... labels: - traefik.tcp.routers.my-router.rule="HostSNI(`my-host.com`)" - - traefik.tcp.routers.my-router.rule.tls="true" + - traefik.tcp.routers.my-router.tls="true" - traefik.tcp.services.my-service.loadbalancer.server.port="4123" ``` diff --git a/docs/content/providers/file.md b/docs/content/providers/file.md index 48f9a62a0..78e471a1c 100644 --- a/docs/content/providers/file.md +++ b/docs/content/providers/file.md @@ -46,7 +46,7 @@ You can write these configuration elements: entryPoints = ["web"] middlewares = ["my-basic-auth"] service = "service-foo" - rule = "Path(`foo`)" + rule = "Path(`/foo`)" # Add the middleware [http.middlewares] @@ -75,7 +75,7 @@ You can write these configuration elements: middlewares: - my-basic-auth service: service-foo - rule: Path(`foo`) + rule: Path(`/foo`) # Add the middleware middlewares: diff --git a/docs/content/providers/kubernetes-crd.md b/docs/content/providers/kubernetes-crd.md index 637024e8b..653584187 100644 --- a/docs/content/providers/kubernetes-crd.md +++ b/docs/content/providers/kubernetes-crd.md @@ -168,6 +168,27 @@ Value of `kubernetes.io/ingress.class` annotation that identifies Ingress object If the parameter is non-empty, only Ingresses containing an annotation with the same value are processed. Otherwise, Ingresses missing the annotation, having an empty value, or the value `traefik` are processed. +### `throttleDuration` + +_Optional, Default: 0 (no throttling)_ + +```toml tab="File (TOML)" +[providers.kubernetesCRD] + throttleDuration = "10s" + # ... +``` + +```yaml tab="File (YAML)" +providers: + kubernetesCRD: + throttleDuration: "10s" + # ... +``` + +```bash tab="CLI" +--providers.kubernetescrd.throttleDuration="10s" +``` + ## Resource Configuration If you're in a hurry, maybe you'd rather go through the [dynamic](../reference/dynamic-configuration/kubernetes-crd.md) configuration reference. diff --git a/docs/content/providers/kubernetes-ingress.md b/docs/content/providers/kubernetes-ingress.md index ca0978d44..a8819ae98 100644 --- a/docs/content/providers/kubernetes-ingress.md +++ b/docs/content/providers/kubernetes-ingress.md @@ -305,6 +305,27 @@ providers: Published Kubernetes Service to copy status from. +### `throttleDuration` + +_Optional, Default: 0 (no throttling)_ + +```toml tab="File (TOML)" +[providers.kubernetesIngress] + throttleDuration = "10s" + # ... +``` + +```yaml tab="File (YAML)" +providers: + kubernetesIngress: + throttleDuration: "10s" + # ... +``` + +```bash tab="CLI" +--providers.kubernetesingress.throttleDuration="10s" +``` + ## Further If one wants to know more about the various aspects of the Ingress spec that Traefik supports, many examples of Ingresses definitions are located in the tests [data](https://github.com/containous/traefik/tree/v2.0/pkg/provider/kubernetes/ingress/fixtures) of the Traefik repository. diff --git a/docs/content/providers/marathon.md b/docs/content/providers/marathon.md index e19e9877c..b7064c869 100644 --- a/docs/content/providers/marathon.md +++ b/docs/content/providers/marathon.md @@ -243,7 +243,7 @@ That is to say, if none of the application's labels match the expression, no rou In addition, the expression also matched against the application's constraints, such as described in [Marathon constraints](https://mesosphere.github.io/marathon/docs/constraints.html). If the expression is empty, all detected applications are included. -The expression syntax is based on the `Label("key", "value")`, and `LabelRegexp("key", "value")`, as well as the usual boolean logic. +The expression syntax is based on the `Label("key", "value")`, and `LabelRegex("key", "value")`, as well as the usual boolean logic. In addition, to match against marathon constraints, the function `MarathonConstraint("field:operator:value")` can be used, where the field, operator, and value parts are joined together in a single string with the `:` separator. ??? example "Constraints Expression Examples" @@ -275,7 +275,7 @@ In addition, to match against marathon constraints, the function `MarathonConstr ```toml # Includes only applications having a label with key `a.label.name` and a value matching the `a.+` regular expression. - constraints = "LabelRegexp(`a.label.name`, `a.+`)" + constraints = "LabelRegex(`a.label.name`, `a.+`)" ``` ```toml @@ -398,37 +398,116 @@ when waiting for the first response header from a Marathon master. Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration), or directly as a number of seconds. -### `TLS` +### `tls` _Optional_ +#### `tls.ca` + +TODO add description. + +```toml tab="File (TOML)" +[providers.marathon.tls] + ca = "path/to/ca.crt" +``` + +```yaml tab="File (YAML)" +providers: + marathon: + tls: + ca: path/to/ca.crt +``` + +```bash tab="CLI" +--providers.marathon.tls.ca=path/to/ca.crt +``` + +#### `tls.caOptional` + +TODO add description. + +```toml tab="File (TOML)" +[providers.marathon.tls] + caOptional = true +``` + +```yaml tab="File (YAML)" +providers: + marathon: + tls: + caOptional: true +``` + +```bash tab="CLI" +--providers.marathon.tls.caOptional=true +``` + +#### `tls.cert` + +TODO add description. + +```toml tab="File (TOML)" +[providers.marathon.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```yaml tab="File (YAML)" +providers: + marathon: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```bash tab="CLI" +--providers.marathon.tls.cert=path/to/foo.cert +--providers.marathon.tls.key=path/to/foo.key +``` + +#### `tls.key` + +TODO add description. + +```toml tab="File (TOML)" +[providers.marathon.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```yaml tab="File (YAML)" +providers: + marathon: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```bash tab="CLI" +--providers.marathon.tls.cert=path/to/foo.cert +--providers.marathon.tls.key=path/to/foo.key +``` + +#### `tls.insecureSkipVerify` + +TODO add description. + ```toml tab="File (TOML)" [providers.marathon.tls] - ca = "/etc/ssl/ca.crt" - cert = "/etc/ssl/marathon.cert" - key = "/etc/ssl/marathon.key" insecureSkipVerify = true ``` ```yaml tab="File (YAML)" providers: - marathon + marathon: tls: - ca: "/etc/ssl/ca.crt" - cert: "/etc/ssl/marathon.cert" - key: "/etc/ssl/marathon.key" - insecureSkipVerify: true + insecureSkipVerify: true ``` ```bash tab="CLI" ---providers.marathon.tls.ca="/etc/ssl/ca.crt" ---providers.marathon.tls.cert="/etc/ssl/marathon.cert" ---providers.marathon.tls.key="/etc/ssl/marathon.key" ---providers.marathon.tls.insecureskipverify=true +--providers.marathon.tls.insecureSkipVerify=true ``` -TLS client configuration. [tls/#Config](https://golang.org/pkg/crypto/tls/#Config). - ### `tlsHandshakeTimeout` _Optional, Default=5s_ diff --git a/docs/content/providers/rancher.md b/docs/content/providers/rancher.md index f3aa97121..5931476bc 100644 --- a/docs/content/providers/rancher.md +++ b/docs/content/providers/rancher.md @@ -239,7 +239,7 @@ Constraints is an expression that Traefik matches against the container's labels That is to say, if none of the container's labels match the expression, no route for the container is created. If the expression is empty, all detected containers are included. -The expression syntax is based on the `Label("key", "value")`, and `LabelRegexp("key", "value")` functions, as well as the usual boolean logic, as shown in examples below. +The expression syntax is based on the `Label("key", "value")`, and `LabelRegex("key", "value")` functions, as well as the usual boolean logic, as shown in examples below. ??? example "Constraints Expression Examples" @@ -270,7 +270,7 @@ The expression syntax is based on the `Label("key", "value")`, and `LabelRegexp( ```toml # Includes only containers having a label with key `a.label.name` and a value matching the `a.+` regular expression. - constraints = "LabelRegexp(`a.label.name`, `a.+`)" + constraints = "LabelRegex(`a.label.name`, `a.+`)" ``` See also [Restrict the Scope of Service Discovery](./overview.md#restrict-the-scope-of-service-discovery). diff --git a/docs/content/reference/dynamic-configuration/docker-labels.yml b/docs/content/reference/dynamic-configuration/docker-labels.yml index c01640149..757e525cf 100644 --- a/docs/content/reference/dynamic-configuration/docker-labels.yml +++ b/docs/content/reference/dynamic-configuration/docker-labels.yml @@ -45,13 +45,13 @@ - "traefik.http.middlewares.middleware09.headers.customrequestheaders.name1=foobar" - "traefik.http.middlewares.middleware09.headers.customresponseheaders.name0=foobar" - "traefik.http.middlewares.middleware09.headers.customresponseheaders.name1=foobar" +- "traefik.http.middlewares.middleware09.headers.featurepolicy=foobar" - "traefik.http.middlewares.middleware09.headers.forcestsheader=true" - "traefik.http.middlewares.middleware09.headers.framedeny=true" - "traefik.http.middlewares.middleware09.headers.hostsproxyheaders=foobar, foobar" - "traefik.http.middlewares.middleware09.headers.isdevelopment=true" - "traefik.http.middlewares.middleware09.headers.publickey=foobar" - "traefik.http.middlewares.middleware09.headers.referrerpolicy=foobar" -- "traefik.http.middlewares.middleware09.headers.featurepolicy=foobar" - "traefik.http.middlewares.middleware09.headers.sslforcehost=true" - "traefik.http.middlewares.middleware09.headers.sslhost=foobar" - "traefik.http.middlewares.middleware09.headers.sslproxyheaders.name0=foobar" @@ -65,10 +65,9 @@ - "traefik.http.middlewares.middleware10.ipwhitelist.ipstrategy.excludedips=foobar, foobar" - "traefik.http.middlewares.middleware10.ipwhitelist.sourcerange=foobar, foobar" - "traefik.http.middlewares.middleware11.inflightreq.amount=42" -- "traefik.http.middlewares.middleware11.inflightreq.sourcecriterion.requestheadername=foobar" -- "traefik.http.middlewares.middleware11.inflightreq.sourcecriterion.requesthost=true" - "traefik.http.middlewares.middleware11.inflightreq.sourcecriterion.ipstrategy.depth=42" - "traefik.http.middlewares.middleware11.inflightreq.sourcecriterion.ipstrategy.excludedips=foobar, foobar" +- "traefik.http.middlewares.middleware11.inflightreq.sourcecriterion.requestheadername=foobar" - "traefik.http.middlewares.middleware11.inflightreq.sourcecriterion.requesthost=true" - "traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.commonname=true" - "traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.country=true" @@ -88,18 +87,24 @@ - "traefik.http.middlewares.middleware12.passtlsclientcert.info.subject.province=true" - "traefik.http.middlewares.middleware12.passtlsclientcert.info.subject.serialnumber=true" - "traefik.http.middlewares.middleware12.passtlsclientcert.pem=true" -- "traefik.http.middlewares.middleware13.redirectregex.permanent=true" -- "traefik.http.middlewares.middleware13.redirectregex.regex=foobar" -- "traefik.http.middlewares.middleware13.redirectregex.replacement=foobar" -- "traefik.http.middlewares.middleware14.redirectscheme.permanent=true" -- "traefik.http.middlewares.middleware14.redirectscheme.port=foobar" -- "traefik.http.middlewares.middleware14.redirectscheme.scheme=foobar" -- "traefik.http.middlewares.middleware15.replacepath.path=foobar" -- "traefik.http.middlewares.middleware16.replacepathregex.regex=foobar" -- "traefik.http.middlewares.middleware16.replacepathregex.replacement=foobar" -- "traefik.http.middlewares.middleware17.retry.attempts=42" -- "traefik.http.middlewares.middleware18.stripprefix.prefixes=foobar, foobar" -- "traefik.http.middlewares.middleware19.stripprefixregex.regex=foobar, foobar" +- "traefik.http.middlewares.middleware13.ratelimit.average=42" +- "traefik.http.middlewares.middleware13.ratelimit.burst=42" +- "traefik.http.middlewares.middleware13.ratelimit.sourcecriterion.ipstrategy.depth=42" +- "traefik.http.middlewares.middleware13.ratelimit.sourcecriterion.ipstrategy.excludedips=foobar, foobar" +- "traefik.http.middlewares.middleware13.ratelimit.sourcecriterion.requestheadername=foobar" +- "traefik.http.middlewares.middleware13.ratelimit.sourcecriterion.requesthost=true" +- "traefik.http.middlewares.middleware14.redirectregex.permanent=true" +- "traefik.http.middlewares.middleware14.redirectregex.regex=foobar" +- "traefik.http.middlewares.middleware14.redirectregex.replacement=foobar" +- "traefik.http.middlewares.middleware15.redirectscheme.permanent=true" +- "traefik.http.middlewares.middleware15.redirectscheme.port=foobar" +- "traefik.http.middlewares.middleware15.redirectscheme.scheme=foobar" +- "traefik.http.middlewares.middleware16.replacepath.path=foobar" +- "traefik.http.middlewares.middleware17.replacepathregex.regex=foobar" +- "traefik.http.middlewares.middleware17.replacepathregex.replacement=foobar" +- "traefik.http.middlewares.middleware18.retry.attempts=42" +- "traefik.http.middlewares.middleware19.stripprefix.prefixes=foobar, foobar" +- "traefik.http.middlewares.middleware20.stripprefixregex.regex=foobar, foobar" - "traefik.http.routers.router0.entrypoints=foobar, foobar" - "traefik.http.routers.router0.middlewares=foobar, foobar" - "traefik.http.routers.router0.priority=42" @@ -135,8 +140,8 @@ - "traefik.http.services.service0.loadbalancer.passhostheader=true" - "traefik.http.services.service0.loadbalancer.responseforwarding.flushinterval=foobar" - "traefik.http.services.service0.loadbalancer.sticky=true" -- "traefik.http.services.service0.loadbalancer.sticky.cookie.name=foobar" - "traefik.http.services.service0.loadbalancer.sticky.cookie.httponly=true" +- "traefik.http.services.service0.loadbalancer.sticky.cookie.name=foobar" - "traefik.http.services.service0.loadbalancer.sticky.cookie.secure=true" - "traefik.http.services.service0.loadbalancer.server.port=foobar" - "traefik.http.services.service0.loadbalancer.server.scheme=foobar" @@ -151,8 +156,8 @@ - "traefik.http.services.service1.loadbalancer.passhostheader=true" - "traefik.http.services.service1.loadbalancer.responseforwarding.flushinterval=foobar" - "traefik.http.services.service1.loadbalancer.sticky=true" -- "traefik.http.services.service1.loadbalancer.sticky.cookie.name=foobar" - "traefik.http.services.service1.loadbalancer.sticky.cookie.httponly=true" +- "traefik.http.services.service1.loadbalancer.sticky.cookie.name=foobar" - "traefik.http.services.service1.loadbalancer.sticky.cookie.secure=true" - "traefik.http.services.service1.loadbalancer.server.port=foobar" - "traefik.http.services.service1.loadbalancer.server.scheme=foobar" diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml index 71ad8d891..88fc62144 100644 --- a/docs/content/reference/dynamic-configuration/file.toml +++ b/docs/content/reference/dynamic-configuration/file.toml @@ -62,17 +62,28 @@ [http.services.Service01.loadBalancer.responseForwarding] flushInterval = "foobar" [http.services.Service02] - [http.services.Service02.weighted] + [http.services.Service02.mirroring] + service = "foobar" - [[http.services.Service02.weighted.services]] + [[http.services.Service02.mirroring.mirrors]] + name = "foobar" + percent = 42 + + [[http.services.Service02.mirroring.mirrors]] + name = "foobar" + percent = 42 + [http.services.Service03] + [http.services.Service03.weighted] + + [[http.services.Service03.weighted.services]] name = "foobar" weight = 42 - [[http.services.Service02.weighted.services]] + [[http.services.Service03.weighted.services]] name = "foobar" weight = 42 - [http.services.Service02.weighted.sticky] - [http.services.Service02.weighted.sticky.cookie] + [http.services.Service03.weighted.sticky] + [http.services.Service03.weighted.sticky.cookie] name = "foobar" secure = true httpOnly = true @@ -202,30 +213,40 @@ serialNumber = true domainComponent = true [http.middlewares.Middleware13] - [http.middlewares.Middleware13.redirectRegex] + [http.middlewares.Middleware13.rateLimit] + average = 42 + burst = 42 + [http.middlewares.Middleware13.rateLimit.sourceCriterion] + requestHeaderName = "foobar" + requestHost = true + [http.middlewares.Middleware13.rateLimit.sourceCriterion.ipStrategy] + depth = 42 + excludedIPs = ["foobar", "foobar"] + [http.middlewares.Middleware14] + [http.middlewares.Middleware14.redirectRegex] regex = "foobar" replacement = "foobar" permanent = true - [http.middlewares.Middleware14] - [http.middlewares.Middleware14.redirectScheme] + [http.middlewares.Middleware15] + [http.middlewares.Middleware15.redirectScheme] scheme = "foobar" port = "foobar" permanent = true - [http.middlewares.Middleware15] - [http.middlewares.Middleware15.replacePath] - path = "foobar" [http.middlewares.Middleware16] - [http.middlewares.Middleware16.replacePathRegex] + [http.middlewares.Middleware16.replacePath] + path = "foobar" + [http.middlewares.Middleware17] + [http.middlewares.Middleware17.replacePathRegex] regex = "foobar" replacement = "foobar" - [http.middlewares.Middleware17] - [http.middlewares.Middleware17.retry] - attempts = 42 [http.middlewares.Middleware18] - [http.middlewares.Middleware18.stripPrefix] - prefixes = ["foobar", "foobar"] + [http.middlewares.Middleware18.retry] + attempts = 42 [http.middlewares.Middleware19] - [http.middlewares.Middleware19.stripPrefixRegex] + [http.middlewares.Middleware19.stripPrefix] + prefixes = ["foobar", "foobar"] + [http.middlewares.Middleware20] + [http.middlewares.Middleware20.stripPrefixRegex] regex = ["foobar", "foobar"] [tcp] diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml index 89753c2a5..46a54373f 100644 --- a/docs/content/reference/dynamic-configuration/file.yaml +++ b/docs/content/reference/dynamic-configuration/file.yaml @@ -69,6 +69,14 @@ http: responseForwarding: flushInterval: foobar Service02: + mirroring: + service: foobar + mirrors: + - name: foobar + percent: 42 + - name: foobar + percent: 42 + Service03: weighted: services: - name: foobar @@ -201,9 +209,11 @@ http: inFlightReq: amount: 42 sourceCriterion: - ipStrategy: + ipstrategy: depth: 42 - excludedIPs: [ foobar, foobar ] + excludedIPs: + - foobar + - foobar requestHeaderName: foobar requestHost: true Middleware12: @@ -230,45 +240,47 @@ http: serialNumber: true domainComponent: true Middleware13: - redirectRegex: - regex: foobar - replacement: foobar - permanent: true - Middleware14: - redirectScheme: - scheme: foobar - port: foobar - permanent: true - Middleware15: - replacePath: - path: foobar - Middleware16: - replacePathRegex: - regex: foobar - replacement: foobar - Middleware17: - retry: - attempts: 42 - Middleware18: - stripPrefix: - prefixes: - - foobar - - foobar - Middleware19: - stripPrefixRegex: - regex: - - foobar - - foobar - Middleware20: rateLimit: average: 42 burst: 42 sourceCriterion: - ipStrategy: + ipstrategy: depth: 42 - excludedIPs: [ foobar, foobar ] + excludedIPs: + - foobar + - foobar requestHeaderName: foobar requestHost: true + Middleware14: + redirectRegex: + regex: foobar + replacement: foobar + permanent: true + Middleware15: + redirectScheme: + scheme: foobar + port: foobar + permanent: true + Middleware16: + replacePath: + path: foobar + Middleware17: + replacePathRegex: + regex: foobar + replacement: foobar + Middleware18: + retry: + attempts: 42 + Middleware19: + stripPrefix: + prefixes: + - foobar + - foobar + Middleware20: + stripPrefixRegex: + regex: + - foobar + - foobar tcp: routers: TCPRouter0: diff --git a/docs/content/reference/dynamic-configuration/marathon-labels.json b/docs/content/reference/dynamic-configuration/marathon-labels.json index 163924b4a..5b1360841 100644 --- a/docs/content/reference/dynamic-configuration/marathon-labels.json +++ b/docs/content/reference/dynamic-configuration/marathon-labels.json @@ -45,13 +45,13 @@ "traefik.http.middlewares.middleware09.headers.customrequestheaders.name1": "foobar", "traefik.http.middlewares.middleware09.headers.customresponseheaders.name0": "foobar", "traefik.http.middlewares.middleware09.headers.customresponseheaders.name1": "foobar", +"traefik.http.middlewares.middleware09.headers.featurepolicy": "foobar", "traefik.http.middlewares.middleware09.headers.forcestsheader": "true", "traefik.http.middlewares.middleware09.headers.framedeny": "true", "traefik.http.middlewares.middleware09.headers.hostsproxyheaders": "foobar, foobar", "traefik.http.middlewares.middleware09.headers.isdevelopment": "true", "traefik.http.middlewares.middleware09.headers.publickey": "foobar", "traefik.http.middlewares.middleware09.headers.referrerpolicy": "foobar", -"traefik.http.middlewares.middleware09.headers.featurepolicy": "foobar", "traefik.http.middlewares.middleware09.headers.sslforcehost": "true", "traefik.http.middlewares.middleware09.headers.sslhost": "foobar", "traefik.http.middlewares.middleware09.headers.sslproxyheaders.name0": "foobar", @@ -64,11 +64,11 @@ "traefik.http.middlewares.middleware10.ipwhitelist.ipstrategy.depth": "42", "traefik.http.middlewares.middleware10.ipwhitelist.ipstrategy.excludedips": "foobar, foobar", "traefik.http.middlewares.middleware10.ipwhitelist.sourcerange": "foobar, foobar", -"traefik.http.middlewares.Middleware11.inflightreq.amount": "42", -"traefik.http.middlewares.Middleware11.inflightreq.sourcecriterion.ipstrategy.depth": "42", -"traefik.http.middlewares.Middleware11.inflightreq.sourcecriterion.ipstrategy.excludedips": "foobar, fiibar", -"traefik.http.middlewares.Middleware11.inflightreq.sourcecriterion.requestheadername": "foobar", -"traefik.http.middlewares.Middleware11.inflightreq.sourcecriterion.requesthost": "true", +"traefik.http.middlewares.middleware11.inflightreq.amount": "42", +"traefik.http.middlewares.middleware11.inflightreq.sourcecriterion.ipstrategy.depth": "42", +"traefik.http.middlewares.middleware11.inflightreq.sourcecriterion.ipstrategy.excludedips": "foobar, foobar", +"traefik.http.middlewares.middleware11.inflightreq.sourcecriterion.requestheadername": "foobar", +"traefik.http.middlewares.middleware11.inflightreq.sourcecriterion.requesthost": "true", "traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.commonname": "true", "traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.country": "true", "traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.domaincomponent": "true", @@ -87,24 +87,24 @@ "traefik.http.middlewares.middleware12.passtlsclientcert.info.subject.province": "true", "traefik.http.middlewares.middleware12.passtlsclientcert.info.subject.serialnumber": "true", "traefik.http.middlewares.middleware12.passtlsclientcert.pem": "true", -"traefik.http.middlewares.middleware13.redirectregex.permanent": "true", -"traefik.http.middlewares.middleware13.redirectregex.regex": "foobar", -"traefik.http.middlewares.middleware13.redirectregex.replacement": "foobar", -"traefik.http.middlewares.middleware14.redirectscheme.permanent": "true", -"traefik.http.middlewares.middleware14.redirectscheme.port": "foobar", -"traefik.http.middlewares.middleware14.redirectscheme.scheme": "foobar", -"traefik.http.middlewares.middleware15.replacepath.path": "foobar", -"traefik.http.middlewares.middleware16.replacepathregex.regex": "foobar", -"traefik.http.middlewares.middleware16.replacepathregex.replacement": "foobar", -"traefik.http.middlewares.middleware17.retry.attempts": "42", -"traefik.http.middlewares.middleware18.stripprefix.prefixes": "foobar, foobar", -"traefik.http.middlewares.middleware19.stripprefixregex.regex": "foobar, foobar", -"traefik.http.middlewares.Middleware20.ratelimit.average": "42", -"traefik.http.middlewares.Middleware20.ratelimit.burst": "42", -"traefik.http.middlewares.Middleware20.ratelimit.sourcecriterion.requestheadername": "foobar", -"traefik.http.middlewares.Middleware20.ratelimit.sourcecriterion.requesthost": "true", -"traefik.http.middlewares.Middleware20.ratelimit.sourcecriterion.ipstrategy.depth": "42", -"traefik.http.middlewares.Middleware20.ratelimit.sourcecriterion.ipstrategy.excludedips": "foobar, foobar", +"traefik.http.middlewares.middleware13.ratelimit.average": "42", +"traefik.http.middlewares.middleware13.ratelimit.burst": "42", +"traefik.http.middlewares.middleware13.ratelimit.sourcecriterion.ipstrategy.depth": "42", +"traefik.http.middlewares.middleware13.ratelimit.sourcecriterion.ipstrategy.excludedips": "foobar, foobar", +"traefik.http.middlewares.middleware13.ratelimit.sourcecriterion.requestheadername": "foobar", +"traefik.http.middlewares.middleware13.ratelimit.sourcecriterion.requesthost": "true", +"traefik.http.middlewares.middleware14.redirectregex.permanent": "true", +"traefik.http.middlewares.middleware14.redirectregex.regex": "foobar", +"traefik.http.middlewares.middleware14.redirectregex.replacement": "foobar", +"traefik.http.middlewares.middleware15.redirectscheme.permanent": "true", +"traefik.http.middlewares.middleware15.redirectscheme.port": "foobar", +"traefik.http.middlewares.middleware15.redirectscheme.scheme": "foobar", +"traefik.http.middlewares.middleware16.replacepath.path": "foobar", +"traefik.http.middlewares.middleware17.replacepathregex.regex": "foobar", +"traefik.http.middlewares.middleware17.replacepathregex.replacement": "foobar", +"traefik.http.middlewares.middleware18.retry.attempts": "42", +"traefik.http.middlewares.middleware19.stripprefix.prefixes": "foobar, foobar", +"traefik.http.middlewares.middleware20.stripprefixregex.regex": "foobar, foobar", "traefik.http.routers.router0.entrypoints": "foobar, foobar", "traefik.http.routers.router0.middlewares": "foobar, foobar", "traefik.http.routers.router0.priority": "42", @@ -140,8 +140,8 @@ "traefik.http.services.service0.loadbalancer.passhostheader": "true", "traefik.http.services.service0.loadbalancer.responseforwarding.flushinterval": "foobar", "traefik.http.services.service0.loadbalancer.sticky": "true", -"traefik.http.services.service0.loadbalancer.sticky.cookie.name": "foobar", "traefik.http.services.service0.loadbalancer.sticky.cookie.httponly": "true", +"traefik.http.services.service0.loadbalancer.sticky.cookie.name": "foobar", "traefik.http.services.service0.loadbalancer.sticky.cookie.secure": "true", "traefik.http.services.service0.loadbalancer.server.port": "foobar", "traefik.http.services.service0.loadbalancer.server.scheme": "foobar", @@ -156,9 +156,9 @@ "traefik.http.services.service1.loadbalancer.passhostheader": "true", "traefik.http.services.service1.loadbalancer.responseforwarding.flushinterval": "foobar", "traefik.http.services.service1.loadbalancer.sticky": "true", +"traefik.http.services.service1.loadbalancer.sticky.cookie.httponly": "true", "traefik.http.services.service1.loadbalancer.sticky.cookie.name": "foobar", "traefik.http.services.service1.loadbalancer.sticky.cookie.secure": "true", -"traefik.http.services.service1.loadbalancer.sticky.cookie.httponly": "true", "traefik.http.services.service1.loadbalancer.server.port": "foobar", "traefik.http.services.service1.loadbalancer.server.scheme": "foobar", "traefik.tcp.routers.tcprouter0.entrypoints": "foobar, foobar", diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index 55ebff757..c5c813407 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -45,6 +45,9 @@ Activate dashboard. (Default: ```true```) `--api.debug`: Enable additional endpoints for debugging and profiling. (Default: ```false```) +`--api.insecure`: +Activate API directly on the entryPoint named traefik. (Default: ```false```) + `--certificatesresolvers.`: Certificates resolvers configuration. (Default: ```false```) @@ -151,19 +154,19 @@ Traefik log format: json | common (Default: ```common```) Log level set to traefik logs. (Default: ```ERROR```) `--metrics.datadog`: -DataDog metrics exporter type. (Default: ```false```) +Datadog metrics exporter type. (Default: ```false```) `--metrics.datadog.addentrypointslabels`: Enable metrics on entry points. (Default: ```true```) `--metrics.datadog.address`: -DataDog's address. (Default: ```localhost:8125```) +Datadog's address. (Default: ```localhost:8125```) `--metrics.datadog.addserviceslabels`: Enable metrics on services. (Default: ```true```) `--metrics.datadog.pushinterval`: -DataDog push interval. (Default: ```10```) +Datadog push interval. (Default: ```10```) `--metrics.influxdb`: InfluxDB metrics exporter type. (Default: ```false```) @@ -207,6 +210,9 @@ Enable metrics on services. (Default: ```true```) `--metrics.prometheus.buckets`: Buckets for latency metrics. (Default: ```0.100000, 0.300000, 1.200000, 5.000000```) +`--metrics.prometheus.entrypoint`: +EntryPoint (Default: ```traefik```) + `--metrics.statsd`: StatsD metrics exporter type. (Default: ```false```) @@ -223,7 +229,10 @@ Enable metrics on services. (Default: ```true```) StatsD push interval. (Default: ```10```) `--ping`: -Enable ping. (Default: ```true```) +Enable ping. (Default: ```false```) + +`--ping.entrypoint`: +EntryPoint (Default: ```traefik```) `--providers.docker`: Enable Docker backend with default settings. (Default: ```false```) @@ -303,6 +312,9 @@ Kubernetes label selector to use. `--providers.kubernetescrd.namespaces`: Kubernetes namespaces. +`--providers.kubernetescrd.throttleduration`: +Ingress refresh throttle duration (Default: ```0```) + `--providers.kubernetescrd.token`: Kubernetes bearer token (not needed for in-cluster client). @@ -336,6 +348,9 @@ Kubernetes Ingress label selector to use. `--providers.kubernetesingress.namespaces`: Kubernetes namespaces. +`--providers.kubernetesingress.throttleduration`: +Ingress refresh throttle duration (Default: ```0```) + `--providers.kubernetesingress.token`: Kubernetes bearer token (not needed for in-cluster client). @@ -433,7 +448,10 @@ Defines the polling interval in seconds. (Default: ```15```) Watch provider. (Default: ```true```) `--providers.rest`: -Enable Rest backend with default settings. (Default: ```true```) +Enable Rest backend with default settings. (Default: ```false```) + +`--providers.rest.insecure`: +Activate REST Provider directly on the entryPoint named traefik. (Default: ```false```) `--serverstransport.forwardingtimeouts.dialtimeout`: The amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists. (Default: ```30```) @@ -457,13 +475,13 @@ Add cert file for self-signed certificate. OpenTracing configuration. (Default: ```false```) `--tracing.datadog`: -Settings for DataDog. (Default: ```false```) +Settings for Datadog. (Default: ```false```) `--tracing.datadog.bagageprefixheadername`: Specifies the header name prefix that will be used to store baggage items in a map. `--tracing.datadog.debug`: -Enable DataDog debug. (Default: ```false```) +Enable Datadog debug. (Default: ```false```) `--tracing.datadog.globaltag`: Key:Value tag to be set on all the spans. @@ -561,11 +579,8 @@ Set the maximum character limit for Span names (default 0 = no limit). (Default: `--tracing.zipkin`: Settings for Zipkin. (Default: ```false```) -`--tracing.zipkin.debug`: -Enable Zipkin debug. (Default: ```false```) - `--tracing.zipkin.httpendpoint`: -HTTP Endpoint to report traces to. (Default: ```http://localhost:9411/api/v1/spans```) +HTTP Endpoint to report traces to. (Default: ```http://localhost:9411/api/v2/spans```) `--tracing.zipkin.id128bit`: Use Zipkin 128 bit root span IDs. (Default: ```true```) diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index e578642e9..a223da524 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -45,6 +45,9 @@ Activate dashboard. (Default: ```true```) `TRAEFIK_API_DEBUG`: Enable additional endpoints for debugging and profiling. (Default: ```false```) +`TRAEFIK_API_INSECURE`: +Activate API directly on the entryPoint named traefik. (Default: ```false```) + `TRAEFIK_CERTIFICATESRESOLVERS_`: Certificates resolvers configuration. (Default: ```false```) @@ -151,19 +154,19 @@ Traefik log format: json | common (Default: ```common```) Log level set to traefik logs. (Default: ```ERROR```) `TRAEFIK_METRICS_DATADOG`: -DataDog metrics exporter type. (Default: ```false```) +Datadog metrics exporter type. (Default: ```false```) `TRAEFIK_METRICS_DATADOG_ADDENTRYPOINTSLABELS`: Enable metrics on entry points. (Default: ```true```) `TRAEFIK_METRICS_DATADOG_ADDRESS`: -DataDog's address. (Default: ```localhost:8125```) +Datadog's address. (Default: ```localhost:8125```) `TRAEFIK_METRICS_DATADOG_ADDSERVICESLABELS`: Enable metrics on services. (Default: ```true```) `TRAEFIK_METRICS_DATADOG_PUSHINTERVAL`: -DataDog push interval. (Default: ```10```) +Datadog push interval. (Default: ```10```) `TRAEFIK_METRICS_INFLUXDB`: InfluxDB metrics exporter type. (Default: ```false```) @@ -207,6 +210,9 @@ Enable metrics on services. (Default: ```true```) `TRAEFIK_METRICS_PROMETHEUS_BUCKETS`: Buckets for latency metrics. (Default: ```0.100000, 0.300000, 1.200000, 5.000000```) +`TRAEFIK_METRICS_PROMETHEUS_ENTRYPOINT`: +EntryPoint (Default: ```traefik```) + `TRAEFIK_METRICS_STATSD`: StatsD metrics exporter type. (Default: ```false```) @@ -223,7 +229,10 @@ Enable metrics on services. (Default: ```true```) StatsD push interval. (Default: ```10```) `TRAEFIK_PING`: -Enable ping. (Default: ```true```) +Enable ping. (Default: ```false```) + +`TRAEFIK_PING_ENTRYPOINT`: +EntryPoint (Default: ```traefik```) `TRAEFIK_PROVIDERS_DOCKER`: Enable Docker backend with default settings. (Default: ```false```) @@ -303,6 +312,9 @@ Kubernetes label selector to use. `TRAEFIK_PROVIDERS_KUBERNETESCRD_NAMESPACES`: Kubernetes namespaces. +`TRAEFIK_PROVIDERS_KUBERNETESCRD_THROTTLEDURATION`: +Ingress refresh throttle duration (Default: ```0```) + `TRAEFIK_PROVIDERS_KUBERNETESCRD_TOKEN`: Kubernetes bearer token (not needed for in-cluster client). @@ -336,6 +348,9 @@ Kubernetes Ingress label selector to use. `TRAEFIK_PROVIDERS_KUBERNETESINGRESS_NAMESPACES`: Kubernetes namespaces. +`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_THROTTLEDURATION`: +Ingress refresh throttle duration (Default: ```0```) + `TRAEFIK_PROVIDERS_KUBERNETESINGRESS_TOKEN`: Kubernetes bearer token (not needed for in-cluster client). @@ -433,7 +448,10 @@ Defines the polling interval in seconds. (Default: ```15```) Watch provider. (Default: ```true```) `TRAEFIK_PROVIDERS_REST`: -Enable Rest backend with default settings. (Default: ```true```) +Enable Rest backend with default settings. (Default: ```false```) + +`TRAEFIK_PROVIDERS_REST_INSECURE`: +Activate REST Provider directly on the entryPoint named traefik. (Default: ```false```) `TRAEFIK_SERVERSTRANSPORT_FORWARDINGTIMEOUTS_DIALTIMEOUT`: The amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists. (Default: ```30```) @@ -457,13 +475,13 @@ Add cert file for self-signed certificate. OpenTracing configuration. (Default: ```false```) `TRAEFIK_TRACING_DATADOG`: -Settings for DataDog. (Default: ```false```) +Settings for Datadog. (Default: ```false```) `TRAEFIK_TRACING_DATADOG_BAGAGEPREFIXHEADERNAME`: Specifies the header name prefix that will be used to store baggage items in a map. `TRAEFIK_TRACING_DATADOG_DEBUG`: -Enable DataDog debug. (Default: ```false```) +Enable Datadog debug. (Default: ```false```) `TRAEFIK_TRACING_DATADOG_GLOBALTAG`: Key:Value tag to be set on all the spans. @@ -561,11 +579,8 @@ Set the maximum character limit for Span names (default 0 = no limit). (Default: `TRAEFIK_TRACING_ZIPKIN`: Settings for Zipkin. (Default: ```false```) -`TRAEFIK_TRACING_ZIPKIN_DEBUG`: -Enable Zipkin debug. (Default: ```false```) - `TRAEFIK_TRACING_ZIPKIN_HTTPENDPOINT`: -HTTP Endpoint to report traces to. (Default: ```http://localhost:9411/api/v1/spans```) +HTTP Endpoint to report traces to. (Default: ```http://localhost:9411/api/v2/spans```) `TRAEFIK_TRACING_ZIPKIN_ID128BIT`: Use Zipkin 128 bit root span IDs. (Default: ```true```) diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 8c75d5170..3c314572c 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -83,6 +83,7 @@ namespaces = ["foobar", "foobar"] labelSelector = "foobar" ingressClass = "foobar" + throttleDuration = "10s" [providers.kubernetesIngress.ingressEndpoint] ip = "foobar" hostname = "foobar" @@ -95,7 +96,9 @@ namespaces = ["foobar", "foobar"] labelSelector = "foobar" ingressClass = "foobar" + throttleDuration = "10s" [providers.rest] + insecure = true [providers.rancher] constraints = "foobar" watch = true @@ -107,6 +110,7 @@ prefix = "foobar" [api] + insecure = true dashboard = true debug = true @@ -115,7 +119,8 @@ buckets = [42.0, 42.0] addEntryPointsLabels = true addServicesLabels = true - [metrics.dataDog] + entryPoint = "foobar" + [metrics.datadog] address = "foobar" pushInterval = "10s" addEntryPointsLabels = true @@ -137,6 +142,7 @@ addServicesLabels = true [ping] + entryPoint = "foobar" [log] level = "foobar" @@ -181,9 +187,8 @@ httpEndpoint = "foobar" sameSpan = true id128Bit = true - debug = true sampleRate = 42.0 - [tracing.dataDog] + [tracing.datadog] localAgentHostPort = "foobar" globalTag = "foobar" debug = true diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index f6a91a75c..e55eac104 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -88,6 +88,7 @@ providers: - foobar labelSelector: foobar ingressClass: foobar + throttleDuration: 10s ingressEndpoint: ip: foobar hostname: foobar @@ -102,7 +103,9 @@ providers: - foobar labelSelector: foobar ingressClass: foobar - rest: {} + throttleDuration: 10s + rest: + insecure: true rancher: constraints: foobar watch: true @@ -113,6 +116,7 @@ providers: intervalPoll: true prefix: foobar api: + insecure: true dashboard: true debug: true metrics: @@ -122,7 +126,8 @@ metrics: - 42 addEntryPointsLabels: true addServicesLabels: true - dataDog: + entryPoint: foobar + datadog: address: foobar pushInterval: 42 addEntryPointsLabels: true @@ -142,7 +147,8 @@ metrics: password: foobar addEntryPointsLabels: true addServicesLabels: true -ping: {} +ping: + entryPoint: foobar log: level: foobar filePath: foobar @@ -186,9 +192,8 @@ tracing: httpEndpoint: foobar sameSpan: true id128Bit: true - debug: true sampleRate: 42 - dataDog: + datadog: localAgentHostPort: foobar globalTag: foobar debug: true diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md index df8d9e676..f51412c8a 100644 --- a/docs/content/routing/entrypoints.md +++ b/docs/content/routing/entrypoints.md @@ -149,7 +149,7 @@ If the proxyprotocol header is passed, then the version is determined automatica entryPoints: web: address: ":80" - proxyProtocol + proxyProtocol: trustedIPs: - "127.0.0.1/32" - "192.168.1.7" @@ -213,7 +213,7 @@ You can configure Traefik to trust the forwarded headers information (`X-Forward entryPoints: web: address: ":80" - forwardedHeaders + forwardedHeaders: trustedIPs: - "127.0.0.1/32" - "192.168.1.7" diff --git a/docs/content/routing/routers/index.md b/docs/content/routing/routers/index.md index 4951d7240..c9067a1af 100644 --- a/docs/content/routing/routers/index.md +++ b/docs/content/routing/routers/index.md @@ -216,7 +216,7 @@ The table below lists all the available matchers: | ```Host(`domain-1`, ...)``` | Check if the request domain targets one of the given `domains`. | | ```HostRegexp(`traefik.io`, `{subdomain:[a-z]+}.traefik.io`, ...)``` | Check if the request domain matches the given `regexp`. | | ```Method(`GET`, ...)``` | Check if the request method is one of the given `methods` (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`) | -| ```Path(`path`, `/articles/{category}/{id:[0-9]+}`, ...)``` | Match exact request path. It accepts a sequence of literal and regular expression paths. | +| ```Path(`/path`, `/articles/{category}/{id:[0-9]+}`, ...)``` | Match exact request path. It accepts a sequence of literal and regular expression paths. | | ```PathPrefix(`/products/`, `/articles/{category}/{id:[0-9]+}`)``` | Match request prefix path. It accepts a sequence of literal and regular expression prefix paths. | | ```Query(`foo=bar`, `bar=baz`)``` | Match` Query String parameters. It accepts a sequence of key=value pairs. | @@ -631,7 +631,7 @@ Services are the target for the router. rule: "HostSNI(`foo-domain`)" service: service-id # will terminate the TLS request by default - tld: {} + tls: {} ``` ??? example "Configuring passthrough" diff --git a/docs/content/user-guides/marathon.md b/docs/content/user-guides/marathon.md index e47489e91..847a96016 100644 --- a/docs/content/user-guides/marathon.md +++ b/docs/content/user-guides/marathon.md @@ -98,7 +98,7 @@ The remaining section is going to explore them along with a benefit/cost trade-o It may seem obvious to reuse the Marathon health checks as a signal to Traefik whether an application should be taken into load-balancing rotation or not. -Apart from the increased latency a failing health check may have, a major problem with this is is that Marathon does not persist the health check results. +Apart from the increased latency a failing health check may have, a major problem with this is that Marathon does not persist the health check results. Consequently, if a master re-election occurs in the Marathon clusters, all health check results will revert to the _unknown_ state, effectively causing all applications inside the cluster to become unavailable and leading to a complete cluster failure. Re-elections do not only happen during regular maintenance work (often requiring rolling upgrades of the Marathon nodes) but also when the Marathon leader fails spontaneously. As such, there is no way to handle this situation deterministically. diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 46cba835d..b7f09c0e1 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -122,7 +122,7 @@ nav: - 'Access Logs': 'observability/access-logs.md' - 'Metrics': - 'Overview': 'observability/metrics/overview.md' - - 'DataDog': 'observability/metrics/datadog.md' + - 'Datadog': 'observability/metrics/datadog.md' - 'InfluxDB': 'observability/metrics/influxdb.md' - 'Prometheus': 'observability/metrics/prometheus.md' - 'StatsD': 'observability/metrics/statsd.md' @@ -130,7 +130,7 @@ nav: - 'Overview': 'observability/tracing/overview.md' - 'Jaeger': 'observability/tracing/jaeger.md' - 'Zipkin': 'observability/tracing/zipkin.md' - - 'DataDog': 'observability/tracing/datadog.md' + - 'Datadog': 'observability/tracing/datadog.md' - 'Instana': 'observability/tracing/instana.md' - 'Haystack': 'observability/tracing/haystack.md' - 'User Guides': @@ -143,6 +143,8 @@ nav: - 'TLS Challenge': 'user-guides/docker-compose/acme-tls/index.md' - 'HTTP Challenge': 'user-guides/docker-compose/acme-http/index.md' - 'DNS Challenge': 'user-guides/docker-compose/acme-dns/index.md' + - 'Migration': + - 'Traefik v1 to v2': 'migration/v1-to-v2.md' - 'Contributing': - 'Thank You!': 'contributing/thank-you.md' - 'Submitting Issues': 'contributing/submitting-issues.md' diff --git a/exp.Dockerfile b/exp.Dockerfile index 6f59b73e3..b12c512b8 100644 --- a/exp.Dockerfile +++ b/exp.Dockerfile @@ -12,7 +12,7 @@ RUN yarn install RUN npm run build # BUILD -FROM golang:1.13rc1-alpine as gobuild +FROM golang:1.13-alpine as gobuild RUN apk --update upgrade \ && apk --no-cache --no-progress add git mercurial bash gcc musl-dev curl tar ca-certificates tzdata \ diff --git a/go.mod b/go.mod index d3304053d..c910ddf8f 100644 --- a/go.mod +++ b/go.mod @@ -43,9 +43,9 @@ require ( github.com/go-check/check v0.0.0-00010101000000-000000000000 github.com/go-kit/kit v0.9.0 github.com/golang/protobuf v1.3.2 - github.com/google/go-github/v27 v27.0.4 + github.com/google/go-github/v28 v28.0.0 github.com/googleapis/gnostic v0.1.0 // indirect - github.com/gorilla/mux v1.6.2 + github.com/gorilla/mux v1.7.3 github.com/gorilla/websocket v1.4.0 github.com/hashicorp/go-version v1.2.0 github.com/huandu/xstrings v1.2.0 // indirect @@ -65,10 +65,10 @@ require ( github.com/opencontainers/go-digest v1.0.0-rc1 // indirect github.com/opencontainers/image-spec v1.0.1 // indirect github.com/opencontainers/runc v1.0.0-rc8 // indirect - github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 // indirect github.com/opentracing/basictracer-go v1.0.0 // indirect github.com/opentracing/opentracing-go v1.1.0 - github.com/openzipkin-contrib/zipkin-go-opentracing v0.3.5 + github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.3 + github.com/openzipkin/zipkin-go v0.2.1 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/philhofer/fwd v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 diff --git a/go.sum b/go.sum index cbaba2ca0..aa48a1794 100644 --- a/go.sum +++ b/go.sum @@ -161,6 +161,7 @@ github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZi github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk= github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -193,6 +194,7 @@ github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI= @@ -218,8 +220,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-github/v27 v27.0.4 h1:N/EEqsvJLgqTbepTiMBz+12KhwLovv6YvwpRezd+4Fg= -github.com/google/go-github/v27 v27.0.4/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0= +github.com/google/go-github/v28 v28.0.0 h1:+UjHI4+1W/vsXR4jJBWt0ZA74XHbvt5yBAvsf1M3bgM= +github.com/google/go-github/v28 v28.0.0/go.mod h1:+5GboIspo7F0NG2qsvfYh7en6F3EK37uyqv+c35AR3s= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= @@ -317,6 +319,7 @@ github.com/linode/linodego v0.10.0 h1:AMdb82HVgY8o3mjBXJcUv9B+fnJjfDMn2rNRGbX+jv github.com/linode/linodego v0.10.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA= github.com/looplab/fsm v0.1.0 h1:Qte7Zdn/5hBNbXzP7yxVU4OIFHWXBovyTT2LaBTyC20= github.com/looplab/fsm v0.1.0/go.mod h1:m2VaOfDHxqXBBMgc26m6yUOwkFn8H2AlJDE+jd/uafI= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailgun/timetools v0.0.0-20141028012446-7e6055773c51 h1:Kg/NPZLLC3aAFr1YToMs98dbCdhootQ1hZIvZU28hAQ= @@ -383,9 +386,11 @@ github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7l github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.3.5 h1:82Tnq9OJpn+h5xgGpss5/mOv3KXdjtkdorFSOUusjM8= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.3.5/go.mod h1:uVHyebswE1cCXr2A73cRM2frx5ld1RJUCJkFNZ90ZiI= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.3 h1:XudIMByQMXJ6oDHy4SipNyo35LxjA69Z7v1nL0aAZvA= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.3/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1 h1:noL5/5Uf1HpVl3wNsfkZhIKbSWCVi5jgqkONNx8PXcA= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/oracle/oci-go-sdk v7.0.0+incompatible h1:oj5ESjXwwkFRdhZSnPlShvLWYdt/IZ65RQxveYM3maA= github.com/oracle/oci-go-sdk v7.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014 h1:37VE5TYj2m/FLA9SNr4z0+A0JefvTmR60Zwf8XSEV7c= @@ -396,6 +401,7 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -441,6 +447,7 @@ github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2 github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -604,6 +611,7 @@ google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3 google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.22.1 h1:/7cs52RnTJmD43s3uxzlq2U7nqVTd/37viQwMrMNlOM= diff --git a/integration/access_log_test.go b/integration/access_log_test.go index 5017b2132..ea21fe500 100644 --- a/integration/access_log_test.go +++ b/integration/access_log_test.go @@ -309,8 +309,6 @@ func (s *AccessLogSuite) TestAccessLogFrontendRedirect(c *check.C) { } func (s *AccessLogSuite) TestAccessLogRateLimit(c *check.C) { - c.Skip("RateLimit is disable for now") - ensureWorkingDirectoryIsClean() expected := []accessLogValue{ diff --git a/integration/fixtures/access_log_config.toml b/integration/fixtures/access_log_config.toml index 19f5416e3..68e653788 100644 --- a/integration/fixtures/access_log_config.toml +++ b/integration/fixtures/access_log_config.toml @@ -22,6 +22,7 @@ address = ":8008" [api] + insecure = true [providers] [providers.docker] diff --git a/integration/fixtures/acme/acme_base.toml b/integration/fixtures/acme/acme_base.toml index ae8b2be83..9a8242e63 100644 --- a/integration/fixtures/acme/acme_base.toml +++ b/integration/fixtures/acme/acme_base.toml @@ -31,6 +31,7 @@ {{end}} [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/acme/acme_domains.toml b/integration/fixtures/acme/acme_domains.toml index 72f047acf..e9b150eb3 100644 --- a/integration/fixtures/acme/acme_domains.toml +++ b/integration/fixtures/acme/acme_domains.toml @@ -31,6 +31,7 @@ {{end}} [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/acme/acme_multiple_resolvers.toml b/integration/fixtures/acme/acme_multiple_resolvers.toml index 73313d3f3..6da4a2a48 100644 --- a/integration/fixtures/acme/acme_multiple_resolvers.toml +++ b/integration/fixtures/acme/acme_multiple_resolvers.toml @@ -31,6 +31,7 @@ {{end}} [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/acme/acme_tcp.toml b/integration/fixtures/acme/acme_tcp.toml index c016a4139..3bf7e3721 100644 --- a/integration/fixtures/acme/acme_tcp.toml +++ b/integration/fixtures/acme/acme_tcp.toml @@ -31,6 +31,7 @@ {{end}} [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/acme/acme_tls.toml b/integration/fixtures/acme/acme_tls.toml index 2319974bd..990ba69d4 100644 --- a/integration/fixtures/acme/acme_tls.toml +++ b/integration/fixtures/acme/acme_tls.toml @@ -31,6 +31,7 @@ {{end}} [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/acme/acme_tls_dynamic.toml b/integration/fixtures/acme/acme_tls_dynamic.toml index eac99adc1..832ae1f27 100644 --- a/integration/fixtures/acme/acme_tls_dynamic.toml +++ b/integration/fixtures/acme/acme_tls_dynamic.toml @@ -31,6 +31,7 @@ {{end}} [api] + insecure = true [providers] [providers.file] diff --git a/integration/fixtures/acme/acme_tls_multiple_entrypoints.toml b/integration/fixtures/acme/acme_tls_multiple_entrypoints.toml index f4601b695..3ffbdfad8 100644 --- a/integration/fixtures/acme/acme_tls_multiple_entrypoints.toml +++ b/integration/fixtures/acme/acme_tls_multiple_entrypoints.toml @@ -34,3 +34,4 @@ {{end}} [api] + insecure = true diff --git a/integration/fixtures/docker/minimal.toml b/integration/fixtures/docker/minimal.toml index 4ba52559e..8e6024d84 100644 --- a/integration/fixtures/docker/minimal.toml +++ b/integration/fixtures/docker/minimal.toml @@ -10,6 +10,7 @@ address = ":8000" [api] + insecure = true [providers] [providers.docker] diff --git a/integration/fixtures/docker/simple.toml b/integration/fixtures/docker/simple.toml index 630fd5549..a2c0de53f 100644 --- a/integration/fixtures/docker/simple.toml +++ b/integration/fixtures/docker/simple.toml @@ -10,6 +10,7 @@ address = ":8000" [api] + insecure = true [providers] [providers.docker] diff --git a/integration/fixtures/grpc/config.toml b/integration/fixtures/grpc/config.toml index 5ebf5e946..1e8aac3a0 100644 --- a/integration/fixtures/grpc/config.toml +++ b/integration/fixtures/grpc/config.toml @@ -13,6 +13,7 @@ address = ":4443" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/grpc/config_h2c.toml b/integration/fixtures/grpc/config_h2c.toml index 8dfcc99da..683b72f58 100644 --- a/integration/fixtures/grpc/config_h2c.toml +++ b/integration/fixtures/grpc/config_h2c.toml @@ -10,6 +10,7 @@ address = ":8081" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/grpc/config_h2c_termination.toml b/integration/fixtures/grpc/config_h2c_termination.toml index 10fbdaeaa..f4188fbe7 100644 --- a/integration/fixtures/grpc/config_h2c_termination.toml +++ b/integration/fixtures/grpc/config_h2c_termination.toml @@ -10,6 +10,7 @@ address = ":4443" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/grpc/config_insecure.toml b/integration/fixtures/grpc/config_insecure.toml index 417ebdd22..997a23c5b 100644 --- a/integration/fixtures/grpc/config_insecure.toml +++ b/integration/fixtures/grpc/config_insecure.toml @@ -13,6 +13,7 @@ address = ":4443" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/grpc/config_retry.toml b/integration/fixtures/grpc/config_retry.toml index dfd316ea5..e3d5efe99 100644 --- a/integration/fixtures/grpc/config_retry.toml +++ b/integration/fixtures/grpc/config_retry.toml @@ -13,6 +13,7 @@ address = ":4443" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/healthcheck/multiple-entrypoints.toml b/integration/fixtures/healthcheck/multiple-entrypoints.toml index 3c1d60f3c..12bab7274 100644 --- a/integration/fixtures/healthcheck/multiple-entrypoints.toml +++ b/integration/fixtures/healthcheck/multiple-entrypoints.toml @@ -12,6 +12,7 @@ address = ":9000" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/healthcheck/port_overload.toml b/integration/fixtures/healthcheck/port_overload.toml index 40b24391b..eb7accedd 100644 --- a/integration/fixtures/healthcheck/port_overload.toml +++ b/integration/fixtures/healthcheck/port_overload.toml @@ -10,6 +10,7 @@ address = ":8000" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/healthcheck/simple.toml b/integration/fixtures/healthcheck/simple.toml index 92bc1e4d7..a2338a8b0 100644 --- a/integration/fixtures/healthcheck/simple.toml +++ b/integration/fixtures/healthcheck/simple.toml @@ -10,6 +10,7 @@ address = ":8000" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/https/clientca/https_1ca1config.toml b/integration/fixtures/https/clientca/https_1ca1config.toml index f52896e55..951fa703d 100644 --- a/integration/fixtures/https/clientca/https_1ca1config.toml +++ b/integration/fixtures/https/clientca/https_1ca1config.toml @@ -10,6 +10,7 @@ address = ":4443" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/https/clientca/https_2ca1config.toml b/integration/fixtures/https/clientca/https_2ca1config.toml index 948374c04..1ccc98954 100644 --- a/integration/fixtures/https/clientca/https_2ca1config.toml +++ b/integration/fixtures/https/clientca/https_2ca1config.toml @@ -10,6 +10,7 @@ address = ":4443" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/https/clientca/https_2ca2config.toml b/integration/fixtures/https/clientca/https_2ca2config.toml index 5757ac602..43725bcb2 100644 --- a/integration/fixtures/https/clientca/https_2ca2config.toml +++ b/integration/fixtures/https/clientca/https_2ca2config.toml @@ -10,6 +10,7 @@ address = ":4443" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/https/dynamic_https_sni.toml b/integration/fixtures/https/dynamic_https_sni.toml index 2a7ff45fa..20b82c0dd 100644 --- a/integration/fixtures/https/dynamic_https_sni.toml +++ b/integration/fixtures/https/dynamic_https_sni.toml @@ -13,6 +13,7 @@ address = ":8443" [api] + insecure = true [providers] [providers.file] diff --git a/integration/fixtures/https/dynamic_https_sni_default_cert.toml b/integration/fixtures/https/dynamic_https_sni_default_cert.toml index 05f3c1b6d..d8e6f8d45 100644 --- a/integration/fixtures/https/dynamic_https_sni_default_cert.toml +++ b/integration/fixtures/https/dynamic_https_sni_default_cert.toml @@ -10,6 +10,7 @@ address = ":4443" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/https/https_redirect.toml b/integration/fixtures/https/https_redirect.toml index e9e9f78bc..6ee48d0ca 100644 --- a/integration/fixtures/https/https_redirect.toml +++ b/integration/fixtures/https/https_redirect.toml @@ -13,6 +13,7 @@ address = ":8443" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" @@ -150,9 +151,9 @@ [http.middlewares.foo-slash-add-prefix.addPrefix] prefix = "/foo/" [http.middlewares.id-strip-regex-prefix.stripPrefixRegex] - regex = ["/{id:[a-z]+}"] + regex = ["/[a-z]+"] [http.middlewares.id-slash-strip-regex-prefix.stripPrefixRegex] - regex = ["/{id:[a-z]+}/"] + regex = ["/[a-z]+/"] [http.middlewares.api-regex-replace.replacePathRegex] regex = "/api" replacement = "/" diff --git a/integration/fixtures/https/https_sni.toml b/integration/fixtures/https/https_sni.toml index c5212af6a..a847d3de0 100644 --- a/integration/fixtures/https/https_sni.toml +++ b/integration/fixtures/https/https_sni.toml @@ -10,6 +10,7 @@ address = ":4443" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/https/https_sni_case_insensitive_dynamic.toml b/integration/fixtures/https/https_sni_case_insensitive_dynamic.toml index 614a54ee1..9e63fff4b 100644 --- a/integration/fixtures/https/https_sni_case_insensitive_dynamic.toml +++ b/integration/fixtures/https/https_sni_case_insensitive_dynamic.toml @@ -10,6 +10,7 @@ address = ":4443" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/https/https_sni_default_cert.toml b/integration/fixtures/https/https_sni_default_cert.toml index 05f3c1b6d..d8e6f8d45 100644 --- a/integration/fixtures/https/https_sni_default_cert.toml +++ b/integration/fixtures/https/https_sni_default_cert.toml @@ -10,6 +10,7 @@ address = ":4443" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/https/https_sni_strict.toml b/integration/fixtures/https/https_sni_strict.toml index 9ada0a5e6..09f442c00 100644 --- a/integration/fixtures/https/https_sni_strict.toml +++ b/integration/fixtures/https/https_sni_strict.toml @@ -10,6 +10,7 @@ address = ":4443" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/https/https_tls_options.toml b/integration/fixtures/https/https_tls_options.toml index 4e7dfde43..9bd67c277 100644 --- a/integration/fixtures/https/https_tls_options.toml +++ b/integration/fixtures/https/https_tls_options.toml @@ -10,6 +10,7 @@ address = ":4443" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/https/rootcas/https.toml b/integration/fixtures/https/rootcas/https.toml index 54b502faa..2525c7473 100644 --- a/integration/fixtures/https/rootcas/https.toml +++ b/integration/fixtures/https/rootcas/https.toml @@ -29,6 +29,7 @@ fblo6RBxUQ== address = ":8081" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/https/rootcas/https_with_file.toml b/integration/fixtures/https/rootcas/https_with_file.toml index 21e957df8..62a79ac11 100644 --- a/integration/fixtures/https/rootcas/https_with_file.toml +++ b/integration/fixtures/https/rootcas/https_with_file.toml @@ -14,6 +14,7 @@ address = ":8081" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/k8s/04-ingressroute.yml b/integration/fixtures/k8s/04-ingressroute.yml index 596073206..85643e638 100644 --- a/integration/fixtures/k8s/04-ingressroute.yml +++ b/integration/fixtures/k8s/04-ingressroute.yml @@ -1,5 +1,17 @@ apiVersion: traefik.containo.us/v1alpha1 kind: Middleware +metadata: + name: mychain + namespace: default + +spec: + chain: + middlewares: + - name: stripprefix + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware metadata: name: stripprefix namespace: default @@ -26,4 +38,4 @@ spec: - name: whoami port: 80 middlewares: - - name: stripprefix + - name: mychain diff --git a/integration/fixtures/k8s_crd.toml b/integration/fixtures/k8s_crd.toml index 21bfa9855..891e3f13e 100644 --- a/integration/fixtures/k8s_crd.toml +++ b/integration/fixtures/k8s_crd.toml @@ -6,6 +6,7 @@ level = "DEBUG" [api] + insecure = true [entryPoints] [entryPoints.footcp] diff --git a/integration/fixtures/k8s_default.toml b/integration/fixtures/k8s_default.toml index 2b39f0f65..ede432aed 100644 --- a/integration/fixtures/k8s_default.toml +++ b/integration/fixtures/k8s_default.toml @@ -3,6 +3,7 @@ sendAnonymousUsage = false [api] + insecure = true [log] level = "DEBUG" diff --git a/integration/fixtures/marathon/simple.toml b/integration/fixtures/marathon/simple.toml index 81082bfe4..f8b4e7266 100644 --- a/integration/fixtures/marathon/simple.toml +++ b/integration/fixtures/marathon/simple.toml @@ -12,6 +12,7 @@ address = ":9090" [api] + insecure = true [providers] [providers.marathon] diff --git a/integration/fixtures/mirror.toml b/integration/fixtures/mirror.toml index a5a07cce8..9448eec7a 100644 --- a/integration/fixtures/mirror.toml +++ b/integration/fixtures/mirror.toml @@ -3,6 +3,7 @@ sendAnonymousUsage = false [api] + insecure = true [log] level = "DEBUG" diff --git a/integration/fixtures/multiple_provider.toml b/integration/fixtures/multiple_provider.toml index 0ac809de2..e5f972d38 100644 --- a/integration/fixtures/multiple_provider.toml +++ b/integration/fixtures/multiple_provider.toml @@ -10,6 +10,7 @@ address = ":8000" [api] + insecure = true [providers] [providers.docker] diff --git a/integration/fixtures/multiprovider.toml b/integration/fixtures/multiprovider.toml index 23c0161ae..bc59d7fab 100644 --- a/integration/fixtures/multiprovider.toml +++ b/integration/fixtures/multiprovider.toml @@ -6,6 +6,7 @@ level = "DEBUG" [api] + insecure = true [entryPoints] [entryPoints.web] @@ -13,6 +14,7 @@ [providers] [providers.rest] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/proxy-protocol/with.toml b/integration/fixtures/proxy-protocol/with.toml index f16361986..c47e97dc5 100644 --- a/integration/fixtures/proxy-protocol/with.toml +++ b/integration/fixtures/proxy-protocol/with.toml @@ -12,6 +12,7 @@ trustedIPs = ["{{.HaproxyIP}}"] [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/proxy-protocol/without.toml b/integration/fixtures/proxy-protocol/without.toml index ef95ca5c7..71b982486 100644 --- a/integration/fixtures/proxy-protocol/without.toml +++ b/integration/fixtures/proxy-protocol/without.toml @@ -12,6 +12,7 @@ trustedIPs = ["1.2.3.4"] [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/ratelimit/simple.toml b/integration/fixtures/ratelimit/simple.toml index 7bdb5a367..aed8d0293 100644 --- a/integration/fixtures/ratelimit/simple.toml +++ b/integration/fixtures/ratelimit/simple.toml @@ -3,6 +3,7 @@ sendAnonymousUsage = false [api] + insecure = true [log] level = "DEBUG" diff --git a/integration/fixtures/rest/simple.toml b/integration/fixtures/rest/simple.toml index 575c6e2e4..8bf7ff14c 100644 --- a/integration/fixtures/rest/simple.toml +++ b/integration/fixtures/rest/simple.toml @@ -10,6 +10,8 @@ address = ":8000" [api] + insecure = true [providers] [providers.rest] + insecure = true diff --git a/integration/fixtures/rest/simple_secure.toml b/integration/fixtures/rest/simple_secure.toml new file mode 100644 index 000000000..312c8f628 --- /dev/null +++ b/integration/fixtures/rest/simple_secure.toml @@ -0,0 +1,27 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + +[entryPoints] + [entryPoints.web] + address = ":8000" + +[api] + insecure = true + +[providers.rest] + +[providers.file] + filename = "{{ .SelfFilename }}" + +[http.routers.rest] + rule="PathPrefix(`/secure`)" + service="rest@internal" + middlewares=["strip"] + +[http.middlewares.strip.stripPrefix] + prefixes = [ "/secure" ] + diff --git a/integration/fixtures/retry/simple.toml b/integration/fixtures/retry/simple.toml index fea2a4a60..c9e287ae3 100644 --- a/integration/fixtures/retry/simple.toml +++ b/integration/fixtures/retry/simple.toml @@ -10,6 +10,7 @@ address = ":8000" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/router_errors.toml b/integration/fixtures/router_errors.toml index 335339e2b..db9429e3c 100644 --- a/integration/fixtures/router_errors.toml +++ b/integration/fixtures/router_errors.toml @@ -10,6 +10,7 @@ address = ":4443" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" @@ -17,6 +18,11 @@ ## dynamic configuration ## [http.routers] + [http.routers.router3] + entrypoints=["unknown-entrypoint"] + service = "service1" + rule = "Host(`mydomain.com`)" + [http.routers.router4] service = "service1" rule = "Host(`snitest.net`)" diff --git a/integration/fixtures/service_errors.toml b/integration/fixtures/service_errors.toml index 3bdc495bb..cb0997a83 100644 --- a/integration/fixtures/service_errors.toml +++ b/integration/fixtures/service_errors.toml @@ -10,6 +10,7 @@ address = ":4443" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/simple_auth.toml b/integration/fixtures/simple_auth.toml index 51476e428..1edc7ea6b 100644 --- a/integration/fixtures/simple_auth.toml +++ b/integration/fixtures/simple_auth.toml @@ -13,6 +13,7 @@ address = ":8001" [api] + insecure = true middlewares = ["authentication@file"] [ping] diff --git a/integration/fixtures/simple_hostresolver.toml b/integration/fixtures/simple_hostresolver.toml index 77627f89b..01789610a 100644 --- a/integration/fixtures/simple_hostresolver.toml +++ b/integration/fixtures/simple_hostresolver.toml @@ -10,6 +10,7 @@ address = ":8000" [api] + insecure = true [providers] [providers.docker] diff --git a/integration/fixtures/simple_secure_api.toml b/integration/fixtures/simple_secure_api.toml new file mode 100644 index 000000000..f10759fbf --- /dev/null +++ b/integration/fixtures/simple_secure_api.toml @@ -0,0 +1,25 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[entryPoints] + [entryPoints.web] + address = ":8000" + + [entryPoints.traefik] + address = ":8080" + + +[api] + +[providers.file] + filename = "{{ .SelfFilename }}" + +[http.routers.api] + rule="PathPrefix(`/secure`)" + service="api@internal" + middlewares=["strip"] + +[http.middlewares.strip.stripPrefix] + prefixes = [ "/secure" ] + diff --git a/integration/fixtures/simple_stats.toml b/integration/fixtures/simple_stats.toml index 4283299db..eeb4c2533 100644 --- a/integration/fixtures/simple_stats.toml +++ b/integration/fixtures/simple_stats.toml @@ -10,6 +10,7 @@ address = ":8000" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/simple_web.toml b/integration/fixtures/simple_web.toml index 8751d9479..1d17ccb57 100644 --- a/integration/fixtures/simple_web.toml +++ b/integration/fixtures/simple_web.toml @@ -10,3 +10,4 @@ address = ":8000" [api] + insecure = true diff --git a/integration/fixtures/simple_whitelist.toml b/integration/fixtures/simple_whitelist.toml index bef0e455d..03fa451e4 100644 --- a/integration/fixtures/simple_whitelist.toml +++ b/integration/fixtures/simple_whitelist.toml @@ -9,9 +9,10 @@ [entryPoints.web] address = ":8000" [entryPoints.web.ForwardedHeaders] - insecure=true + insecure = true [api] + insecure = true [providers] [providers.docker] diff --git a/integration/fixtures/tcp/catch-all-no-tls-with-https.toml b/integration/fixtures/tcp/catch-all-no-tls-with-https.toml index cc65bda34..1f9a18aaa 100644 --- a/integration/fixtures/tcp/catch-all-no-tls-with-https.toml +++ b/integration/fixtures/tcp/catch-all-no-tls-with-https.toml @@ -10,6 +10,7 @@ address = ":8093" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/tcp/catch-all-no-tls.toml b/integration/fixtures/tcp/catch-all-no-tls.toml index 0132822ee..3e79f8f79 100644 --- a/integration/fixtures/tcp/catch-all-no-tls.toml +++ b/integration/fixtures/tcp/catch-all-no-tls.toml @@ -10,6 +10,7 @@ address = ":8093" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/tcp/mixed.toml b/integration/fixtures/tcp/mixed.toml index e673276ad..ef25965ec 100644 --- a/integration/fixtures/tcp/mixed.toml +++ b/integration/fixtures/tcp/mixed.toml @@ -10,6 +10,7 @@ address = ":8093" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/tcp/multi-tls-options.toml b/integration/fixtures/tcp/multi-tls-options.toml index b36dae76a..1dee6c285 100644 --- a/integration/fixtures/tcp/multi-tls-options.toml +++ b/integration/fixtures/tcp/multi-tls-options.toml @@ -10,6 +10,7 @@ address = ":8093" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/tcp/non-tls-fallback.toml b/integration/fixtures/tcp/non-tls-fallback.toml index 45ff3caa7..ed15f1072 100644 --- a/integration/fixtures/tcp/non-tls-fallback.toml +++ b/integration/fixtures/tcp/non-tls-fallback.toml @@ -10,6 +10,7 @@ address = ":8093" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/tcp/non-tls.toml b/integration/fixtures/tcp/non-tls.toml index 6c7acf6df..37b840825 100644 --- a/integration/fixtures/tcp/non-tls.toml +++ b/integration/fixtures/tcp/non-tls.toml @@ -10,6 +10,7 @@ address = ":8093" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/timeout/forwarding_timeouts.toml b/integration/fixtures/timeout/forwarding_timeouts.toml index 9df89a175..0dc5d4933 100644 --- a/integration/fixtures/timeout/forwarding_timeouts.toml +++ b/integration/fixtures/timeout/forwarding_timeouts.toml @@ -17,6 +17,7 @@ format = "json" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/timeout/keepalive.toml b/integration/fixtures/timeout/keepalive.toml index 4a8924362..f3d7c3df7 100644 --- a/integration/fixtures/timeout/keepalive.toml +++ b/integration/fixtures/timeout/keepalive.toml @@ -13,6 +13,7 @@ address = ":8000" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/tlsclientheaders/simple.toml b/integration/fixtures/tlsclientheaders/simple.toml index 5e6af461d..207ad6006 100644 --- a/integration/fixtures/tlsclientheaders/simple.toml +++ b/integration/fixtures/tlsclientheaders/simple.toml @@ -13,6 +13,7 @@ address = ":8443" [api] + insecure = true [providers] [providers.docker] diff --git a/integration/fixtures/tracing/simple-jaeger-collector.toml b/integration/fixtures/tracing/simple-jaeger-collector.toml index 14bb2b42a..d10d5b914 100644 --- a/integration/fixtures/tracing/simple-jaeger-collector.toml +++ b/integration/fixtures/tracing/simple-jaeger-collector.toml @@ -6,6 +6,7 @@ level = "DEBUG" [api] + insecure = true [entryPoints] [entryPoints.web] @@ -28,7 +29,7 @@ [http.routers] [http.routers.router1] Service = "service1" - Middlewares = ["retry", "ratelimit"] + Middlewares = ["retry", "ratelimit-1"] Rule = "Path(`/ratelimit`)" [http.routers.router2] Service = "service2" @@ -44,16 +45,9 @@ attempts = 3 [http.middlewares.basic-auth.basicAuth] users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"] - [http.middlewares.ratelimit.rateLimit] - extractorfunc = "client.ip" - [http.middlewares.ratelimit.rateLimit.rateSet.rateset1] - period = "60s" - average = 4 - burst = 5 - [http.middlewares.ratelimit.rateLimit.rateSet.rateset2] - period = "3s" - average = 1 - burst = 2 + [http.middlewares.ratelimit-1.rateLimit] + average = 1 + burst = 2 [http.services] diff --git a/integration/fixtures/tracing/simple-jaeger.toml b/integration/fixtures/tracing/simple-jaeger.toml index 3bc8a08d3..fea514f79 100644 --- a/integration/fixtures/tracing/simple-jaeger.toml +++ b/integration/fixtures/tracing/simple-jaeger.toml @@ -6,6 +6,7 @@ level = "DEBUG" [api] + insecure = true [entryPoints] [entryPoints.web] @@ -27,7 +28,7 @@ [http.routers] [http.routers.router1] Service = "service1" - Middlewares = ["retry", "ratelimit"] + Middlewares = ["retry", "ratelimit-1"] Rule = "Path(`/ratelimit`)" [http.routers.router2] Service = "service2" @@ -43,17 +44,9 @@ attempts = 3 [http.middlewares.basic-auth.basicAuth] users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"] - [http.middlewares.ratelimit.rateLimit] - extractorfunc = "client.ip" - [http.middlewares.ratelimit.rateLimit.rateSet.rateset1] - period = "60s" - average = 4 - burst = 5 - [http.middlewares.ratelimit.rateLimit.rateSet.rateset2] - period = "3s" - average = 1 - burst = 2 - + [http.middlewares.ratelimit-1.rateLimit] + average = 1 + burst = 2 [http.services] [http.services.service1] diff --git a/integration/fixtures/tracing/simple-zipkin.toml b/integration/fixtures/tracing/simple-zipkin.toml index 46f8ff697..43842b70e 100644 --- a/integration/fixtures/tracing/simple-zipkin.toml +++ b/integration/fixtures/tracing/simple-zipkin.toml @@ -6,6 +6,7 @@ level = "DEBUG" [api] + insecure = true [entryPoints] [entryPoints.web] @@ -14,8 +15,7 @@ [tracing] servicename = "tracing" [tracing.zipkin] - httpEndpoint = "http://{{.IP}}:9411/api/v1/spans" - debug = true + httpEndpoint = "http://{{.IP}}:9411/api/v2/spans" [providers.file] filename = "{{ .SelfFilename }}" @@ -25,7 +25,7 @@ [http.routers] [http.routers.router1] service = "service1" - middlewares = ["retry", "ratelimit"] + middlewares = ["retry", "ratelimit-1"] rule = "Path(`/ratelimit`)" [http.routers.router2] service = "service2" @@ -41,16 +41,9 @@ attempts = 3 [http.middlewares.basic-auth.basicAuth] users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"] - [http.middlewares.ratelimit.rateLimit] - extractorfunc = "client.ip" - [http.middlewares.ratelimit.rateLimit.rateSet.rateset1] - period = "60s" - average = 4 - burst = 5 - [http.middlewares.ratelimit.rateLimit.rateSet.rateset2] - period = "3s" - average = 1 - burst = 2 + [http.middlewares.ratelimit-1.rateLimit] + average = 1 + burst = 2 [http.services] [http.services.service1] diff --git a/integration/fixtures/traefik_log_config.toml b/integration/fixtures/traefik_log_config.toml index e7b077c61..3ca5cc771 100644 --- a/integration/fixtures/traefik_log_config.toml +++ b/integration/fixtures/traefik_log_config.toml @@ -14,6 +14,7 @@ address = ":8000" [api] + insecure = true dashboard = false [providers] diff --git a/integration/fixtures/websocket/config.toml b/integration/fixtures/websocket/config.toml index 3e357c057..83a499ce7 100644 --- a/integration/fixtures/websocket/config.toml +++ b/integration/fixtures/websocket/config.toml @@ -10,6 +10,7 @@ address = ":8000" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/websocket/config_https.toml b/integration/fixtures/websocket/config_https.toml index c878e1ef3..91549eeaa 100644 --- a/integration/fixtures/websocket/config_https.toml +++ b/integration/fixtures/websocket/config_https.toml @@ -13,6 +13,7 @@ address = ":8000" [api] + insecure = true [providers.file] filename = "{{ .SelfFilename }}" diff --git a/integration/fixtures/wrr.toml b/integration/fixtures/wrr.toml index ad63ceb0d..67c6801fc 100644 --- a/integration/fixtures/wrr.toml +++ b/integration/fixtures/wrr.toml @@ -3,6 +3,7 @@ sendAnonymousUsage = false [api] + insecure = true [log] level = "DEBUG" diff --git a/integration/fixtures/wrr_sticky.toml b/integration/fixtures/wrr_sticky.toml index 4181dde54..0d63558c5 100644 --- a/integration/fixtures/wrr_sticky.toml +++ b/integration/fixtures/wrr_sticky.toml @@ -3,6 +3,7 @@ sendAnonymousUsage = false [api] + insecure = true [log] level = "DEBUG" diff --git a/integration/https_test.go b/integration/https_test.go index 0d35b9570..efb7fb017 100644 --- a/integration/https_test.go +++ b/integration/https_test.go @@ -501,8 +501,8 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthentication(c *check.C) { // TestWithClientCertificateAuthentication // Use two CA:s and test that clients with client signed by either of them can connect func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAs(c *check.C) { - server1 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { rw.Write([]byte("server1")) })) - server2 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { rw.Write([]byte("server2")) })) + server1 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { _, _ = rw.Write([]byte("server1")) })) + server2 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { _, _ = rw.Write([]byte("server2")) })) defer func() { server1.Close() server2.Close() @@ -598,8 +598,8 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAs(c *check // TestWithClientCertificateAuthentication // Use two CA:s in two different files and test that clients with client signed by either of them can connect func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAsMultipleFiles(c *check.C) { - server1 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { rw.Write([]byte("server1")) })) - server2 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { rw.Write([]byte("server2")) })) + server1 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { _, _ = rw.Write([]byte("server1")) })) + server2 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { _, _ = rw.Write([]byte("server2")) })) defer func() { server1.Close() server2.Close() diff --git a/integration/resources/compose/access_log.yml b/integration/resources/compose/access_log.yml index d07f6d8a1..57215aef1 100644 --- a/integration/resources/compose/access_log.yml +++ b/integration/resources/compose/access_log.yml @@ -55,19 +55,17 @@ frontendRedirect: - traefik.http.middlewares.redirecthttp.redirectScheme.scheme=http - traefik.http.middlewares.redirecthttp.redirectScheme.port=8000 - traefik.http.services.service3.loadbalancer.server.port=80 -# TODO: disable temporarily (rateLimit) -#rateLimit: -# image: containous/whoami -# labels: -# - traefik.enable=true -# - traefik.http.routers.rt-rateLimit.entryPoints=httpRateLimit -# - traefik.http.routers.rt-rateLimit.rule=Host("ratelimit.docker.local") -# - traefik.http.routers.rt-rateLimit.middlewares=rate -# - traefik.http.middlewares.rate.ratelimit.extractorfunc=client.ip -# - traefik.http.middlewares.rate.ratelimit.rateset.Rate0.average=1 -# - traefik.http.middlewares.rate.ratelimit.rateset.Rate0.burst=2 -# - traefik.http.middlewares.rate.ratelimit.rateset.Rate0.period=10s -# - traefik.http.services.service3.loadbalancer.server.port=80 +rateLimit: + image: containous/whoami + labels: + - traefik.enable=true + - traefik.http.routers.rt-rateLimit.entryPoints=httpRateLimit + - traefik.http.routers.rt-rateLimit.rule=Host("ratelimit.docker.local") + - traefik.http.routers.rt-rateLimit.middlewares=rate + - traefik.http.middlewares.rate.ratelimit + - traefik.http.middlewares.rate.ratelimit.average=1 + - traefik.http.middlewares.rate.ratelimit.burst=2 + - traefik.http.services.service3.loadbalancer.server.port=80 frontendWhitelist: image: containous/whoami labels: diff --git a/integration/resources/compose/tracing.yml b/integration/resources/compose/tracing.yml index 51f3eb8f0..2e3f1de7b 100644 --- a/integration/resources/compose/tracing.yml +++ b/integration/resources/compose/tracing.yml @@ -1,5 +1,5 @@ zipkin: - image: openzipkin/zipkin:2.12.6 + image: openzipkin/zipkin:2.16.2 environment: STORAGE_TYPE: mem JAVA_OPTS: -Dlogging.level.zipkin=DEBUG diff --git a/integration/rest_test.go b/integration/rest_test.go index 692542c9f..d87092571 100644 --- a/integration/rest_test.go +++ b/integration/rest_test.go @@ -4,6 +4,8 @@ import ( "bytes" "encoding/json" "net/http" + "os" + "strings" "time" "github.com/containous/traefik/v2/integration/try" @@ -20,7 +22,7 @@ func (s *RestSuite) SetUpSuite(c *check.C) { s.composeProject.Start(c) } -func (s *RestSuite) TestSimpleConfiguration(c *check.C) { +func (s *RestSuite) TestSimpleConfigurationInsecure(c *check.C) { cmd, display := s.traefikCmd(withConfigFile("fixtures/rest/simple.toml")) defer display(c) @@ -110,3 +112,107 @@ func (s *RestSuite) TestSimpleConfiguration(c *check.C) { c.Assert(err, checker.IsNil) } } + +func (s *RestSuite) TestSimpleConfiguration(c *check.C) { + file := s.adaptFile(c, "fixtures/rest/simple_secure.toml", struct{}{}) + defer os.Remove(file) + + cmd, display := s.traefikCmd(withConfigFile(file)) + + defer display(c) + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + // Expected a 404 as we did not configure anything. + err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) + c.Assert(err, checker.IsNil) + + err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2000*time.Millisecond, try.BodyContains("PathPrefix(`/secure`)")) + c.Assert(err, checker.IsNil) + + request, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8080/api/providers/rest", strings.NewReader("{}")) + c.Assert(err, checker.IsNil) + + response, err := http.DefaultClient.Do(request) + c.Assert(err, checker.IsNil) + c.Assert(response.StatusCode, checker.Equals, http.StatusNotFound) + + testCase := []struct { + desc string + config *dynamic.Configuration + ruleMatch string + }{ + { + desc: "deploy http configuration", + config: &dynamic.Configuration{ + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "router1": { + EntryPoints: []string{"web"}, + Middlewares: []string{}, + Service: "service1", + Rule: "PathPrefix(`/`)", + }, + }, + Services: map[string]*dynamic.Service{ + "service1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://" + s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress + ":80", + }, + }, + }, + }, + }, + }, + }, + ruleMatch: "PathPrefix(`/`)", + }, + { + desc: "deploy tcp configuration", + config: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "router1": { + EntryPoints: []string{"web"}, + Service: "service1", + Rule: "HostSNI(`*`)", + }, + }, + Services: map[string]*dynamic.TCPService{ + "service1": { + LoadBalancer: &dynamic.TCPLoadBalancerService{ + Servers: []dynamic.TCPServer{ + { + Address: s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress + ":80", + }, + }, + }, + }, + }, + }, + }, + ruleMatch: "HostSNI(`*`)", + }, + } + + for _, test := range testCase { + json, err := json.Marshal(test.config) + c.Assert(err, checker.IsNil) + + request, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8000/secure/api/providers/rest", bytes.NewReader(json)) + c.Assert(err, checker.IsNil) + + response, err := http.DefaultClient.Do(request) + c.Assert(err, checker.IsNil) + c.Assert(response.StatusCode, checker.Equals, http.StatusOK) + + err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1000*time.Millisecond, try.BodyContains(test.ruleMatch)) + c.Assert(err, checker.IsNil) + + err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + c.Assert(err, checker.IsNil) + } +} diff --git a/integration/simple_test.go b/integration/simple_test.go index 9b40b8b7c..55075163a 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -2,7 +2,6 @@ package integration import ( "bytes" - "context" "encoding/json" "fmt" "io/ioutil" @@ -161,33 +160,6 @@ func (s *SimpleSuite) TestRequestAcceptGraceTimeout(c *check.C) { } } -func (s *SimpleSuite) TestApiOnSameEntryPoint(c *check.C) { - c.Skip("Waiting for new api handler implementation") - s.createComposeProject(c, "base") - s.composeProject.Start(c) - - cmd, output := s.traefikCmd("--entryPoints.http.Address=:8000", "--api.entryPoint=http", "--log.level=DEBUG", "--providers.docker") - defer output(c) - - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer cmd.Process.Kill() - - // TODO validate : run on 80 - // Expected a 404 as we did not configure anything - err = try.GetRequest("http://127.0.0.1:8000/test", 1*time.Second, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://127.0.0.1:8000/api/rawdata", 1*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://127.0.0.1:8000/api/rawdata", 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.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) -} - func (s *SimpleSuite) TestStatsWithMultipleEntryPoint(c *check.C) { c.Skip("Stats is missing") s.createComposeProject(c, "stats") @@ -250,7 +222,7 @@ func (s *SimpleSuite) TestDefaultEntryPointHTTP(c *check.C) { s.createComposeProject(c, "base") s.composeProject.Start(c) - cmd, output := s.traefikCmd("--entryPoints.http.Address=:8000", "--log.level=DEBUG", "--providers.docker", "--api") + cmd, output := s.traefikCmd("--entryPoints.http.Address=:8000", "--log.level=DEBUG", "--providers.docker", "--api.insecure") defer output(c) err := cmd.Start() @@ -268,7 +240,7 @@ func (s *SimpleSuite) TestWithNonExistingEntryPoint(c *check.C) { s.createComposeProject(c, "base") s.composeProject.Start(c) - cmd, output := s.traefikCmd("--entryPoints.http.Address=:8000", "--log.level=DEBUG", "--providers.docker", "--api") + cmd, output := s.traefikCmd("--entryPoints.http.Address=:8000", "--log.level=DEBUG", "--providers.docker", "--api.insecure") defer output(c) err := cmd.Start() @@ -286,7 +258,7 @@ func (s *SimpleSuite) TestMetricsPrometheusDefaultEntryPoint(c *check.C) { s.createComposeProject(c, "base") s.composeProject.Start(c) - cmd, output := s.traefikCmd("--entryPoints.http.Address=:8000", "--api", "--metrics.prometheus.buckets=0.1,0.3,1.2,5.0", "--providers.docker", "--log.level=DEBUG") + cmd, output := s.traefikCmd("--entryPoints.http.Address=:8000", "--api.insecure", "--metrics.prometheus.buckets=0.1,0.3,1.2,5.0", "--providers.docker", "--log.level=DEBUG") defer output(c) err := cmd.Start() @@ -546,6 +518,10 @@ func (s *SimpleSuite) TestRouterConfigErrors(c *check.C) { err = try.GetRequest("http://127.0.0.1:8080/api/http/routers", 1000*time.Millisecond, try.BodyContains(`["middleware \"unknown@file\" does not exist","found different TLS options for routers on the same host snitest.net, so using the default TLS options instead"]`)) c.Assert(err, checker.IsNil) + // router3 has an error because it uses an unknown entrypoint + err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router3@file", 1000*time.Millisecond, try.BodyContains(`entryPoint \"unknown-entrypoint\" doesn't exist`, "no valid entryPoint for this router")) + c.Assert(err, checker.IsNil) + // router4 is enabled, but in warning state because its tls options conf was messed up err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router4@file", 1000*time.Millisecond, try.BodyContains(`"status":"warning"`)) c.Assert(err, checker.IsNil) @@ -768,9 +744,10 @@ func (s *SimpleSuite) TestMirrorCanceled(c *check.C) { req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil) c.Assert(err, checker.IsNil) - newCtx, _ := context.WithTimeout(req.Context(), time.Second) - req = req.WithContext(newCtx) - http.DefaultClient.Do(req) + client := &http.Client{ + Timeout: time.Second, + } + _, _ = client.Do(req) } countTotal := atomic.LoadInt32(&count) @@ -781,3 +758,27 @@ func (s *SimpleSuite) TestMirrorCanceled(c *check.C) { c.Assert(val1, checker.Equals, int32(0)) c.Assert(val2, checker.Equals, int32(0)) } + +func (s *SimpleSuite) TestSecureAPI(c *check.C) { + s.createComposeProject(c, "base") + s.composeProject.Start(c) + + file := s.adaptFile(c, "./fixtures/simple_secure_api.toml", struct{}{}) + 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:8000/secure/api/rawdata", 1*time.Second, try.StatusCodeIs(http.StatusOK)) + c.Assert(err, checker.IsNil) + + err = try.GetRequest("http://127.0.0.1:8000/api/rawdata", 1*time.Second, try.StatusCodeIs(http.StatusNotFound)) + c.Assert(err, checker.IsNil) + + err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.StatusCodeIs(http.StatusNotFound)) + c.Assert(err, checker.IsNil) +} diff --git a/integration/testdata/rawdata-crd.json b/integration/testdata/rawdata-crd.json index c26803e36..2cd67af47 100644 --- a/integration/testdata/rawdata-crd.json +++ b/integration/testdata/rawdata-crd.json @@ -10,31 +10,45 @@ "tls": { "options": "default/mytlsoption" }, - "status": "enabled" + "status": "enabled", + "using": [ + "web" + ] }, "default/test2.route-23c7f4c450289ee29016@kubernetescrd": { "entryPoints": [ "web" ], "middlewares": [ - "default/stripprefix" + "default/mychain@kubernetescrd" ], "service": "default/test2.route-23c7f4c450289ee29016", "rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/tobestripped`)", - "status": "enabled" + "status": "enabled", + "using": [ + "web" + ] } }, "middlewares": { - "default/stripprefix@kubernetescrd": { - "stripPrefix": { - "prefixes": [ - "/tobestripped" + "default/mychain@kubernetescrd": { + "chain": { + "middlewares": [ + "default/stripprefix@kubernetescrd" ] }, "status": "enabled", "usedBy": [ "default/test2.route-23c7f4c450289ee29016@kubernetescrd" ] + }, + "default/stripprefix@kubernetescrd": { + "stripPrefix": { + "prefixes": [ + "/tobestripped" + ] + }, + "status": "enabled" } }, "services": { @@ -42,10 +56,10 @@ "loadBalancer": { "servers": [ { - "url": "http://10.42.0.4:80" + "url": "http://10.42.0.2:80" }, { - "url": "http://10.42.0.5:80" + "url": "http://10.42.0.6:80" } ], "passHostHeader": true @@ -55,18 +69,18 @@ "default/test.route-6b204d94623b3df4370c@kubernetescrd" ], "serverStatus": { - "http://10.42.0.4:80": "UP", - "http://10.42.0.5:80": "UP" + "http://10.42.0.2:80": "UP", + "http://10.42.0.6:80": "UP" } }, "default/test2.route-23c7f4c450289ee29016@kubernetescrd": { "loadBalancer": { "servers": [ { - "url": "http://10.42.0.4:80" + "url": "http://10.42.0.2:80" }, { - "url": "http://10.42.0.5:80" + "url": "http://10.42.0.6:80" } ], "passHostHeader": true @@ -76,8 +90,8 @@ "default/test2.route-23c7f4c450289ee29016@kubernetescrd" ], "serverStatus": { - "http://10.42.0.4:80": "UP", - "http://10.42.0.5:80": "UP" + "http://10.42.0.2:80": "UP", + "http://10.42.0.6:80": "UP" } } }, @@ -92,7 +106,10 @@ "passthrough": false, "options": "default/mytlsoption" }, - "status": "enabled" + "status": "enabled", + "using": [ + "footcp" + ] } }, "tcpServices": { @@ -100,10 +117,10 @@ "loadBalancer": { "servers": [ { - "address": "10.42.0.3:8080" + "address": "10.42.0.4:8080" }, { - "address": "10.42.0.6:8080" + "address": "10.42.0.5:8080" } ] }, diff --git a/integration/testdata/rawdata-ingress.json b/integration/testdata/rawdata-ingress.json index 9a9e1b9d7..4908d6388 100644 --- a/integration/testdata/rawdata-ingress.json +++ b/integration/testdata/rawdata-ingress.json @@ -4,17 +4,29 @@ "service": "default/whoami/http", "rule": "Host(`whoami.test.https`) \u0026\u0026 PathPrefix(`/whoami`)", "tls": {}, - "status": "enabled" + "status": "enabled", + "using": [ + "traefik", + "web" + ] }, "whoami-test-https/whoami@kubernetes": { "service": "default/whoami/http", "rule": "Host(`whoami.test.https`) \u0026\u0026 PathPrefix(`/whoami`)", - "status": "enabled" + "status": "enabled", + "using": [ + "traefik", + "web" + ] }, "whoami-test/whoami@kubernetes": { "service": "default/whoami/http", "rule": "Host(`whoami.test`) \u0026\u0026 PathPrefix(`/whoami`)", - "status": "enabled" + "status": "enabled", + "using": [ + "traefik", + "web" + ] } }, "services": { @@ -22,10 +34,10 @@ "loadBalancer": { "servers": [ { - "url": "http://10.42.0.4:80" + "url": "http://10.42.0.2:80" }, { - "url": "http://10.42.0.5:80" + "url": "http://10.42.0.4:80" } ], "passHostHeader": true @@ -37,8 +49,8 @@ "whoami-test/whoami@kubernetes" ], "serverStatus": { - "http://10.42.0.4:80": "UP", - "http://10.42.0.5:80": "UP" + "http://10.42.0.2:80": "UP", + "http://10.42.0.4:80": "UP" } } } diff --git a/integration/tracing_test.go b/integration/tracing_test.go index 26cf987f9..0dbf009c5 100644 --- a/integration/tracing_test.go +++ b/integration/tracing_test.go @@ -41,8 +41,6 @@ func (s *TracingSuite) startZipkin(c *check.C) { } func (s *TracingSuite) TestZipkinRateLimit(c *check.C) { - c.Skip("RateLimit is disable for now") - s.startZipkin(c) defer s.composeProject.Stop(c, "zipkin") file := s.adaptFile(c, "fixtures/tracing/simple-zipkin.toml", TracingTemplate{ @@ -88,7 +86,7 @@ func (s *TracingSuite) TestZipkinRateLimit(c *check.C) { err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests)) c.Assert(err, checker.IsNil) - err = try.GetRequest("http://"+s.IP+":9411/api/v2/spans?serviceName=tracing", 20*time.Second, try.BodyContains("forward service1/router1@file", "ratelimit@file")) + err = try.GetRequest("http://"+s.IP+":9411/api/v2/spans?serviceName=tracing", 20*time.Second, try.BodyContains("forward service1/router1@file", "ratelimit-1@file")) c.Assert(err, checker.IsNil) } @@ -157,8 +155,6 @@ func (s *TracingSuite) startJaeger(c *check.C) { } func (s *TracingSuite) TestJaegerRateLimit(c *check.C) { - c.Skip("RateLimit is disable for now") - s.startJaeger(c) defer s.composeProject.Stop(c, "jaeger") file := s.adaptFile(c, "fixtures/tracing/simple-jaeger.toml", TracingTemplate{ @@ -200,13 +196,11 @@ func (s *TracingSuite) TestJaegerRateLimit(c *check.C) { err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) c.Assert(err, checker.IsNil) - time.Sleep(3 * time.Second) err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests)) c.Assert(err, checker.IsNil) - err = try.GetRequest("http://"+s.IP+":16686/api/traces?service=tracing", 20*time.Second, try.BodyContains("forward service1/router1@file", "ratelimit@file")) + err = try.GetRequest("http://"+s.IP+":16686/api/traces?service=tracing", 20*time.Second, try.BodyContains("forward service1/router1@file", "ratelimit-1@file")) c.Assert(err, checker.IsNil) - } func (s *TracingSuite) TestJaegerRetry(c *check.C) { diff --git a/pkg/anonymize/anonymize_config_test.go b/pkg/anonymize/anonymize_config_test.go index 6dfd372f7..683c70a1b 100644 --- a/pkg/anonymize/anonymize_config_test.go +++ b/pkg/anonymize/anonymize_config_test.go @@ -186,7 +186,7 @@ func TestDo_globalConfiguration(t *testing.T) { Prometheus: &types.Prometheus{ Buckets: []float64{0.1, 0.3, 1.2, 5}, }, - DataDog: &types.DataDog{ + Datadog: &types.Datadog{ Address: "localhost:8181", PushInterval: 12, }, @@ -223,10 +223,9 @@ func TestDo_globalConfiguration(t *testing.T) { HTTPEndpoint: "fff", SameSpan: true, ID128Bit: true, - Debug: true, SampleRate: 53, }, - DataDog: &datadog.Config{ + Datadog: &datadog.Config{ LocalAgentHostPort: "ggg", GlobalTag: "eee", Debug: true, diff --git a/pkg/api/criterion.go b/pkg/api/criterion.go new file mode 100644 index 000000000..d81a9f717 --- /dev/null +++ b/pkg/api/criterion.go @@ -0,0 +1,102 @@ +package api + +import ( + "fmt" + "net/http" + "net/url" + "strconv" + "strings" +) + +const ( + defaultPerPage = 100 + defaultPage = 1 +) + +const nextPageHeader = "X-Next-Page" + +type pageInfo struct { + startIndex int + endIndex int + nextPage int +} + +type searchCriterion struct { + Search string `url:"search"` + Status string `url:"status"` +} + +func newSearchCriterion(query url.Values) *searchCriterion { + if len(query) == 0 { + return nil + } + + search := query.Get("search") + status := query.Get("status") + + if status == "" && search == "" { + return nil + } + + return &searchCriterion{Search: search, Status: status} +} + +func (c *searchCriterion) withStatus(name string) bool { + return c.Status == "" || strings.EqualFold(name, c.Status) +} + +func (c *searchCriterion) searchIn(values ...string) bool { + if c.Search == "" { + return true + } + + for _, v := range values { + if strings.Contains(strings.ToLower(v), strings.ToLower(c.Search)) { + return true + } + } + + return false +} + +func pagination(request *http.Request, max int) (pageInfo, error) { + perPage, err := getIntParam(request, "per_page", defaultPerPage) + if err != nil { + return pageInfo{}, err + } + + page, err := getIntParam(request, "page", defaultPage) + if err != nil { + return pageInfo{}, err + } + + startIndex := (page - 1) * perPage + if startIndex != 0 && startIndex >= max { + return pageInfo{}, fmt.Errorf("invalid request: page: %d, per_page: %d", page, perPage) + } + + endIndex := startIndex + perPage + if endIndex >= max { + endIndex = max + } + + nextPage := 1 + if page*perPage < max { + nextPage = page + 1 + } + + return pageInfo{startIndex: startIndex, endIndex: endIndex, nextPage: nextPage}, nil +} + +func getIntParam(request *http.Request, key string, defaultValue int) (int, error) { + raw := request.URL.Query().Get(key) + if raw == "" { + return defaultValue, nil + } + + value, err := strconv.Atoi(raw) + if err != nil || value <= 0 { + return 0, fmt.Errorf("invalid request: %s: %d", key, value) + } + return value, nil +} diff --git a/pkg/api/handler.go b/pkg/api/handler.go index 7ee900c75..87f844c6c 100644 --- a/pkg/api/handler.go +++ b/pkg/api/handler.go @@ -2,9 +2,8 @@ package api import ( "encoding/json" - "fmt" "net/http" - "strconv" + "reflect" "strings" "github.com/containous/traefik/v2/pkg/config/runtime" @@ -15,12 +14,19 @@ import ( "github.com/gorilla/mux" ) -const ( - defaultPerPage = 100 - defaultPage = 1 -) +type apiError struct { + Message string `json:"message"` +} -const nextPageHeader = "X-Next-Page" +func writeError(rw http.ResponseWriter, msg string, code int) { + data, err := json.Marshal(apiError{Message: msg}) + if err != nil { + http.Error(rw, msg, code) + return + } + + http.Error(rw, string(data), code) +} type serviceInfoRepresentation struct { *runtime.ServiceInfo @@ -36,12 +42,6 @@ type RunTimeRepresentation struct { TCPServices map[string]*runtime.TCPServiceInfo `json:"tcpServices,omitempty"` } -type pageInfo struct { - startIndex int - endIndex int - nextPage int -} - // Handler serves the configuration and status of Traefik on API endpoints. type Handler struct { dashboard bool @@ -55,6 +55,15 @@ type Handler struct { dashboardAssets *assetfs.AssetFS } +// NewBuilder returns a http.Handler builder based on runtime.Configuration +func NewBuilder(staticConfig static.Configuration) func(*runtime.Configuration) http.Handler { + return func(configuration *runtime.Configuration) http.Handler { + router := mux.NewRouter() + New(staticConfig, configuration).Append(router) + return router + } +} + // New returns a Handler defined by staticConfig, and if provided, by runtimeConfig. // It finishes populating the information provided in the runtimeConfig. func New(staticConfig static.Configuration, runtimeConfig *runtime.Configuration) *Handler { @@ -136,48 +145,19 @@ func (h Handler) getRuntimeConfiguration(rw http.ResponseWriter, request *http.R } } -func pagination(request *http.Request, max int) (pageInfo, error) { - perPage, err := getIntParam(request, "per_page", defaultPerPage) - if err != nil { - return pageInfo{}, err - } - - page, err := getIntParam(request, "page", defaultPage) - if err != nil { - return pageInfo{}, err - } - - startIndex := (page - 1) * perPage - if startIndex != 0 && startIndex >= max { - return pageInfo{}, fmt.Errorf("invalid request: page: %d, per_page: %d", page, perPage) - } - - endIndex := startIndex + perPage - if endIndex >= max { - endIndex = max - } - - nextPage := 1 - if page*perPage < max { - nextPage = page + 1 - } - - return pageInfo{startIndex: startIndex, endIndex: endIndex, nextPage: nextPage}, nil -} - -func getIntParam(request *http.Request, key string, defaultValue int) (int, error) { - raw := request.URL.Query().Get(key) - if raw == "" { - return defaultValue, nil - } - - value, err := strconv.Atoi(raw) - if err != nil || value <= 0 { - return 0, fmt.Errorf("invalid request: %s: %d", key, value) - } - return value, nil -} - func getProviderName(id string) string { return strings.SplitN(id, "@", 2)[1] } + +func extractType(element interface{}) string { + v := reflect.ValueOf(element).Elem() + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + if field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct { + if !field.IsNil() { + return v.Type().Field(i).Name + } + } + } + return "" +} diff --git a/pkg/api/handler_entrypoint.go b/pkg/api/handler_entrypoint.go index b4cc3f4e5..808b7e536 100644 --- a/pkg/api/handler_entrypoint.go +++ b/pkg/api/handler_entrypoint.go @@ -2,6 +2,7 @@ package api import ( "encoding/json" + "fmt" "net/http" "sort" "strconv" @@ -30,28 +31,31 @@ func (h Handler) getEntryPoints(rw http.ResponseWriter, request *http.Request) { return results[i].Name < results[j].Name }) + rw.Header().Set("Content-Type", "application/json") + pageInfo, err := pagination(request, len(results)) if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) + writeError(rw, err.Error(), http.StatusBadRequest) return } - rw.Header().Set("Content-Type", "application/json") rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage)) err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex]) if err != nil { log.FromContext(request.Context()).Error(err) - http.Error(rw, err.Error(), http.StatusInternalServerError) + writeError(rw, err.Error(), http.StatusInternalServerError) } } func (h Handler) getEntryPoint(rw http.ResponseWriter, request *http.Request) { entryPointID := mux.Vars(request)["entryPointID"] + rw.Header().Set("Content-Type", "application/json") + ep, ok := h.staticConfig.EntryPoints[entryPointID] if !ok { - http.NotFound(rw, request) + writeError(rw, fmt.Sprintf("entry point not found: %s", entryPointID), http.StatusNotFound) return } @@ -60,11 +64,9 @@ func (h Handler) getEntryPoint(rw http.ResponseWriter, request *http.Request) { Name: entryPointID, } - rw.Header().Set("Content-Type", "application/json") - err := json.NewEncoder(rw).Encode(result) if err != nil { log.FromContext(request.Context()).Error(err) - http.Error(rw, err.Error(), http.StatusInternalServerError) + writeError(rw, err.Error(), http.StatusInternalServerError) } } diff --git a/pkg/api/handler_http.go b/pkg/api/handler_http.go index 39bc3fd3f..fe43c215f 100644 --- a/pkg/api/handler_http.go +++ b/pkg/api/handler_http.go @@ -2,9 +2,11 @@ package api import ( "encoding/json" + "fmt" "net/http" "sort" "strconv" + "strings" "github.com/containous/traefik/v2/pkg/config/runtime" "github.com/containous/traefik/v2/pkg/log" @@ -17,182 +19,224 @@ type routerRepresentation struct { Provider string `json:"provider,omitempty"` } +func newRouterRepresentation(name string, rt *runtime.RouterInfo) routerRepresentation { + return routerRepresentation{ + RouterInfo: rt, + Name: name, + Provider: getProviderName(name), + } +} + type serviceRepresentation struct { *runtime.ServiceInfo ServerStatus map[string]string `json:"serverStatus,omitempty"` Name string `json:"name,omitempty"` Provider string `json:"provider,omitempty"` + Type string `json:"type,omitempty"` +} + +func newServiceRepresentation(name string, si *runtime.ServiceInfo) serviceRepresentation { + return serviceRepresentation{ + ServiceInfo: si, + Name: name, + Provider: getProviderName(name), + ServerStatus: si.GetAllStatus(), + Type: strings.ToLower(extractType(si.Service)), + } } type middlewareRepresentation struct { *runtime.MiddlewareInfo Name string `json:"name,omitempty"` Provider string `json:"provider,omitempty"` + Type string `json:"type,omitempty"` +} + +func newMiddlewareRepresentation(name string, mi *runtime.MiddlewareInfo) middlewareRepresentation { + return middlewareRepresentation{ + MiddlewareInfo: mi, + Name: name, + Provider: getProviderName(name), + Type: strings.ToLower(extractType(mi.Middleware)), + } } func (h Handler) getRouters(rw http.ResponseWriter, request *http.Request) { results := make([]routerRepresentation, 0, len(h.runtimeConfiguration.Routers)) + criterion := newSearchCriterion(request.URL.Query()) + for name, rt := range h.runtimeConfiguration.Routers { - results = append(results, routerRepresentation{ - RouterInfo: rt, - Name: name, - Provider: getProviderName(name), - }) + if keepRouter(name, rt, criterion) { + results = append(results, newRouterRepresentation(name, rt)) + } } sort.Slice(results, func(i, j int) bool { return results[i].Name < results[j].Name }) + rw.Header().Set("Content-Type", "application/json") + pageInfo, err := pagination(request, len(results)) if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) + writeError(rw, err.Error(), http.StatusBadRequest) return } - rw.Header().Set("Content-Type", "application/json") rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage)) err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex]) if err != nil { log.FromContext(request.Context()).Error(err) - http.Error(rw, err.Error(), http.StatusInternalServerError) + writeError(rw, err.Error(), http.StatusInternalServerError) } } func (h Handler) getRouter(rw http.ResponseWriter, request *http.Request) { routerID := mux.Vars(request)["routerID"] + rw.Header().Set("Content-Type", "application/json") + router, ok := h.runtimeConfiguration.Routers[routerID] if !ok { - http.NotFound(rw, request) + writeError(rw, fmt.Sprintf("router not found: %s", routerID), http.StatusNotFound) return } - result := routerRepresentation{ - RouterInfo: router, - Name: routerID, - Provider: getProviderName(routerID), - } - - rw.Header().Set("Content-Type", "application/json") + result := newRouterRepresentation(routerID, router) err := json.NewEncoder(rw).Encode(result) if err != nil { log.FromContext(request.Context()).Error(err) - http.Error(rw, err.Error(), http.StatusInternalServerError) + writeError(rw, err.Error(), http.StatusInternalServerError) } } func (h Handler) getServices(rw http.ResponseWriter, request *http.Request) { results := make([]serviceRepresentation, 0, len(h.runtimeConfiguration.Services)) + criterion := newSearchCriterion(request.URL.Query()) + for name, si := range h.runtimeConfiguration.Services { - results = append(results, serviceRepresentation{ - ServiceInfo: si, - Name: name, - Provider: getProviderName(name), - ServerStatus: si.GetAllStatus(), - }) + if keepService(name, si, criterion) { + results = append(results, newServiceRepresentation(name, si)) + } } sort.Slice(results, func(i, j int) bool { return results[i].Name < results[j].Name }) + rw.Header().Set("Content-Type", "application/json") + pageInfo, err := pagination(request, len(results)) if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) + writeError(rw, err.Error(), http.StatusBadRequest) return } - rw.Header().Set("Content-Type", "application/json") rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage)) err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex]) if err != nil { log.FromContext(request.Context()).Error(err) - http.Error(rw, err.Error(), http.StatusInternalServerError) + writeError(rw, err.Error(), http.StatusInternalServerError) } } func (h Handler) getService(rw http.ResponseWriter, request *http.Request) { serviceID := mux.Vars(request)["serviceID"] + rw.Header().Add("Content-Type", "application/json") + service, ok := h.runtimeConfiguration.Services[serviceID] if !ok { - http.NotFound(rw, request) + writeError(rw, fmt.Sprintf("service not found: %s", serviceID), http.StatusNotFound) return } - result := serviceRepresentation{ - ServiceInfo: service, - Name: serviceID, - Provider: getProviderName(serviceID), - ServerStatus: service.GetAllStatus(), - } - - rw.Header().Add("Content-Type", "application/json") + result := newServiceRepresentation(serviceID, service) err := json.NewEncoder(rw).Encode(result) if err != nil { log.FromContext(request.Context()).Error(err) - http.Error(rw, err.Error(), http.StatusInternalServerError) + writeError(rw, err.Error(), http.StatusInternalServerError) } } func (h Handler) getMiddlewares(rw http.ResponseWriter, request *http.Request) { results := make([]middlewareRepresentation, 0, len(h.runtimeConfiguration.Middlewares)) + criterion := newSearchCriterion(request.URL.Query()) + for name, mi := range h.runtimeConfiguration.Middlewares { - results = append(results, middlewareRepresentation{ - MiddlewareInfo: mi, - Name: name, - Provider: getProviderName(name), - }) + if keepMiddleware(name, mi, criterion) { + results = append(results, newMiddlewareRepresentation(name, mi)) + } } sort.Slice(results, func(i, j int) bool { return results[i].Name < results[j].Name }) + rw.Header().Set("Content-Type", "application/json") + pageInfo, err := pagination(request, len(results)) if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) + writeError(rw, err.Error(), http.StatusBadRequest) return } - rw.Header().Set("Content-Type", "application/json") rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage)) err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex]) if err != nil { log.FromContext(request.Context()).Error(err) - http.Error(rw, err.Error(), http.StatusInternalServerError) + writeError(rw, err.Error(), http.StatusInternalServerError) } } func (h Handler) getMiddleware(rw http.ResponseWriter, request *http.Request) { middlewareID := mux.Vars(request)["middlewareID"] + rw.Header().Set("Content-Type", "application/json") + middleware, ok := h.runtimeConfiguration.Middlewares[middlewareID] if !ok { - http.NotFound(rw, request) + writeError(rw, fmt.Sprintf("middleware not found: %s", middlewareID), http.StatusNotFound) return } - result := middlewareRepresentation{ - MiddlewareInfo: middleware, - Name: middlewareID, - Provider: getProviderName(middlewareID), - } - - rw.Header().Set("Content-Type", "application/json") + result := newMiddlewareRepresentation(middlewareID, middleware) err := json.NewEncoder(rw).Encode(result) if err != nil { log.FromContext(request.Context()).Error(err) - http.Error(rw, err.Error(), http.StatusInternalServerError) + writeError(rw, err.Error(), http.StatusInternalServerError) } } + +func keepRouter(name string, item *runtime.RouterInfo, criterion *searchCriterion) bool { + if criterion == nil { + return true + } + + return criterion.withStatus(item.Status) && criterion.searchIn(item.Rule, name) +} + +func keepService(name string, item *runtime.ServiceInfo, criterion *searchCriterion) bool { + if criterion == nil { + return true + } + + return criterion.withStatus(item.Status) && criterion.searchIn(name) +} + +func keepMiddleware(name string, item *runtime.MiddlewareInfo, criterion *searchCriterion) bool { + if criterion == nil { + return true + } + + return criterion.withStatus(item.Status) && criterion.searchIn(name) +} diff --git a/pkg/api/handler_http_test.go b/pkg/api/handler_http_test.go index 57524e675..38286151f 100644 --- a/pkg/api/handler_http_test.go +++ b/pkg/api/handler_http_test.go @@ -1,6 +1,7 @@ package api import ( + "context" "encoding/json" "fmt" "io/ioutil" @@ -137,6 +138,68 @@ func TestHandler_HTTP(t *testing.T) { statusCode: http.StatusBadRequest, }, }, + { + desc: "routers filtered by status", + path: "/api/http/routers?status=enabled", + conf: runtime.Configuration{ + Routers: map[string]*runtime.RouterInfo{ + "test@myprovider": { + Router: &dynamic.Router{ + EntryPoints: []string{"web"}, + Service: "foo-service@myprovider", + Rule: "Host(`foo.bar.other`)", + Middlewares: []string{"addPrefixTest", "auth"}, + }, + Status: runtime.StatusEnabled, + }, + "bar@myprovider": { + Router: &dynamic.Router{ + EntryPoints: []string{"web"}, + Service: "foo-service@myprovider", + Rule: "Host(`foo.bar`)", + Middlewares: []string{"auth", "addPrefixTest@anotherprovider"}, + }, + Status: runtime.StatusDisabled, + }, + }, + }, + expected: expected{ + statusCode: http.StatusOK, + nextPage: "1", + jsonFile: "testdata/routers-filtered-status.json", + }, + }, + { + desc: "routers filtered by search", + path: "/api/http/routers?search=fii", + conf: runtime.Configuration{ + Routers: map[string]*runtime.RouterInfo{ + "test@myprovider": { + Router: &dynamic.Router{ + EntryPoints: []string{"web"}, + Service: "fii-service@myprovider", + Rule: "Host(`fii.bar.other`)", + Middlewares: []string{"addPrefixTest", "auth"}, + }, + Status: runtime.StatusEnabled, + }, + "bar@myprovider": { + Router: &dynamic.Router{ + EntryPoints: []string{"web"}, + Service: "foo-service@myprovider", + Rule: "Host(`foo.bar`)", + Middlewares: []string{"auth", "addPrefixTest@anotherprovider"}, + }, + Status: runtime.StatusDisabled, + }, + }, + }, + expected: expected{ + statusCode: http.StatusOK, + nextPage: "1", + jsonFile: "testdata/routers-filtered-search.json", + }, + }, { desc: "one router by id", path: "/api/http/routers/bar@myprovider", @@ -232,6 +295,45 @@ func TestHandler_HTTP(t *testing.T) { si.UpdateServerStatus("http://127.0.0.2", "UP") return si }(), + "canary@myprovider": { + Service: &dynamic.Service{ + Weighted: &dynamic.WeightedRoundRobin{ + Services: nil, + Sticky: &dynamic.Sticky{ + Cookie: &dynamic.Cookie{ + Name: "chocolat", + Secure: true, + HTTPOnly: true, + }, + }, + }, + }, + Status: runtime.StatusEnabled, + UsedBy: []string{"foo@myprovider"}, + }, + "mirror@myprovider": { + Service: &dynamic.Service{ + Mirroring: &dynamic.Mirroring{ + Service: "one@myprovider", + Mirrors: []dynamic.MirrorService{ + { + Name: "two@myprovider", + Percent: 10, + }, + { + Name: "three@myprovider", + Percent: 15, + }, + { + Name: "four@myprovider", + Percent: 80, + }, + }, + }, + }, + Status: runtime.StatusEnabled, + UsedBy: []string{"foo@myprovider"}, + }, }, }, expected: expected{ @@ -301,6 +403,100 @@ func TestHandler_HTTP(t *testing.T) { jsonFile: "testdata/services-page2.json", }, }, + { + desc: "services filtered by status", + path: "/api/http/services?status=enabled", + conf: runtime.Configuration{ + Services: map[string]*runtime.ServiceInfo{ + "bar@myprovider": func() *runtime.ServiceInfo { + si := &runtime.ServiceInfo{ + Service: &dynamic.Service{ + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1", + }, + }, + }, + }, + UsedBy: []string{"foo@myprovider", "test@myprovider"}, + Status: runtime.StatusEnabled, + } + si.UpdateServerStatus("http://127.0.0.1", "UP") + return si + }(), + "baz@myprovider": func() *runtime.ServiceInfo { + si := &runtime.ServiceInfo{ + Service: &dynamic.Service{ + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.2", + }, + }, + }, + }, + UsedBy: []string{"foo@myprovider"}, + Status: runtime.StatusDisabled, + } + si.UpdateServerStatus("http://127.0.0.2", "UP") + return si + }(), + }, + }, + expected: expected{ + statusCode: http.StatusOK, + nextPage: "1", + jsonFile: "testdata/services-filtered-status.json", + }, + }, + { + desc: "services filtered by search", + path: "/api/http/services?search=baz", + conf: runtime.Configuration{ + Services: map[string]*runtime.ServiceInfo{ + "bar@myprovider": func() *runtime.ServiceInfo { + si := &runtime.ServiceInfo{ + Service: &dynamic.Service{ + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1", + }, + }, + }, + }, + UsedBy: []string{"foo@myprovider", "test@myprovider"}, + Status: runtime.StatusEnabled, + } + si.UpdateServerStatus("http://127.0.0.1", "UP") + return si + }(), + "baz@myprovider": func() *runtime.ServiceInfo { + si := &runtime.ServiceInfo{ + Service: &dynamic.Service{ + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.2", + }, + }, + }, + }, + UsedBy: []string{"foo@myprovider"}, + Status: runtime.StatusDisabled, + } + si.UpdateServerStatus("http://127.0.0.2", "UP") + return si + }(), + }, + }, + expected: expected{ + statusCode: http.StatusOK, + nextPage: "1", + jsonFile: "testdata/services-filtered-search.json", + }, + }, { desc: "one service by id", path: "/api/http/services/bar@myprovider", @@ -411,6 +607,86 @@ func TestHandler_HTTP(t *testing.T) { jsonFile: "testdata/middlewares.json", }, }, + { + desc: "middlewares filtered by status", + path: "/api/http/middlewares?status=enabled", + conf: runtime.Configuration{ + Middlewares: map[string]*runtime.MiddlewareInfo{ + "auth@myprovider": { + Middleware: &dynamic.Middleware{ + BasicAuth: &dynamic.BasicAuth{ + Users: []string{"admin:admin"}, + }, + }, + UsedBy: []string{"bar@myprovider", "test@myprovider"}, + Status: runtime.StatusEnabled, + }, + "addPrefixTest@myprovider": { + Middleware: &dynamic.Middleware{ + AddPrefix: &dynamic.AddPrefix{ + Prefix: "/titi", + }, + }, + UsedBy: []string{"test@myprovider"}, + Status: runtime.StatusDisabled, + }, + "addPrefixTest@anotherprovider": { + Middleware: &dynamic.Middleware{ + AddPrefix: &dynamic.AddPrefix{ + Prefix: "/toto", + }, + }, + UsedBy: []string{"bar@myprovider"}, + Status: runtime.StatusEnabled, + }, + }, + }, + expected: expected{ + statusCode: http.StatusOK, + nextPage: "1", + jsonFile: "testdata/middlewares-filtered-status.json", + }, + }, + { + desc: "middlewares filtered by search", + path: "/api/http/middlewares?search=addprefixtest", + conf: runtime.Configuration{ + Middlewares: map[string]*runtime.MiddlewareInfo{ + "auth@myprovider": { + Middleware: &dynamic.Middleware{ + BasicAuth: &dynamic.BasicAuth{ + Users: []string{"admin:admin"}, + }, + }, + UsedBy: []string{"bar@myprovider", "test@myprovider"}, + Status: runtime.StatusEnabled, + }, + "addPrefixTest@myprovider": { + Middleware: &dynamic.Middleware{ + AddPrefix: &dynamic.AddPrefix{ + Prefix: "/titi", + }, + }, + UsedBy: []string{"test@myprovider"}, + Status: runtime.StatusDisabled, + }, + "addPrefixTest@anotherprovider": { + Middleware: &dynamic.Middleware{ + AddPrefix: &dynamic.AddPrefix{ + Prefix: "/toto", + }, + }, + UsedBy: []string{"bar@myprovider"}, + Status: runtime.StatusEnabled, + }, + }, + }, + expected: expected{ + statusCode: http.StatusOK, + nextPage: "1", + jsonFile: "testdata/middlewares-filtered-search.json", + }, + }, { desc: "all middlewares, 1 res per page, want page 2", path: "/api/http/middlewares?page=2&per_page=1", @@ -521,6 +797,8 @@ func TestHandler_HTTP(t *testing.T) { rtConf := &test.conf // To lazily initialize the Statuses. rtConf.PopulateUsedBy() + rtConf.GetRoutersByEntryPoints(context.Background(), []string{"web"}, false) + handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf) router := mux.NewRouter() handler.Append(router) diff --git a/pkg/api/handler_overview.go b/pkg/api/handler_overview.go index f08d80351..21f1921e1 100644 --- a/pkg/api/handler_overview.go +++ b/pkg/api/handler_overview.go @@ -56,7 +56,7 @@ func (h Handler) getOverview(rw http.ResponseWriter, request *http.Request) { err := json.NewEncoder(rw).Encode(result) if err != nil { log.FromContext(request.Context()).Error(err) - http.Error(rw, err.Error(), http.StatusInternalServerError) + writeError(rw, err.Error(), http.StatusInternalServerError) } } diff --git a/pkg/api/handler_tcp.go b/pkg/api/handler_tcp.go index 3f91f0386..823af5178 100644 --- a/pkg/api/handler_tcp.go +++ b/pkg/api/handler_tcp.go @@ -2,9 +2,11 @@ package api import ( "encoding/json" + "fmt" "net/http" "sort" "strconv" + "strings" "github.com/containous/traefik/v2/pkg/config/runtime" "github.com/containous/traefik/v2/pkg/log" @@ -17,118 +19,146 @@ type tcpRouterRepresentation struct { Provider string `json:"provider,omitempty"` } +func newTCPRouterRepresentation(name string, rt *runtime.TCPRouterInfo) tcpRouterRepresentation { + return tcpRouterRepresentation{ + TCPRouterInfo: rt, + Name: name, + Provider: getProviderName(name), + } +} + type tcpServiceRepresentation struct { *runtime.TCPServiceInfo Name string `json:"name,omitempty"` Provider string `json:"provider,omitempty"` + Type string `json:"type,omitempty"` +} + +func newTCPServiceRepresentation(name string, si *runtime.TCPServiceInfo) tcpServiceRepresentation { + return tcpServiceRepresentation{ + TCPServiceInfo: si, + Name: name, + Provider: getProviderName(name), + Type: strings.ToLower(extractType(si.TCPService)), + } } func (h Handler) getTCPRouters(rw http.ResponseWriter, request *http.Request) { results := make([]tcpRouterRepresentation, 0, len(h.runtimeConfiguration.TCPRouters)) + criterion := newSearchCriterion(request.URL.Query()) + for name, rt := range h.runtimeConfiguration.TCPRouters { - results = append(results, tcpRouterRepresentation{ - TCPRouterInfo: rt, - Name: name, - Provider: getProviderName(name), - }) + if keepTCPRouter(name, rt, criterion) { + results = append(results, newTCPRouterRepresentation(name, rt)) + } } sort.Slice(results, func(i, j int) bool { return results[i].Name < results[j].Name }) + rw.Header().Set("Content-Type", "application/json") + pageInfo, err := pagination(request, len(results)) if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) + writeError(rw, err.Error(), http.StatusBadRequest) return } - rw.Header().Set("Content-Type", "application/json") rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage)) err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex]) if err != nil { log.FromContext(request.Context()).Error(err) - http.Error(rw, err.Error(), http.StatusInternalServerError) + writeError(rw, err.Error(), http.StatusInternalServerError) } } func (h Handler) getTCPRouter(rw http.ResponseWriter, request *http.Request) { routerID := mux.Vars(request)["routerID"] + rw.Header().Set("Content-Type", "application/json") + router, ok := h.runtimeConfiguration.TCPRouters[routerID] if !ok { - http.NotFound(rw, request) + writeError(rw, fmt.Sprintf("router not found: %s", routerID), http.StatusNotFound) return } - result := tcpRouterRepresentation{ - TCPRouterInfo: router, - Name: routerID, - Provider: getProviderName(routerID), - } - - rw.Header().Set("Content-Type", "application/json") + result := newTCPRouterRepresentation(routerID, router) err := json.NewEncoder(rw).Encode(result) if err != nil { log.FromContext(request.Context()).Error(err) - http.Error(rw, err.Error(), http.StatusInternalServerError) + writeError(rw, err.Error(), http.StatusInternalServerError) } } func (h Handler) getTCPServices(rw http.ResponseWriter, request *http.Request) { results := make([]tcpServiceRepresentation, 0, len(h.runtimeConfiguration.TCPServices)) + criterion := newSearchCriterion(request.URL.Query()) + for name, si := range h.runtimeConfiguration.TCPServices { - results = append(results, tcpServiceRepresentation{ - TCPServiceInfo: si, - Name: name, - Provider: getProviderName(name), - }) + if keepTCPService(name, si, criterion) { + results = append(results, newTCPServiceRepresentation(name, si)) + } } sort.Slice(results, func(i, j int) bool { return results[i].Name < results[j].Name }) + rw.Header().Set("Content-Type", "application/json") + pageInfo, err := pagination(request, len(results)) if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) + writeError(rw, err.Error(), http.StatusBadRequest) return } - rw.Header().Set("Content-Type", "application/json") rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage)) err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex]) if err != nil { log.FromContext(request.Context()).Error(err) - http.Error(rw, err.Error(), http.StatusInternalServerError) + writeError(rw, err.Error(), http.StatusInternalServerError) } } func (h Handler) getTCPService(rw http.ResponseWriter, request *http.Request) { serviceID := mux.Vars(request)["serviceID"] + rw.Header().Set("Content-Type", "application/json") + service, ok := h.runtimeConfiguration.TCPServices[serviceID] if !ok { - http.NotFound(rw, request) + writeError(rw, fmt.Sprintf("service not found: %s", serviceID), http.StatusNotFound) return } - result := tcpServiceRepresentation{ - TCPServiceInfo: service, - Name: serviceID, - Provider: getProviderName(serviceID), - } - - rw.Header().Set("Content-Type", "application/json") + result := newTCPServiceRepresentation(serviceID, service) err := json.NewEncoder(rw).Encode(result) if err != nil { log.FromContext(request.Context()).Error(err) - http.Error(rw, err.Error(), http.StatusInternalServerError) + writeError(rw, err.Error(), http.StatusInternalServerError) } } + +func keepTCPRouter(name string, item *runtime.TCPRouterInfo, criterion *searchCriterion) bool { + if criterion == nil { + return true + } + + return criterion.withStatus(item.Status) && criterion.searchIn(item.Rule, name) +} + +func keepTCPService(name string, item *runtime.TCPServiceInfo, criterion *searchCriterion) bool { + if criterion == nil { + return true + } + + return criterion.withStatus(item.Status) && criterion.searchIn(name) +} diff --git a/pkg/api/handler_tcp_test.go b/pkg/api/handler_tcp_test.go index 28c833f8b..798876de4 100644 --- a/pkg/api/handler_tcp_test.go +++ b/pkg/api/handler_tcp_test.go @@ -1,6 +1,7 @@ package api import ( + "context" "encoding/json" "io/ioutil" "net/http" @@ -112,6 +113,86 @@ func TestHandler_TCP(t *testing.T) { jsonFile: "testdata/tcprouters-page2.json", }, }, + { + desc: "TCP routers filtered by status", + path: "/api/tcp/routers?status=enabled", + conf: runtime.Configuration{ + TCPRouters: map[string]*runtime.TCPRouterInfo{ + "test@myprovider": { + TCPRouter: &dynamic.TCPRouter{ + EntryPoints: []string{"web"}, + Service: "foo-service@myprovider", + Rule: "Host(`foo.bar.other`)", + TLS: &dynamic.RouterTCPTLSConfig{ + Passthrough: false, + }, + }, + Status: runtime.StatusEnabled, + }, + "bar@myprovider": { + TCPRouter: &dynamic.TCPRouter{ + EntryPoints: []string{"web"}, + Service: "foo-service@myprovider", + Rule: "Host(`foo.bar`)", + }, + Status: runtime.StatusWarning, + }, + "foo@myprovider": { + TCPRouter: &dynamic.TCPRouter{ + EntryPoints: []string{"web"}, + Service: "foo-service@myprovider", + Rule: "Host(`foo.bar`)", + }, + Status: runtime.StatusDisabled, + }, + }, + }, + expected: expected{ + statusCode: http.StatusOK, + nextPage: "1", + jsonFile: "testdata/tcprouters-filtered-status.json", + }, + }, + { + desc: "TCP routers filtered by search", + path: "/api/tcp/routers?search=bar@my", + conf: runtime.Configuration{ + TCPRouters: map[string]*runtime.TCPRouterInfo{ + "test@myprovider": { + TCPRouter: &dynamic.TCPRouter{ + EntryPoints: []string{"web"}, + Service: "foo-service@myprovider", + Rule: "Host(`foo.bar.other`)", + TLS: &dynamic.RouterTCPTLSConfig{ + Passthrough: false, + }, + }, + Status: runtime.StatusEnabled, + }, + "bar@myprovider": { + TCPRouter: &dynamic.TCPRouter{ + EntryPoints: []string{"web"}, + Service: "foo-service@myprovider", + Rule: "Host(`foo.bar`)", + }, + Status: runtime.StatusWarning, + }, + "foo@myprovider": { + TCPRouter: &dynamic.TCPRouter{ + EntryPoints: []string{"web"}, + Service: "foo-service@myprovider", + Rule: "Host(`foo.bar`)", + }, + Status: runtime.StatusDisabled, + }, + }, + }, + expected: expected{ + statusCode: http.StatusOK, + nextPage: "1", + jsonFile: "testdata/tcprouters-filtered-search.json", + }, + }, { desc: "one TCP router by id", path: "/api/tcp/routers/bar@myprovider", @@ -219,6 +300,110 @@ func TestHandler_TCP(t *testing.T) { jsonFile: "testdata/tcpservices.json", }, }, + { + desc: "tcp services filtered by status", + path: "/api/tcp/services?status=enabled", + conf: runtime.Configuration{ + TCPServices: map[string]*runtime.TCPServiceInfo{ + "bar@myprovider": { + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPLoadBalancerService{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.1:2345", + }, + }, + }, + }, + UsedBy: []string{"foo@myprovider", "test@myprovider"}, + Status: runtime.StatusEnabled, + }, + "baz@myprovider": { + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPLoadBalancerService{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.2:2345", + }, + }, + }, + }, + UsedBy: []string{"foo@myprovider"}, + Status: runtime.StatusWarning, + }, + "foz@myprovider": { + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPLoadBalancerService{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.2:2345", + }, + }, + }, + }, + UsedBy: []string{"foo@myprovider"}, + Status: runtime.StatusDisabled, + }, + }, + }, + expected: expected{ + statusCode: http.StatusOK, + nextPage: "1", + jsonFile: "testdata/tcpservices-filtered-status.json", + }, + }, + { + desc: "tcp services filtered by search", + path: "/api/tcp/services?search=baz@my", + conf: runtime.Configuration{ + TCPServices: map[string]*runtime.TCPServiceInfo{ + "bar@myprovider": { + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPLoadBalancerService{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.1:2345", + }, + }, + }, + }, + UsedBy: []string{"foo@myprovider", "test@myprovider"}, + Status: runtime.StatusEnabled, + }, + "baz@myprovider": { + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPLoadBalancerService{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.2:2345", + }, + }, + }, + }, + UsedBy: []string{"foo@myprovider"}, + Status: runtime.StatusWarning, + }, + "foz@myprovider": { + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPLoadBalancerService{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.2:2345", + }, + }, + }, + }, + UsedBy: []string{"foo@myprovider"}, + Status: runtime.StatusDisabled, + }, + }, + }, + expected: expected{ + statusCode: http.StatusOK, + nextPage: "1", + jsonFile: "testdata/tcpservices-filtered-search.json", + }, + }, { desc: "all tcp services, 1 res per page, want page 2", path: "/api/tcp/services?page=2&per_page=1", @@ -330,6 +515,10 @@ func TestHandler_TCP(t *testing.T) { t.Parallel() rtConf := &test.conf + // To lazily initialize the Statuses. + rtConf.PopulateUsedBy() + rtConf.GetTCPRoutersByEntryPoints(context.Background(), []string{"web"}) + handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf) router := mux.NewRouter() handler.Append(router) diff --git a/pkg/api/testdata/middleware-auth.json b/pkg/api/testdata/middleware-auth.json index 0ed588873..3c7ff53fa 100644 --- a/pkg/api/testdata/middleware-auth.json +++ b/pkg/api/testdata/middleware-auth.json @@ -7,6 +7,7 @@ "name": "auth@myprovider", "provider": "myprovider", "status": "enabled", + "type": "basicauth", "usedBy": [ "bar@myprovider", "test@myprovider" diff --git a/pkg/api/testdata/middlewares-filtered-search.json b/pkg/api/testdata/middlewares-filtered-search.json new file mode 100644 index 000000000..ba1382268 --- /dev/null +++ b/pkg/api/testdata/middlewares-filtered-search.json @@ -0,0 +1,26 @@ +[ + { + "addPrefix": { + "prefix": "/toto" + }, + "name": "addPrefixTest@anotherprovider", + "provider": "anotherprovider", + "status": "enabled", + "type": "addprefix", + "usedBy": [ + "bar@myprovider" + ] + }, + { + "addPrefix": { + "prefix": "/titi" + }, + "name": "addPrefixTest@myprovider", + "provider": "myprovider", + "status": "disabled", + "type": "addprefix", + "usedBy": [ + "test@myprovider" + ] + } +] \ No newline at end of file diff --git a/pkg/api/testdata/middlewares-filtered-status.json b/pkg/api/testdata/middlewares-filtered-status.json new file mode 100644 index 000000000..eab60c6a4 --- /dev/null +++ b/pkg/api/testdata/middlewares-filtered-status.json @@ -0,0 +1,29 @@ +[ + { + "addPrefix": { + "prefix": "/toto" + }, + "name": "addPrefixTest@anotherprovider", + "provider": "anotherprovider", + "status": "enabled", + "type": "addprefix", + "usedBy": [ + "bar@myprovider" + ] + }, + { + "basicAuth": { + "users": [ + "admin:admin" + ] + }, + "name": "auth@myprovider", + "provider": "myprovider", + "status": "enabled", + "type": "basicauth", + "usedBy": [ + "bar@myprovider", + "test@myprovider" + ] + } +] \ No newline at end of file diff --git a/pkg/api/testdata/middlewares-page2.json b/pkg/api/testdata/middlewares-page2.json index 25ebc13f5..ecaf5978a 100644 --- a/pkg/api/testdata/middlewares-page2.json +++ b/pkg/api/testdata/middlewares-page2.json @@ -6,6 +6,7 @@ "name": "addPrefixTest@myprovider", "provider": "myprovider", "status": "enabled", + "type": "addprefix", "usedBy": [ "test@myprovider" ] diff --git a/pkg/api/testdata/middlewares.json b/pkg/api/testdata/middlewares.json index c27533ee1..f48f52efb 100644 --- a/pkg/api/testdata/middlewares.json +++ b/pkg/api/testdata/middlewares.json @@ -6,6 +6,7 @@ "name": "addPrefixTest@anotherprovider", "provider": "anotherprovider", "status": "enabled", + "type": "addprefix", "usedBy": [ "bar@myprovider" ] @@ -17,6 +18,7 @@ "name": "addPrefixTest@myprovider", "provider": "myprovider", "status": "enabled", + "type": "addprefix", "usedBy": [ "test@myprovider" ] @@ -30,6 +32,7 @@ "name": "auth@myprovider", "provider": "myprovider", "status": "enabled", + "type": "basicauth", "usedBy": [ "bar@myprovider", "test@myprovider" diff --git a/pkg/api/testdata/router-bar.json b/pkg/api/testdata/router-bar.json index 267a9628b..098ea0b1a 100644 --- a/pkg/api/testdata/router-bar.json +++ b/pkg/api/testdata/router-bar.json @@ -10,5 +10,8 @@ "provider": "myprovider", "rule": "Host(`foo.bar`)", "service": "foo-service@myprovider", - "status": "enabled" + "status": "enabled", + "using": [ + "web" + ] } \ No newline at end of file diff --git a/pkg/api/testdata/routers-filtered-search.json b/pkg/api/testdata/routers-filtered-search.json new file mode 100644 index 000000000..4e251f5da --- /dev/null +++ b/pkg/api/testdata/routers-filtered-search.json @@ -0,0 +1,19 @@ +[ + { + "entryPoints": [ + "web" + ], + "middlewares": [ + "addPrefixTest", + "auth" + ], + "name": "test@myprovider", + "provider": "myprovider", + "rule": "Host(`fii.bar.other`)", + "service": "fii-service@myprovider", + "status": "enabled", + "using": [ + "web" + ] + } +] \ No newline at end of file diff --git a/pkg/api/testdata/routers-filtered-status.json b/pkg/api/testdata/routers-filtered-status.json new file mode 100644 index 000000000..1c96e3802 --- /dev/null +++ b/pkg/api/testdata/routers-filtered-status.json @@ -0,0 +1,19 @@ +[ + { + "entryPoints": [ + "web" + ], + "middlewares": [ + "addPrefixTest", + "auth" + ], + "name": "test@myprovider", + "provider": "myprovider", + "rule": "Host(`foo.bar.other`)", + "service": "foo-service@myprovider", + "status": "enabled", + "using": [ + "web" + ] + } +] \ No newline at end of file diff --git a/pkg/api/testdata/routers-many-lastpage.json b/pkg/api/testdata/routers-many-lastpage.json index 290672375..df1ee7929 100644 --- a/pkg/api/testdata/routers-many-lastpage.json +++ b/pkg/api/testdata/routers-many-lastpage.json @@ -7,7 +7,10 @@ "provider": "myprovider", "rule": "Host(`foo.bar14`)", "service": "foo-service@myprovider", - "status": "enabled" + "status": "enabled", + "using": [ + "web" + ] }, { "entryPoints": [ @@ -17,7 +20,10 @@ "provider": "myprovider", "rule": "Host(`foo.bar15`)", "service": "foo-service@myprovider", - "status": "enabled" + "status": "enabled", + "using": [ + "web" + ] }, { "entryPoints": [ @@ -27,7 +33,10 @@ "provider": "myprovider", "rule": "Host(`foo.bar16`)", "service": "foo-service@myprovider", - "status": "enabled" + "status": "enabled", + "using": [ + "web" + ] }, { "entryPoints": [ @@ -37,7 +46,10 @@ "provider": "myprovider", "rule": "Host(`foo.bar17`)", "service": "foo-service@myprovider", - "status": "enabled" + "status": "enabled", + "using": [ + "web" + ] }, { "entryPoints": [ @@ -47,6 +59,9 @@ "provider": "myprovider", "rule": "Host(`foo.bar18`)", "service": "foo-service@myprovider", - "status": "enabled" + "status": "enabled", + "using": [ + "web" + ] } ] \ No newline at end of file diff --git a/pkg/api/testdata/routers-page2.json b/pkg/api/testdata/routers-page2.json index 8c7f65d1b..579e2a04f 100644 --- a/pkg/api/testdata/routers-page2.json +++ b/pkg/api/testdata/routers-page2.json @@ -7,6 +7,9 @@ "provider": "myprovider", "rule": "Host(`toto.bar`)", "service": "foo-service@myprovider", - "status": "enabled" + "status": "enabled", + "using": [ + "web" + ] } ] \ No newline at end of file diff --git a/pkg/api/testdata/routers.json b/pkg/api/testdata/routers.json index 664aedc51..ed0d10e28 100644 --- a/pkg/api/testdata/routers.json +++ b/pkg/api/testdata/routers.json @@ -11,7 +11,10 @@ "provider": "myprovider", "rule": "Host(`foo.bar`)", "service": "foo-service@myprovider", - "status": "enabled" + "status": "enabled", + "using": [ + "web" + ] }, { "entryPoints": [ @@ -25,6 +28,9 @@ "provider": "myprovider", "rule": "Host(`foo.bar.other`)", "service": "foo-service@myprovider", - "status": "enabled" + "status": "enabled", + "using": [ + "web" + ] } ] \ No newline at end of file diff --git a/pkg/api/testdata/service-bar.json b/pkg/api/testdata/service-bar.json index e26b65a72..11e832136 100644 --- a/pkg/api/testdata/service-bar.json +++ b/pkg/api/testdata/service-bar.json @@ -13,6 +13,7 @@ "http://127.0.0.1": "UP" }, "status": "enabled", + "type": "loadbalancer", "usedBy": [ "foo@myprovider", "test@myprovider" diff --git a/pkg/api/testdata/services-filtered-search.json b/pkg/api/testdata/services-filtered-search.json new file mode 100644 index 000000000..fd854ed72 --- /dev/null +++ b/pkg/api/testdata/services-filtered-search.json @@ -0,0 +1,22 @@ +[ + { + "loadBalancer": { + "passHostHeader": false, + "servers": [ + { + "url": "http://127.0.0.2" + } + ] + }, + "name": "baz@myprovider", + "provider": "myprovider", + "serverStatus": { + "http://127.0.0.2": "UP" + }, + "status": "disabled", + "type": "loadbalancer", + "usedBy": [ + "foo@myprovider" + ] + } +] \ No newline at end of file diff --git a/pkg/api/testdata/services-filtered-status.json b/pkg/api/testdata/services-filtered-status.json new file mode 100644 index 000000000..df1a52dcb --- /dev/null +++ b/pkg/api/testdata/services-filtered-status.json @@ -0,0 +1,23 @@ +[ + { + "loadBalancer": { + "passHostHeader": false, + "servers": [ + { + "url": "http://127.0.0.1" + } + ] + }, + "name": "bar@myprovider", + "provider": "myprovider", + "serverStatus": { + "http://127.0.0.1": "UP" + }, + "status": "enabled", + "type": "loadbalancer", + "usedBy": [ + "foo@myprovider", + "test@myprovider" + ] + } +] \ No newline at end of file diff --git a/pkg/api/testdata/services-page2.json b/pkg/api/testdata/services-page2.json index 66b8390d1..9b8b5ce72 100644 --- a/pkg/api/testdata/services-page2.json +++ b/pkg/api/testdata/services-page2.json @@ -14,6 +14,7 @@ "http://127.0.0.2": "UP" }, "status": "enabled", + "type": "loadbalancer", "usedBy": [ "foo@myprovider" ] diff --git a/pkg/api/testdata/services.json b/pkg/api/testdata/services.json index bd54b536c..e96abc23b 100644 --- a/pkg/api/testdata/services.json +++ b/pkg/api/testdata/services.json @@ -14,6 +14,7 @@ "http://127.0.0.1": "UP" }, "status": "enabled", + "type": "loadbalancer", "usedBy": [ "foo@myprovider", "test@myprovider" @@ -34,6 +35,51 @@ "http://127.0.0.2": "UP" }, "status": "enabled", + "type": "loadbalancer", + "usedBy": [ + "foo@myprovider" + ] + }, + { + "name": "canary@myprovider", + "provider": "myprovider", + "status": "enabled", + "type": "weighted", + "usedBy": [ + "foo@myprovider" + ], + "weighted": { + "sticky": { + "cookie": { + "httpOnly": true, + "name": "chocolat", + "secure": true + } + } + } + }, + { + "mirroring": { + "mirrors": [ + { + "name": "two@myprovider", + "percent": 10 + }, + { + "name": "three@myprovider", + "percent": 15 + }, + { + "name": "four@myprovider", + "percent": 80 + } + ], + "service": "one@myprovider" + }, + "name": "mirror@myprovider", + "provider": "myprovider", + "status": "enabled", + "type": "mirroring", "usedBy": [ "foo@myprovider" ] diff --git a/pkg/api/testdata/tcprouter-bar.json b/pkg/api/testdata/tcprouter-bar.json index b6b244199..70d06a3ea 100644 --- a/pkg/api/testdata/tcprouter-bar.json +++ b/pkg/api/testdata/tcprouter-bar.json @@ -5,5 +5,9 @@ "name": "bar@myprovider", "provider": "myprovider", "rule": "Host(`foo.bar`)", - "service": "foo-service@myprovider" + "service": "foo-service@myprovider", + "status": "enabled", + "using": [ + "web" + ] } \ No newline at end of file diff --git a/pkg/api/testdata/tcprouters-filtered-search.json b/pkg/api/testdata/tcprouters-filtered-search.json new file mode 100644 index 000000000..4593f1b03 --- /dev/null +++ b/pkg/api/testdata/tcprouters-filtered-search.json @@ -0,0 +1,15 @@ +[ + { + "entryPoints": [ + "web" + ], + "name": "bar@myprovider", + "provider": "myprovider", + "rule": "Host(`foo.bar`)", + "service": "foo-service@myprovider", + "status": "warning", + "using": [ + "web" + ] + } +] \ No newline at end of file diff --git a/pkg/api/testdata/tcprouters-filtered-status.json b/pkg/api/testdata/tcprouters-filtered-status.json new file mode 100644 index 000000000..64d232ebc --- /dev/null +++ b/pkg/api/testdata/tcprouters-filtered-status.json @@ -0,0 +1,18 @@ +[ + { + "entryPoints": [ + "web" + ], + "name": "test@myprovider", + "provider": "myprovider", + "rule": "Host(`foo.bar.other`)", + "service": "foo-service@myprovider", + "status": "enabled", + "tls": { + "passthrough": false + }, + "using": [ + "web" + ] + } +] \ No newline at end of file diff --git a/pkg/api/testdata/tcprouters-page2.json b/pkg/api/testdata/tcprouters-page2.json index 73d97c6d6..579e2a04f 100644 --- a/pkg/api/testdata/tcprouters-page2.json +++ b/pkg/api/testdata/tcprouters-page2.json @@ -6,6 +6,10 @@ "name": "baz@myprovider", "provider": "myprovider", "rule": "Host(`toto.bar`)", - "service": "foo-service@myprovider" + "service": "foo-service@myprovider", + "status": "enabled", + "using": [ + "web" + ] } ] \ No newline at end of file diff --git a/pkg/api/testdata/tcprouters.json b/pkg/api/testdata/tcprouters.json index a2bf3c401..bf6bffc17 100644 --- a/pkg/api/testdata/tcprouters.json +++ b/pkg/api/testdata/tcprouters.json @@ -7,7 +7,10 @@ "provider": "myprovider", "rule": "Host(`foo.bar`)", "service": "foo-service@myprovider", - "status": "warning" + "status": "warning", + "using": [ + "web" + ] }, { "entryPoints": [ @@ -17,7 +20,10 @@ "provider": "myprovider", "rule": "Host(`foo.bar`)", "service": "foo-service@myprovider", - "status": "disabled" + "status": "disabled", + "using": [ + "web" + ] }, { "entryPoints": [ @@ -30,6 +36,9 @@ "status": "enabled", "tls": { "passthrough": false - } + }, + "using": [ + "web" + ] } ] \ No newline at end of file diff --git a/pkg/api/testdata/tcpservice-bar.json b/pkg/api/testdata/tcpservice-bar.json index 114f0b74b..ade480a92 100644 --- a/pkg/api/testdata/tcpservice-bar.json +++ b/pkg/api/testdata/tcpservice-bar.json @@ -8,6 +8,8 @@ }, "name": "bar@myprovider", "provider": "myprovider", + "status": "enabled", + "type": "loadbalancer", "usedBy": [ "foo@myprovider", "test@myprovider" diff --git a/pkg/api/testdata/tcpservices-filtered-search.json b/pkg/api/testdata/tcpservices-filtered-search.json new file mode 100644 index 000000000..130d5eace --- /dev/null +++ b/pkg/api/testdata/tcpservices-filtered-search.json @@ -0,0 +1,18 @@ +[ + { + "loadBalancer": { + "servers": [ + { + "address": "127.0.0.2:2345" + } + ] + }, + "name": "baz@myprovider", + "provider": "myprovider", + "status": "warning", + "type": "loadbalancer", + "usedBy": [ + "foo@myprovider" + ] + } +] \ No newline at end of file diff --git a/pkg/api/testdata/tcpservices-filtered-status.json b/pkg/api/testdata/tcpservices-filtered-status.json new file mode 100644 index 000000000..03ec085a0 --- /dev/null +++ b/pkg/api/testdata/tcpservices-filtered-status.json @@ -0,0 +1,19 @@ +[ + { + "loadBalancer": { + "servers": [ + { + "address": "127.0.0.1:2345" + } + ] + }, + "name": "bar@myprovider", + "provider": "myprovider", + "status": "enabled", + "type": "loadbalancer", + "usedBy": [ + "foo@myprovider", + "test@myprovider" + ] + } +] \ No newline at end of file diff --git a/pkg/api/testdata/tcpservices-page2.json b/pkg/api/testdata/tcpservices-page2.json index 345151040..414e0f37d 100644 --- a/pkg/api/testdata/tcpservices-page2.json +++ b/pkg/api/testdata/tcpservices-page2.json @@ -9,6 +9,8 @@ }, "name": "baz@myprovider", "provider": "myprovider", + "status": "enabled", + "type": "loadbalancer", "usedBy": [ "foo@myprovider" ] diff --git a/pkg/api/testdata/tcpservices.json b/pkg/api/testdata/tcpservices.json index 24320d948..c3f9f7ea6 100644 --- a/pkg/api/testdata/tcpservices.json +++ b/pkg/api/testdata/tcpservices.json @@ -10,6 +10,7 @@ "name": "bar@myprovider", "provider": "myprovider", "status": "enabled", + "type": "loadbalancer", "usedBy": [ "foo@myprovider", "test@myprovider" @@ -26,6 +27,7 @@ "name": "baz@myprovider", "provider": "myprovider", "status": "warning", + "type": "loadbalancer", "usedBy": [ "foo@myprovider" ] @@ -41,6 +43,7 @@ "name": "foz@myprovider", "provider": "myprovider", "status": "disabled", + "type": "loadbalancer", "usedBy": [ "foo@myprovider" ] diff --git a/pkg/config/dynamic/fixtures/sample.toml b/pkg/config/dynamic/fixtures/sample.toml index a90cee3f6..122a58398 100644 --- a/pkg/config/dynamic/fixtures/sample.toml +++ b/pkg/config/dynamic/fixtures/sample.toml @@ -119,7 +119,7 @@ buckets = [42.0, 42.0] entryPoint = "foobar" middlewares = ["foobar", "foobar"] - [metrics.dataDog] + [metrics.datadog] address = "foobar" pushInterval = "10s" [metrics.statsD] @@ -179,7 +179,7 @@ id128Bit = true debug = true sampleRate = 42.0 - [tracing.dataDog] + [tracing.datadog] localAgentHostPort = "foobar" globalTag = "foobar" debug = true @@ -249,16 +249,14 @@ prefixes = ["foobar", "foobar"] [http.middlewares.Middleware10] [http.middlewares.Middleware10.rateLimit] - extractorFunc = "foobar" - [http.middlewares.Middleware10.rateLimit.rateSet] - [http.middlewares.Middleware10.rateLimit.rateSet.Rate0] - period = 42000000000 - average = 42 - burst = 42 - [http.middlewares.Middleware10.rateLimit.rateSet.Rate1] - period = 42000000000 - average = 42 - burst = 42 + average = 42 + burst = 42 + [http.middlewares.Middleware10.rateLimit.sourceCriterion] + requestHeaderName = "foobar" + requestHost = true + [http.middlewares.Middleware10.rateLimit.sourceCriterion.ipStrategy] + depth = 42 + excludedIPs = ["foobar", "foobar"] [http.middlewares.Middleware11] [http.middlewares.Middleware11.redirectRegex] regex = "foobar" diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go index 84aefd044..fa391b000 100644 --- a/pkg/config/dynamic/zz_generated.deepcopy.go +++ b/pkg/config/dynamic/zz_generated.deepcopy.go @@ -687,6 +687,43 @@ func (in *Middleware) DeepCopy() *Middleware { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MirrorService) DeepCopyInto(out *MirrorService) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MirrorService. +func (in *MirrorService) DeepCopy() *MirrorService { + if in == nil { + return nil + } + out := new(MirrorService) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Mirroring) DeepCopyInto(out *Mirroring) { + *out = *in + if in.Mirrors != nil { + in, out := &in.Mirrors, &out.Mirrors + *out = make([]MirrorService, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Mirroring. +func (in *Mirroring) DeepCopy() *Mirroring { + if in == nil { + return nil + } + out := new(Mirroring) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PassTLSClientCert) DeepCopyInto(out *PassTLSClientCert) { *out = *in @@ -967,6 +1004,11 @@ func (in *Service) DeepCopyInto(out *Service) { *out = new(WeightedRoundRobin) (*in).DeepCopyInto(*out) } + if in.Mirroring != nil { + in, out := &in.Mirroring, &out.Mirroring + *out = new(Mirroring) + (*in).DeepCopyInto(*out) + } return } diff --git a/pkg/config/file/file_node_test.go b/pkg/config/file/file_node_test.go index 1d9db2cde..4b5b4e975 100644 --- a/pkg/config/file/file_node_test.go +++ b/pkg/config/file/file_node_test.go @@ -143,7 +143,7 @@ func Test_decodeFileToNode_Toml(t *testing.T) { {Name: "format", Value: "foobar"}, {Name: "level", Value: "foobar"}}}, {Name: "metrics", Children: []*parser.Node{ - {Name: "dataDog", Children: []*parser.Node{ + {Name: "datadog", Children: []*parser.Node{ {Name: "address", Value: "foobar"}, {Name: "pushInterval", Value: "10s"}}}, {Name: "influxDB", Children: []*parser.Node{ @@ -251,7 +251,7 @@ func Test_decodeFileToNode_Toml(t *testing.T) { {Name: "maxIdleConnsPerHost", Value: "42"}, {Name: "rootCAs", Value: "foobar,foobar"}}}, {Name: "tracing", Children: []*parser.Node{ - {Name: "dataDog", Children: []*parser.Node{ + {Name: "datadog", Children: []*parser.Node{ {Name: "bagagePrefixHeaderName", Value: "foobar"}, {Name: "debug", Value: "true"}, {Name: "globalTag", Value: "foobar"}, @@ -282,7 +282,6 @@ func Test_decodeFileToNode_Toml(t *testing.T) { {Name: "serviceName", Value: "foobar"}, {Name: "spanNameLimit", Value: "42"}, {Name: "zipkin", Children: []*parser.Node{ - {Name: "debug", Value: "true"}, {Name: "httpEndpoint", Value: "foobar"}, {Name: "id128Bit", Value: "true"}, {Name: "sameSpan", Value: "true"}, @@ -378,7 +377,7 @@ func Test_decodeFileToNode_Yaml(t *testing.T) { {Name: "format", Value: "foobar"}, {Name: "level", Value: "foobar"}}}, {Name: "metrics", Children: []*parser.Node{ - {Name: "dataDog", Children: []*parser.Node{ + {Name: "datadog", Children: []*parser.Node{ {Name: "address", Value: "foobar"}, {Name: "pushInterval", Value: "10s"}}}, {Name: "influxDB", Children: []*parser.Node{ @@ -486,7 +485,7 @@ func Test_decodeFileToNode_Yaml(t *testing.T) { {Name: "maxIdleConnsPerHost", Value: "42"}, {Name: "rootCAs", Value: "foobar,foobar"}}}, {Name: "tracing", Children: []*parser.Node{ - {Name: "dataDog", Children: []*parser.Node{ + {Name: "datadog", Children: []*parser.Node{ {Name: "bagagePrefixHeaderName", Value: "foobar"}, {Name: "debug", Value: "true"}, {Name: "globalTag", Value: "foobar"}, @@ -517,7 +516,6 @@ func Test_decodeFileToNode_Yaml(t *testing.T) { {Name: "serviceName", Value: "foobar"}, {Name: "spanNameLimit", Value: "42"}, {Name: "zipkin", Children: []*parser.Node{ - {Name: "debug", Value: "true"}, {Name: "httpEndpoint", Value: "foobar"}, {Name: "id128Bit", Value: "true"}, {Name: "sameSpan", Value: "true"}, diff --git a/pkg/config/file/fixtures/sample.toml b/pkg/config/file/fixtures/sample.toml index 920ee573b..4da227f2b 100644 --- a/pkg/config/file/fixtures/sample.toml +++ b/pkg/config/file/fixtures/sample.toml @@ -119,7 +119,7 @@ buckets = [42.0, 42.0] entryPoint = "foobar" middlewares = ["foobar", "foobar"] - [metrics.dataDog] + [metrics.datadog] address = "foobar" pushInterval = "10s" [metrics.statsD] @@ -177,9 +177,8 @@ httpEndpoint = "foobar" sameSpan = true id128Bit = true - debug = true sampleRate = 42.0 - [tracing.dataDog] + [tracing.datadog] localAgentHostPort = "foobar" globalTag = "foobar" debug = true @@ -241,16 +240,14 @@ prefixes = ["foobar", "foobar"] [http.middlewares.Middleware10] [http.middlewares.Middleware10.rateLimit] - extractorFunc = "foobar" - [http.middlewares.Middleware10.rateLimit.rateSet] - [http.middlewares.Middleware10.rateLimit.rateSet.Rate0] - period = 42000000000 - average = 42 - burst = 42 - [http.middlewares.Middleware10.rateLimit.rateSet.Rate1] - period = 42000000000 - average = 42 - burst = 42 + average = 42 + burst = 42 + [http.middlewares.Middleware10.rateLimit.sourceCriterion] + requestHeaderName = "foobar" + requestHost = true + [http.middlewares.Middleware10.rateLimit.sourceCriterion.ipStrategy] + depth = 42 + excludedIPs = ["foobar", "foobar"] [http.middlewares.Middleware11] [http.middlewares.Middleware11.redirectRegex] regex = "foobar" diff --git a/pkg/config/file/fixtures/sample.yml b/pkg/config/file/fixtures/sample.yml index 1cbd3e724..b1bf1ac47 100644 --- a/pkg/config/file/fixtures/sample.yml +++ b/pkg/config/file/fixtures/sample.yml @@ -130,7 +130,7 @@ metrics: middlewares: - foobar - foobar - dataDog: + datadog: address: foobar pushInterval: 10s statsD: @@ -188,9 +188,8 @@ tracing: httpEndpoint: foobar sameSpan: true id128Bit: true - debug: true sampleRate: 42 - dataDog: + datadog: localAgentHostPort: foobar globalTag: foobar debug: true diff --git a/pkg/config/runtime/runtime_http.go b/pkg/config/runtime/runtime_http.go index 1c6b9e4aa..af5062fd9 100644 --- a/pkg/config/runtime/runtime_http.go +++ b/pkg/config/runtime/runtime_http.go @@ -2,13 +2,15 @@ package runtime import ( "context" + "fmt" + "sort" "sync" "github.com/containous/traefik/v2/pkg/config/dynamic" "github.com/containous/traefik/v2/pkg/log" ) -// GetRoutersByEntryPoints returns all the http routers by entry points name and routers name +// GetRoutersByEntryPoints returns all the http routers by entry points name and routers name. func (c *Configuration) GetRoutersByEntryPoints(ctx context.Context, entryPoints []string, tls bool) map[string]map[string]*RouterInfo { entryPointsRouters := make(map[string]map[string]*RouterInfo) @@ -17,13 +19,19 @@ func (c *Configuration) GetRoutersByEntryPoints(ctx context.Context, entryPoints continue } + logger := log.FromContext(log.With(ctx, log.Str(log.RouterName, rtName))) + eps := rt.EntryPoints if len(eps) == 0 { + logger.Debugf("No entryPoint defined for this router, using the default one(s) instead: %+v", entryPoints) eps = entryPoints } + + entryPointsCount := 0 for _, entryPointName := range eps { if !contains(entryPoints, entryPointName) { - log.FromContext(log.With(ctx, log.Str(log.EntryPointName, entryPointName))). + rt.AddError(fmt.Errorf("entryPoint %q doesn't exist", entryPointName), false) + logger.WithField(log.EntryPointName, entryPointName). Errorf("entryPoint %q doesn't exist", entryPointName) continue } @@ -32,14 +40,40 @@ func (c *Configuration) GetRoutersByEntryPoints(ctx context.Context, entryPoints entryPointsRouters[entryPointName] = make(map[string]*RouterInfo) } + entryPointsCount++ + rt.Using = append(rt.Using, entryPointName) + entryPointsRouters[entryPointName][rtName] = rt } + + if entryPointsCount == 0 { + rt.AddError(fmt.Errorf("no valid entryPoint for this router"), true) + logger.Error("no valid entryPoint for this router") + } + + rt.Using = unique(rt.Using) } return entryPointsRouters } -// RouterInfo holds information about a currently running HTTP router +func unique(src []string) []string { + var uniq []string + + set := make(map[string]struct{}) + for _, v := range src { + if _, exist := set[v]; !exist { + set[v] = struct{}{} + uniq = append(uniq, v) + } + } + + sort.Strings(uniq) + + return uniq +} + +// RouterInfo holds information about a currently running HTTP router. type RouterInfo struct { *dynamic.Router // dynamic configuration // Err contains all the errors that occurred during router's creation. @@ -47,7 +81,8 @@ type RouterInfo struct { // Status reports whether the router is disabled, in a warning state, or all good (enabled). // If not in "enabled" state, the reason for it should be in the list of Err. // It is the caller's responsibility to set the initial status. - Status string `json:"status,omitempty"` + Status string `json:"status,omitempty"` + Using []string `json:"using,omitempty"` // Effective entry points used by that router. } // AddError adds err to r.Err, if it does not already exist. @@ -71,13 +106,13 @@ func (r *RouterInfo) AddError(err error, critical bool) { } } -// MiddlewareInfo holds information about a currently running middleware +// MiddlewareInfo holds information about a currently running middleware. type MiddlewareInfo struct { *dynamic.Middleware // dynamic configuration // Err contains all the errors that occurred during service creation. Err []string `json:"error,omitempty"` Status string `json:"status,omitempty"` - UsedBy []string `json:"usedBy,omitempty"` // list of routers and services using that middleware + UsedBy []string `json:"usedBy,omitempty"` // list of routers and services using that middleware. } // AddError adds err to s.Err, if it does not already exist. @@ -101,7 +136,7 @@ func (m *MiddlewareInfo) AddError(err error, critical bool) { } } -// ServiceInfo holds information about a currently running service +// ServiceInfo holds information about a currently running service. type ServiceInfo struct { *dynamic.Service // dynamic configuration // Err contains all the errors that occurred during service creation. diff --git a/pkg/config/runtime/runtime_http_test.go b/pkg/config/runtime/runtime_http_test.go index 38102c61d..909d664b7 100644 --- a/pkg/config/runtime/runtime_http_test.go +++ b/pkg/config/runtime/runtime_http_test.go @@ -104,6 +104,7 @@ func TestGetRoutersByEntryPoints(t *testing.T) { Rule: "Host(`bar.foo`)", }, Status: "enabled", + Using: []string{"web"}, }, "foobar": { Router: &dynamic.Router{ @@ -111,7 +112,9 @@ func TestGetRoutersByEntryPoints(t *testing.T) { Service: "foobar-service@myprovider", Rule: "Host(`bar.foobar`)", }, - Status: "enabled", + Status: "warning", + Err: []string{`entryPoint "webs" doesn't exist`}, + Using: []string{"web"}, }, }, }, @@ -168,6 +171,7 @@ func TestGetRoutersByEntryPoints(t *testing.T) { Rule: "Host(`bar.foo`)", }, Status: "enabled", + Using: []string{"web"}, }, "foobar": { Router: &dynamic.Router{ @@ -176,6 +180,7 @@ func TestGetRoutersByEntryPoints(t *testing.T) { Rule: "Host(`bar.foobar`)", }, Status: "enabled", + Using: []string{"web", "webs"}, }, }, "webs": { @@ -187,6 +192,7 @@ func TestGetRoutersByEntryPoints(t *testing.T) { Rule: "Host(`foo.bar`)", }, Status: "enabled", + Using: []string{"webs"}, }, "foobar": { Router: &dynamic.Router{ @@ -195,6 +201,7 @@ func TestGetRoutersByEntryPoints(t *testing.T) { Rule: "Host(`bar.foobar`)", }, Status: "enabled", + Using: []string{"web", "webs"}, }, }, }, diff --git a/pkg/config/runtime/runtime_tcp.go b/pkg/config/runtime/runtime_tcp.go index a99d519f9..427412bdb 100644 --- a/pkg/config/runtime/runtime_tcp.go +++ b/pkg/config/runtime/runtime_tcp.go @@ -2,24 +2,30 @@ package runtime import ( "context" + "fmt" "github.com/containous/traefik/v2/pkg/config/dynamic" "github.com/containous/traefik/v2/pkg/log" ) -// GetTCPRoutersByEntryPoints returns all the tcp routers by entry points name and routers name +// GetTCPRoutersByEntryPoints returns all the tcp routers by entry points name and routers name. func (c *Configuration) GetTCPRoutersByEntryPoints(ctx context.Context, entryPoints []string) map[string]map[string]*TCPRouterInfo { entryPointsRouters := make(map[string]map[string]*TCPRouterInfo) for rtName, rt := range c.TCPRouters { + logger := log.FromContext(log.With(ctx, log.Str(log.RouterName, rtName))) + eps := rt.EntryPoints if len(eps) == 0 { + logger.Debugf("No entryPoint defined for this router, using the default one(s) instead: %+v", entryPoints) eps = entryPoints } + entryPointsCount := 0 for _, entryPointName := range eps { if !contains(entryPoints, entryPointName) { - log.FromContext(log.With(ctx, log.Str(log.EntryPointName, entryPointName))). + rt.AddError(fmt.Errorf("entryPoint %q doesn't exist", entryPointName), false) + logger.WithField(log.EntryPointName, entryPointName). Errorf("entryPoint %q doesn't exist", entryPointName) continue } @@ -28,21 +34,30 @@ func (c *Configuration) GetTCPRoutersByEntryPoints(ctx context.Context, entryPoi entryPointsRouters[entryPointName] = make(map[string]*TCPRouterInfo) } + entryPointsCount++ + rt.Using = append(rt.Using, entryPointName) + entryPointsRouters[entryPointName][rtName] = rt } + + if entryPointsCount == 0 { + rt.AddError(fmt.Errorf("no valid entryPoint for this router"), true) + logger.Error("no valid entryPoint for this router") + } } return entryPointsRouters } -// TCPRouterInfo holds information about a currently running TCP router +// TCPRouterInfo holds information about a currently running TCP router. type TCPRouterInfo struct { *dynamic.TCPRouter // dynamic configuration Err []string `json:"error,omitempty"` // initialization error // Status reports whether the router is disabled, in a warning state, or all good (enabled). // If not in "enabled" state, the reason for it should be in the list of Err. // It is the caller's responsibility to set the initial status. - Status string `json:"status,omitempty"` + Status string `json:"status,omitempty"` + Using []string `json:"using,omitempty"` // Effective entry points used by that router. } // AddError adds err to r.Err, if it does not already exist. @@ -66,7 +81,7 @@ func (r *TCPRouterInfo) AddError(err error, critical bool) { } } -// TCPServiceInfo holds information about a currently running TCP service +// TCPServiceInfo holds information about a currently running TCP service. type TCPServiceInfo struct { *dynamic.TCPService // dynamic configuration Err []string `json:"error,omitempty"` // initialization error diff --git a/pkg/config/runtime/runtime_tcp_test.go b/pkg/config/runtime/runtime_tcp_test.go index 9fb3f137e..8df42fc9b 100644 --- a/pkg/config/runtime/runtime_tcp_test.go +++ b/pkg/config/runtime/runtime_tcp_test.go @@ -104,6 +104,7 @@ func TestGetTCPRoutersByEntryPoints(t *testing.T) { Rule: "HostSNI(`bar.foo`)", }, Status: "enabled", + Using: []string{"web"}, }, "foobar": { TCPRouter: &dynamic.TCPRouter{ @@ -111,7 +112,9 @@ func TestGetTCPRoutersByEntryPoints(t *testing.T) { Service: "foobar-service@myprovider", Rule: "HostSNI(`bar.foobar`)", }, - Status: "enabled", + Status: "warning", + Err: []string{`entryPoint "webs" doesn't exist`}, + Using: []string{"web"}, }, }, }, @@ -168,6 +171,7 @@ func TestGetTCPRoutersByEntryPoints(t *testing.T) { Rule: "HostSNI(`bar.foo`)", }, Status: "enabled", + Using: []string{"web"}, }, "foobar": { TCPRouter: &dynamic.TCPRouter{ @@ -176,6 +180,7 @@ func TestGetTCPRoutersByEntryPoints(t *testing.T) { Rule: "HostSNI(`bar.foobar`)", }, Status: "enabled", + Using: []string{"web", "webs"}, }, }, "webs": { @@ -187,6 +192,7 @@ func TestGetTCPRoutersByEntryPoints(t *testing.T) { Rule: "HostSNI(`foo.bar`)", }, Status: "enabled", + Using: []string{"webs"}, }, "foobar": { TCPRouter: &dynamic.TCPRouter{ @@ -195,6 +201,7 @@ func TestGetTCPRoutersByEntryPoints(t *testing.T) { Rule: "HostSNI(`bar.foobar`)", }, Status: "enabled", + Using: []string{"web", "webs"}, }, }, }, diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index a701b5e6c..f7caa20cc 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -85,6 +85,7 @@ type ServersTransport struct { // API holds the API configuration type API struct { + Insecure bool `description:"Activate API directly on the entryPoint named traefik." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"` Dashboard bool `description:"Activate dashboard." json:"dashboard,omitempty" toml:"dashboard,omitempty" yaml:"dashboard,omitempty" export:"true"` Debug bool `description:"Enable additional endpoints for debugging and profiling." json:"debug,omitempty" toml:"debug,omitempty" yaml:"debug,omitempty" export:"true"` // TODO: Re-enable statistics @@ -139,7 +140,7 @@ type Tracing struct { SpanNameLimit int `description:"Set the maximum character limit for Span names (default 0 = no limit)." json:"spanNameLimit,omitempty" toml:"spanNameLimit,omitempty" yaml:"spanNameLimit,omitempty" export:"true"` Jaeger *jaeger.Config `description:"Settings for Jaeger." json:"jaeger,omitempty" toml:"jaeger,omitempty" yaml:"jaeger,omitempty" export:"true" label:"allowEmpty"` Zipkin *zipkin.Config `description:"Settings for Zipkin." json:"zipkin,omitempty" toml:"zipkin,omitempty" yaml:"zipkin,omitempty" export:"true" label:"allowEmpty"` - DataDog *datadog.Config `description:"Settings for DataDog." json:"dataDog,omitempty" toml:"dataDog,omitempty" yaml:"dataDog,omitempty" export:"true" label:"allowEmpty"` + Datadog *datadog.Config `description:"Settings for Datadog." json:"datadog,omitempty" toml:"datadog,omitempty" yaml:"datadog,omitempty" export:"true" label:"allowEmpty"` Instana *instana.Config `description:"Settings for Instana." json:"instana,omitempty" toml:"instana,omitempty" yaml:"instana,omitempty" export:"true" label:"allowEmpty"` Haystack *haystack.Config `description:"Settings for Haystack." json:"haystack,omitempty" toml:"haystack,omitempty" yaml:"haystack,omitempty" export:"true" label:"allowEmpty"` } @@ -173,9 +174,9 @@ func (c *Configuration) SetEffectiveConfiguration() { } } - if (c.API != nil) || - (c.Ping != nil) || - (c.Metrics != nil && c.Metrics.Prometheus != nil) || + if (c.API != nil && c.API.Insecure) || + (c.Ping != nil && c.Ping.EntryPoint == DefaultInternalEntryPointName) || + (c.Metrics != nil && c.Metrics.Prometheus != nil && c.Metrics.Prometheus.EntryPoint == DefaultInternalEntryPointName) || (c.Providers.Rest != nil) { if _, ok := c.EntryPoints[DefaultInternalEntryPointName]; !ok { ep := &EntryPoint{Address: ":8080"} diff --git a/pkg/metrics/datadog.go b/pkg/metrics/datadog.go index fcd564461..988fb0b7a 100644 --- a/pkg/metrics/datadog.go +++ b/pkg/metrics/datadog.go @@ -35,7 +35,7 @@ const ( ) // RegisterDatadog registers the metrics pusher if this didn't happen yet and creates a datadog Registry instance. -func RegisterDatadog(ctx context.Context, config *types.DataDog) Registry { +func RegisterDatadog(ctx context.Context, config *types.Datadog) Registry { if datadogTicker == nil { datadogTicker = initDatadogClient(ctx, config) } @@ -66,7 +66,7 @@ func RegisterDatadog(ctx context.Context, config *types.DataDog) Registry { return registry } -func initDatadogClient(ctx context.Context, config *types.DataDog) *time.Ticker { +func initDatadogClient(ctx context.Context, config *types.Datadog) *time.Ticker { address := config.Address if len(address) == 0 { address = "localhost:8125" diff --git a/pkg/metrics/datadog_test.go b/pkg/metrics/datadog_test.go index 32e4fc1d7..08a85061c 100644 --- a/pkg/metrics/datadog_test.go +++ b/pkg/metrics/datadog_test.go @@ -16,7 +16,7 @@ func TestDatadog(t *testing.T) { // This is needed to make sure that UDP Listener listens for data a bit longer, otherwise it will quit after a millisecond udp.Timeout = 5 * time.Second - datadogRegistry := RegisterDatadog(context.Background(), &types.DataDog{Address: ":18125", PushInterval: types.Duration(time.Second), AddEntryPointsLabels: true, AddServicesLabels: true}) + datadogRegistry := RegisterDatadog(context.Background(), &types.Datadog{Address: ":18125", PushInterval: types.Duration(time.Second), AddEntryPointsLabels: true, AddServicesLabels: true}) defer StopDatadog() if !datadogRegistry.IsEpEnabled() || !datadogRegistry.IsSvcEnabled() { diff --git a/pkg/middlewares/accesslog/logger.go b/pkg/middlewares/accesslog/logger.go index 4cce7fe06..1f6b80b4b 100644 --- a/pkg/middlewares/accesslog/logger.go +++ b/pkg/middlewares/accesslog/logger.go @@ -77,7 +77,8 @@ func NewHandler(config *types.AccessLog) (*Handler, error) { case JSONFormat: formatter = new(logrus.JSONFormatter) default: - return nil, fmt.Errorf("unsupported access log format: %s", config.Format) + log.WithoutContext().Errorf("unsupported access log format: %q, defaulting to common format instead.", config.Format) + formatter = new(CommonLogFormatter) } logger := &logrus.Logger{ diff --git a/pkg/middlewares/buffering/buffering.go b/pkg/middlewares/buffering/buffering.go index 97fdd1314..e6183a723 100644 --- a/pkg/middlewares/buffering/buffering.go +++ b/pkg/middlewares/buffering/buffering.go @@ -24,7 +24,7 @@ type buffer struct { func New(ctx context.Context, next http.Handler, config dynamic.Buffering, name string) (http.Handler, error) { logger := middlewares.GetLogger(ctx, name, typeName) logger.Debug("Creating middleware") - logger.Debug("Setting up buffering: request limits: %d (mem), %d (max), response limits: %d (mem), %d (max) with retry: '%s'", + logger.Debugf("Setting up buffering: request limits: %d (mem), %d (max), response limits: %d (mem), %d (max) with retry: '%s'", config.MemRequestBodyBytes, config.MaxRequestBodyBytes, config.MemResponseBodyBytes, config.MaxResponseBodyBytes, config.RetryExpression) oxyBuffer, err := oxybuffer.New( diff --git a/pkg/middlewares/ratelimiter/rate_limiter_test.go b/pkg/middlewares/ratelimiter/rate_limiter_test.go index 94fcedba8..6273ca356 100644 --- a/pkg/middlewares/ratelimiter/rate_limiter_test.go +++ b/pkg/middlewares/ratelimiter/rate_limiter_test.go @@ -73,9 +73,11 @@ func TestNewRateLimiter(t *testing.T) { func TestRateLimit(t *testing.T) { testCases := []struct { - desc string - config dynamic.RateLimit - reqCount int + desc string + config dynamic.RateLimit + loadDuration time.Duration + incomingLoad int // in reqs/s + burst int }{ { desc: "Average is respected", @@ -83,15 +85,47 @@ func TestRateLimit(t *testing.T) { Average: 100, Burst: 1, }, - reqCount: 200, + loadDuration: 2 * time.Second, + incomingLoad: 400, }, { - desc: "Burst is taken into account", + desc: "burst allowed, no bursty traffic", + config: dynamic.RateLimit{ + Average: 100, + Burst: 100, + }, + loadDuration: 2 * time.Second, + incomingLoad: 200, + }, + { + desc: "burst allowed, initial burst, under capacity", + config: dynamic.RateLimit{ + Average: 100, + Burst: 100, + }, + loadDuration: 2 * time.Second, + incomingLoad: 200, + burst: 50, + }, + { + desc: "burst allowed, initial burst, over capacity", + config: dynamic.RateLimit{ + Average: 100, + Burst: 100, + }, + loadDuration: 2 * time.Second, + incomingLoad: 200, + burst: 150, + }, + { + desc: "burst over average, initial burst, over capacity", config: dynamic.RateLimit{ Average: 100, Burst: 200, }, - reqCount: 300, + loadDuration: 2 * time.Second, + incomingLoad: 200, + burst: 300, }, { desc: "Zero average ==> no rate limiting", @@ -99,26 +133,32 @@ func TestRateLimit(t *testing.T) { Average: 0, Burst: 1, }, - reqCount: 100, + incomingLoad: 1000, + loadDuration: time.Second, }, } for _, test := range testCases { test := test t.Run(test.desc, func(t *testing.T) { + t.Parallel() reqCount := 0 + dropped := 0 next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { reqCount++ }) - h, err := New(context.Background(), next, test.config, "rate-limiter") require.NoError(t, err) + period := time.Duration(1e9 / test.incomingLoad) start := time.Now() + end := start.Add(test.loadDuration) + ticker := time.NewTicker(period) + defer ticker.Stop() for { - if reqCount >= test.reqCount { + if time.Now().After(end) { break } @@ -127,34 +167,45 @@ func TestRateLimit(t *testing.T) { w := httptest.NewRecorder() h.ServeHTTP(w, req) - // TODO(mpl): predict and count the 200 VS the 429? + if w.Result().StatusCode != http.StatusOK { + dropped++ + } + if test.burst > 0 && reqCount < test.burst { + // if a burst is defined we first hammer the server with test.burst requests as fast as possible + continue + } + <-ticker.C } - stop := time.Now() elapsed := stop.Sub(start) + if test.config.Average == 0 { - if elapsed > time.Millisecond { - t.Fatalf("rate should not have been limited, but: %d requests in %v", reqCount, elapsed) + if reqCount < 75*test.incomingLoad/100 { + t.Fatalf("we (arbitrarily) expect at least 75%% of the requests to go through with no rate limiting, and yet only %d/%d went through", reqCount, test.incomingLoad) + } + if dropped != 0 { + t.Fatalf("no request should have been dropped if rate limiting is disabled, and yet %d were", dropped) } return } - // Assume allowed burst is initially consumed in an infinitesimal period of time - var expectedDuration time.Duration - if test.config.Average != 0 { - expectedDuration = time.Duration((int64(test.reqCount)-test.config.Burst+1)/test.config.Average) * time.Second - } - + // Note that even when there is no bursty traffic, + // we take into account the configured burst, + // because it also helps absorbing non-bursty traffic. + wantCount := int(test.config.Average*int64(test.loadDuration/time.Second) + test.config.Burst) // Allow for a 2% leeway - minDuration := expectedDuration * 98 / 100 - maxDuration := expectedDuration * 102 / 100 - - if elapsed < minDuration { - t.Fatalf("rate was faster than expected: %d requests in %v", reqCount, elapsed) - } - if elapsed > maxDuration { + maxCount := wantCount * 102 / 100 + // With very high CPU loads, + // we can expect some extra delay in addition to the rate limiting we already do, + // so we allow for some extra leeway there. + // Feel free to adjust wrt to the load on e.g. the CI. + minCount := wantCount * 95 / 100 + if reqCount < minCount { t.Fatalf("rate was slower than expected: %d requests in %v", reqCount, elapsed) } + if reqCount > maxCount { + t.Fatalf("rate was faster than expected: %d requests in %v", reqCount, elapsed) + } }) } } diff --git a/pkg/middlewares/stripprefix/strip_prefix.go b/pkg/middlewares/stripprefix/strip_prefix.go index 42a113206..31bc5ae90 100644 --- a/pkg/middlewares/stripprefix/strip_prefix.go +++ b/pkg/middlewares/stripprefix/strip_prefix.go @@ -49,7 +49,7 @@ func (s *stripPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) { return } } - http.NotFound(rw, req) + s.next.ServeHTTP(rw, req) } func (s *stripPrefix) serveRequest(rw http.ResponseWriter, req *http.Request, prefix string) { diff --git a/pkg/middlewares/stripprefix/strip_prefix_test.go b/pkg/middlewares/stripprefix/strip_prefix_test.go index 31d417162..449720902 100644 --- a/pkg/middlewares/stripprefix/strip_prefix_test.go +++ b/pkg/middlewares/stripprefix/strip_prefix_test.go @@ -28,7 +28,8 @@ func TestStripPrefix(t *testing.T) { Prefixes: []string{}, }, path: "/noprefixes", - expectedStatusCode: http.StatusNotFound, + expectedStatusCode: http.StatusOK, + expectedPath: "/noprefixes", }, { desc: "wildcard (.*) requests", @@ -76,7 +77,8 @@ func TestStripPrefix(t *testing.T) { Prefixes: []string{"/stat/"}, }, path: "/status", - expectedStatusCode: http.StatusNotFound, + expectedStatusCode: http.StatusOK, + expectedPath: "/status", }, { desc: "general prefix on matching path", @@ -149,6 +151,8 @@ func TestStripPrefix(t *testing.T) { require.NoError(t, err) req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost"+test.path, nil) + req.RequestURI = req.URL.RequestURI() + resp := &httptest.ResponseRecorder{Code: http.StatusOK} handler.ServeHTTP(resp, req) diff --git a/pkg/middlewares/stripprefixregex/strip_prefix_regex.go b/pkg/middlewares/stripprefixregex/strip_prefix_regex.go index c2c98328e..6cc82f178 100644 --- a/pkg/middlewares/stripprefixregex/strip_prefix_regex.go +++ b/pkg/middlewares/stripprefixregex/strip_prefix_regex.go @@ -3,13 +3,13 @@ package stripprefixregex import ( "context" "net/http" + "regexp" "strings" "github.com/containous/traefik/v2/pkg/config/dynamic" "github.com/containous/traefik/v2/pkg/middlewares" "github.com/containous/traefik/v2/pkg/middlewares/stripprefix" "github.com/containous/traefik/v2/pkg/tracing" - "github.com/gorilla/mux" "github.com/opentracing/opentracing-go/ext" ) @@ -19,9 +19,9 @@ const ( // StripPrefixRegex is a middleware used to strip prefix from an URL request. type stripPrefixRegex struct { - next http.Handler - router *mux.Router - name string + next http.Handler + expressions []*regexp.Regexp + name string } // New builds a new StripPrefixRegex middleware. @@ -29,13 +29,16 @@ func New(ctx context.Context, next http.Handler, config dynamic.StripPrefixRegex middlewares.GetLogger(ctx, name, typeName).Debug("Creating middleware") stripPrefix := stripPrefixRegex{ - next: next, - router: mux.NewRouter(), - name: name, + next: next, + name: name, } - for _, prefix := range config.Regex { - stripPrefix.router.PathPrefix(prefix) + for _, exp := range config.Regex { + reg, err := regexp.Compile(strings.TrimSpace(exp)) + if err != nil { + return nil, err + } + stripPrefix.expressions = append(stripPrefix.expressions, reg) } return &stripPrefix, nil @@ -46,32 +49,28 @@ func (s *stripPrefixRegex) GetTracingInformation() (string, ext.SpanKindEnum) { } func (s *stripPrefixRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - var match mux.RouteMatch - if s.router.Match(req, &match) { - params := make([]string, 0, len(match.Vars)*2) - for key, val := range match.Vars { - params = append(params, key) - params = append(params, val) - } + for _, exp := range s.expressions { + parts := exp.FindStringSubmatch(req.URL.Path) + if len(parts) > 0 && len(parts[0]) > 0 { + prefix := parts[0] + if !strings.HasPrefix(req.URL.Path, prefix) { + continue + } - prefix, err := match.Route.URL(params...) - if err != nil || len(prefix.Path) > len(req.URL.Path) { - logger := middlewares.GetLogger(req.Context(), s.name, typeName) - logger.Error("Error in stripPrefix middleware", err) + req.Header.Add(stripprefix.ForwardedPrefixHeader, prefix) + + req.URL.Path = strings.Replace(req.URL.Path, prefix, "", 1) + if req.URL.RawPath != "" { + req.URL.RawPath = req.URL.RawPath[len(prefix):] + } + + req.RequestURI = ensureLeadingSlash(req.URL.RequestURI()) + s.next.ServeHTTP(rw, req) return } - - req.URL.Path = req.URL.Path[len(prefix.Path):] - if req.URL.RawPath != "" { - req.URL.RawPath = req.URL.RawPath[len(prefix.Path):] - } - req.Header.Add(stripprefix.ForwardedPrefixHeader, prefix.Path) - req.RequestURI = ensureLeadingSlash(req.URL.RequestURI()) - - s.next.ServeHTTP(rw, req) - return } - http.NotFound(rw, req) + + s.next.ServeHTTP(rw, req) } func ensureLeadingSlash(str string) string { diff --git a/pkg/middlewares/stripprefixregex/strip_prefix_regex_test.go b/pkg/middlewares/stripprefixregex/strip_prefix_regex_test.go index df67d9f57..297fa7192 100644 --- a/pkg/middlewares/stripprefixregex/strip_prefix_regex_test.go +++ b/pkg/middlewares/stripprefixregex/strip_prefix_regex_test.go @@ -15,7 +15,7 @@ import ( func TestStripPrefixRegex(t *testing.T) { testPrefixRegex := dynamic.StripPrefixRegex{ - Regex: []string{"/a/api/", "/b/{regex}/", "/c/{category}/{id:[0-9]+}/"}, + Regex: []string{"/a/api/", "/b/([a-z0-9]+)/", "/c/[a-z0-9]+/[0-9]+/"}, } testCases := []struct { @@ -27,7 +27,13 @@ func TestStripPrefixRegex(t *testing.T) { }{ { path: "/a/test", - expectedStatusCode: http.StatusNotFound, + expectedStatusCode: http.StatusOK, + expectedPath: "/a/test", + }, + { + path: "/a/test", + expectedStatusCode: http.StatusOK, + expectedPath: "/a/test", }, { path: "/a/api/test", @@ -65,7 +71,8 @@ func TestStripPrefixRegex(t *testing.T) { }, { path: "/c/api/abc/test4", - expectedStatusCode: http.StatusNotFound, + expectedStatusCode: http.StatusOK, + expectedPath: "/c/api/abc/test4", }, { path: "/a/api/a%2Fb", diff --git a/pkg/ping/ping.go b/pkg/ping/ping.go index 44383d8bc..e839aa6c3 100644 --- a/pkg/ping/ping.go +++ b/pkg/ping/ping.go @@ -10,11 +10,13 @@ import ( // Handler expose ping routes. type Handler struct { + EntryPoint string `description:"EntryPoint" export:"true" json:"entryPoint,omitempty" toml:"entryPoint,omitempty" yaml:"entryPoint,omitempty"` terminating bool } // SetDefaults sets the default values. func (h *Handler) SetDefaults() { + h.EntryPoint = "traefik" } // WithContext causes the ping endpoint to serve non 200 responses. diff --git a/pkg/provider/kubernetes/crd/fixtures/with_auth.yml b/pkg/provider/kubernetes/crd/fixtures/with_auth.yml new file mode 100644 index 000000000..b03b82b64 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_auth.yml @@ -0,0 +1,65 @@ +apiVersion: v1 +kind: Secret +metadata: + name: authsecret + namespace: default + +data: + users: |2 + dGVzdDokYXByMSRINnVza2trVyRJZ1hMUDZld1RyU3VCa1RycUU4d2ovCnRlc3QyOiRhcHIxJGQ5 + aHI5SEJCJDRIeHdnVWlyM0hQNEVzZ2dQL1FObzAK +--- +apiVersion: v1 +kind: Secret +metadata: + name: casecret + namespace: default + +data: + ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: v1 +kind: Secret +metadata: + name: tlssecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: basicauth + namespace: default + +spec: + basicAuth: + secret: authsecret + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: digestauth + namespace: default + +spec: + digestAuth: + secret: authsecret +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: forwardauth + namespace: default + +spec: + forwardAuth: + address: test.com + tls: + certSecret: tlssecret + caSecret: casecret diff --git a/pkg/provider/kubernetes/crd/fixtures/with_error_page.yml b/pkg/provider/kubernetes/crd/fixtures/with_error_page.yml new file mode 100644 index 000000000..3eea37acd --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_error_page.yml @@ -0,0 +1,15 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: errorpage + namespace: default + +spec: + errors: + status: + - "404" + - "500" + query: query + service: + name: whoami + port: 80 diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index f3307ab8d..51546914b 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -1,6 +1,8 @@ package crd import ( + "bufio" + "bytes" "context" "crypto/sha256" "fmt" @@ -15,8 +17,10 @@ import ( "github.com/containous/traefik/v2/pkg/config/dynamic" "github.com/containous/traefik/v2/pkg/job" "github.com/containous/traefik/v2/pkg/log" + "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1" "github.com/containous/traefik/v2/pkg/safe" "github.com/containous/traefik/v2/pkg/tls" + "github.com/containous/traefik/v2/pkg/types" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" ) @@ -28,13 +32,14 @@ const ( // Provider holds configurations of the provider. type Provider struct { - Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` - Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty"` - CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"` - DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers." json:"disablePassHostHeaders,omitempty" toml:"disablePassHostHeaders,omitempty" yaml:"disablePassHostHeaders,omitempty" export:"true"` - Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"` - LabelSelector string `description:"Kubernetes label selector to use." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"` - IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"` + Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` + Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty"` + CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"` + DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers." json:"disablePassHostHeaders,omitempty" toml:"disablePassHostHeaders,omitempty" yaml:"disablePassHostHeaders,omitempty" export:"true"` + Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"` + LabelSelector string `description:"Kubernetes label selector to use." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"` + IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"` + ThrottleDuration types.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty"` lastConfiguration safe.Safe } @@ -92,6 +97,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. stopWatch := make(chan struct{}, 1) defer close(stopWatch) eventsChan, err := k8sClient.WatchAll(p.Namespaces, stopWatch) + if err != nil { logger.Errorf("Error watching kubernetes events: %v", err) timer := time.NewTimer(1 * time.Second) @@ -102,11 +108,23 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. return nil } } + + throttleDuration := time.Duration(p.ThrottleDuration) + throttledChan := throttleEvents(ctxLog, throttleDuration, stop, eventsChan) + if throttledChan != nil { + eventsChan = throttledChan + } + for { select { case <-stop: return nil case event := <-eventsChan: + // Note that event is the *first* event that came in during this + // throttling interval -- if we're hitting our throttle, we may have + // dropped events. This is fine, because we don't treat different + // event types differently. But if we do in the future, we'll need to + // track more information about the dropped events. conf := p.loadConfigurationFromCRD(ctxLog, k8sClient) if reflect.DeepEqual(p.lastConfiguration.Get(), conf) { @@ -118,6 +136,11 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. Configuration: conf, } } + + // If we're throttling, we sleep here for the throttle duration to + // enforce that we don't refresh faster than our throttle. time.Sleep + // returns immediately if p.ThrottleDuration is 0 (no throttle). + time.Sleep(throttleDuration) } } } @@ -146,12 +169,280 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) } for _, middleware := range client.GetMiddlewares() { - conf.HTTP.Middlewares[makeID(middleware.Namespace, middleware.Name)] = &middleware.Spec + id := makeID(middleware.Namespace, middleware.Name) + ctxMid := log.With(ctx, log.Str(log.MiddlewareName, id)) + + basicAuth, err := createBasicAuthMiddleware(client, middleware.Namespace, middleware.Spec.BasicAuth) + if err != nil { + log.FromContext(ctxMid).Errorf("Error while reading basic auth middleware: %v", err) + continue + } + + digestAuth, err := createDigestAuthMiddleware(client, middleware.Namespace, middleware.Spec.DigestAuth) + if err != nil { + log.FromContext(ctxMid).Errorf("Error while reading digest auth middleware: %v", err) + continue + } + + forwardAuth, err := createForwardAuthMiddleware(client, middleware.Namespace, middleware.Spec.ForwardAuth) + if err != nil { + log.FromContext(ctxMid).Errorf("Error while reading forward auth middleware: %v", err) + continue + } + + errorPage, errorPageService, err := createErrorPageMiddleware(client, middleware.Namespace, middleware.Spec.Errors) + if err != nil { + log.FromContext(ctxMid).Errorf("Error while reading error page middleware: %v", err) + continue + } + + if errorPage != nil && errorPageService != nil { + serviceName := id + "-errorpage-service" + errorPage.Service = serviceName + conf.HTTP.Services[serviceName] = errorPageService + } + + conf.HTTP.Middlewares[id] = &dynamic.Middleware{ + AddPrefix: middleware.Spec.AddPrefix, + StripPrefix: middleware.Spec.StripPrefix, + StripPrefixRegex: middleware.Spec.StripPrefixRegex, + ReplacePath: middleware.Spec.ReplacePath, + ReplacePathRegex: middleware.Spec.ReplacePathRegex, + Chain: createChainMiddleware(ctxMid, middleware.Namespace, middleware.Spec.Chain), + IPWhiteList: middleware.Spec.IPWhiteList, + Headers: middleware.Spec.Headers, + Errors: errorPage, + RateLimit: middleware.Spec.RateLimit, + RedirectRegex: middleware.Spec.RedirectRegex, + RedirectScheme: middleware.Spec.RedirectScheme, + BasicAuth: basicAuth, + DigestAuth: digestAuth, + ForwardAuth: forwardAuth, + InFlightReq: middleware.Spec.InFlightReq, + Buffering: middleware.Spec.Buffering, + CircuitBreaker: middleware.Spec.CircuitBreaker, + Compress: middleware.Spec.Compress, + PassTLSClientCert: middleware.Spec.PassTLSClientCert, + Retry: middleware.Spec.Retry, + } + } return conf } +func createErrorPageMiddleware(client Client, namespace string, errorPage *v1alpha1.ErrorPage) (*dynamic.ErrorPage, *dynamic.Service, error) { + if errorPage == nil { + return nil, nil, nil + } + + errorPageMiddleware := &dynamic.ErrorPage{ + Status: errorPage.Status, + Query: errorPage.Query, + } + + balancerServerHTTP, err := createLoadBalancerServerHTTP(client, namespace, errorPage.Service) + if err != nil { + return nil, nil, err + } + + return errorPageMiddleware, balancerServerHTTP, nil +} + +func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *v1alpha1.ForwardAuth) (*dynamic.ForwardAuth, error) { + if auth == nil { + return nil, nil + } + if len(auth.Address) == 0 { + return nil, fmt.Errorf("forward authentication requires an address") + } + + forwardAuth := &dynamic.ForwardAuth{ + Address: auth.Address, + TrustForwardHeader: auth.TrustForwardHeader, + AuthResponseHeaders: auth.AuthResponseHeaders, + } + + if auth.TLS == nil { + return forwardAuth, nil + } + + forwardAuth.TLS = &dynamic.ClientTLS{ + CAOptional: auth.TLS.CAOptional, + InsecureSkipVerify: auth.TLS.InsecureSkipVerify, + } + + if len(auth.TLS.CASecret) > 0 { + caSecret, err := loadCASecret(namespace, auth.TLS.CASecret, k8sClient) + if err != nil { + return nil, fmt.Errorf("failed to load auth ca secret: %v", err) + } + forwardAuth.TLS.CA = caSecret + } + + if len(auth.TLS.CertSecret) > 0 { + authSecretCert, authSecretKey, err := loadAuthTLSSecret(namespace, auth.TLS.CertSecret, k8sClient) + if err != nil { + return nil, fmt.Errorf("failed to load auth secret: %s", err) + } + forwardAuth.TLS.Cert = authSecretCert + forwardAuth.TLS.Key = authSecretKey + } + + return forwardAuth, nil +} + +func loadCASecret(namespace, secretName string, k8sClient Client) (string, error) { + secret, ok, err := k8sClient.GetSecret(namespace, secretName) + if err != nil { + return "", fmt.Errorf("failed to fetch secret '%s/%s': %s", namespace, secretName, err) + } + if !ok { + return "", fmt.Errorf("secret '%s/%s' not found", namespace, secretName) + } + if secret == nil { + return "", fmt.Errorf("data for secret '%s/%s' must not be nil", namespace, secretName) + } + if len(secret.Data) != 1 { + return "", fmt.Errorf("found %d elements for secret '%s/%s', must be single element exactly", len(secret.Data), namespace, secretName) + } + + for _, v := range secret.Data { + return string(v), nil + } + return "", nil +} + +func loadAuthTLSSecret(namespace, secretName string, k8sClient Client) (string, string, error) { + secret, exists, err := k8sClient.GetSecret(namespace, secretName) + if err != nil { + return "", "", fmt.Errorf("failed to fetch secret '%s/%s': %s", namespace, secretName, err) + } + if !exists { + return "", "", fmt.Errorf("secret '%s/%s' does not exist", namespace, secretName) + } + if secret == nil { + return "", "", fmt.Errorf("data for secret '%s/%s' must not be nil", namespace, secretName) + } + if len(secret.Data) != 2 { + return "", "", fmt.Errorf("found %d elements for secret '%s/%s', must be two elements exactly", len(secret.Data), namespace, secretName) + } + + return getCertificateBlocks(secret, namespace, secretName) +} + +func createBasicAuthMiddleware(client Client, namespace string, basicAuth *v1alpha1.BasicAuth) (*dynamic.BasicAuth, error) { + if basicAuth == nil { + return nil, nil + } + + credentials, err := getAuthCredentials(client, basicAuth.Secret, namespace) + if err != nil { + return nil, err + } + + return &dynamic.BasicAuth{ + Users: credentials, + Realm: basicAuth.Realm, + RemoveHeader: basicAuth.RemoveHeader, + HeaderField: basicAuth.HeaderField, + }, nil +} + +func createDigestAuthMiddleware(client Client, namespace string, digestAuth *v1alpha1.DigestAuth) (*dynamic.DigestAuth, error) { + if digestAuth == nil { + return nil, nil + } + + credentials, err := getAuthCredentials(client, digestAuth.Secret, namespace) + if err != nil { + return nil, err + } + + return &dynamic.DigestAuth{ + Users: credentials, + Realm: digestAuth.Realm, + RemoveHeader: digestAuth.RemoveHeader, + HeaderField: digestAuth.HeaderField, + }, nil +} + +func getAuthCredentials(k8sClient Client, authSecret, namespace string) ([]string, error) { + if authSecret == "" { + return nil, fmt.Errorf("auth secret must be set") + } + + auth, err := loadAuthCredentials(namespace, authSecret, k8sClient) + if err != nil { + return nil, fmt.Errorf("failed to load auth credentials: %s", err) + } + + return auth, nil +} + +func loadAuthCredentials(namespace, secretName string, k8sClient Client) ([]string, error) { + secret, ok, err := k8sClient.GetSecret(namespace, secretName) + if err != nil { + return nil, fmt.Errorf("failed to fetch secret '%s/%s': %s", namespace, secretName, err) + } + if !ok { + return nil, fmt.Errorf("secret '%s/%s' not found", namespace, secretName) + } + if secret == nil { + return nil, fmt.Errorf("data for secret '%s/%s' must not be nil", namespace, secretName) + } + if len(secret.Data) != 1 { + return nil, fmt.Errorf("found %d elements for secret '%s/%s', must be single element exactly", len(secret.Data), namespace, secretName) + } + + var firstSecret []byte + for _, v := range secret.Data { + firstSecret = v + break + } + + var credentials []string + scanner := bufio.NewScanner(bytes.NewReader(firstSecret)) + for scanner.Scan() { + if cred := scanner.Text(); len(cred) > 0 { + credentials = append(credentials, cred) + } + } + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error reading secret for %v/%v: %v", namespace, secretName, err) + } + if len(credentials) == 0 { + return nil, fmt.Errorf("secret '%s/%s' does not contain any credentials", namespace, secretName) + } + + return credentials, nil +} + +func createChainMiddleware(ctx context.Context, namespace string, chain *v1alpha1.Chain) *dynamic.Chain { + if chain == nil { + return nil + } + + var mds []string + for _, mi := range chain.Middlewares { + if strings.Contains(mi.Name, "@") { + if len(mi.Namespace) > 0 { + log.FromContext(ctx). + Warnf("namespace %q is ignored in cross-provider context", mi.Namespace) + } + mds = append(mds, mi.Name) + continue + } + + ns := mi.Namespace + if len(ns) == 0 { + ns = namespace + } + mds = append(mds, makeID(ns, mi.Name)) + } + return &dynamic.Chain{Middlewares: mds} +} + func buildTLSOptions(ctx context.Context, client Client) map[string]tls.Options { tlsOptionsCRD := client.GetTLSOptions() var tlsOptions map[string]tls.Options @@ -316,3 +607,36 @@ func getCABlocks(secret *corev1.Secret, namespace, secretName string) (string, e return cert, nil } + +func throttleEvents(ctx context.Context, throttleDuration time.Duration, stop chan bool, eventsChan <-chan interface{}) chan interface{} { + if throttleDuration == 0 { + return nil + } + // Create a buffered channel to hold the pending event (if we're delaying processing the event due to throttling) + eventsChanBuffered := make(chan interface{}, 1) + + // Run a goroutine that reads events from eventChan and does a + // non-blocking write to pendingEvent. This guarantees that writing to + // eventChan will never block, and that pendingEvent will have + // something in it if there's been an event since we read from that channel. + go func() { + for { + select { + case <-stop: + return + case nextEvent := <-eventsChan: + select { + case eventsChanBuffered <- nextEvent: + default: + // We already have an event in eventsChanBuffered, so we'll + // do a refresh as soon as our throttle allows us to. It's fine + // to drop the event and keep whatever's in the buffer -- we + // don't do different things for different events + log.FromContext(ctx).Debugf("Dropping event kind %T due to throttling", nextEvent) + } + } + } + }() + + return eventsChanBuffered +} diff --git a/pkg/provider/kubernetes/crd/kubernetes_http.go b/pkg/provider/kubernetes/crd/kubernetes_http.go index 52d18dd30..bbadee6a4 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_http.go +++ b/pkg/provider/kubernetes/crd/kubernetes_http.go @@ -64,7 +64,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli serviceName := makeID(ingressRoute.Namespace, key) for _, service := range route.Services { - balancerServerHTTP, err := createLoadBalancerServerHTTP(client, ingressRoute, service) + balancerServerHTTP, err := createLoadBalancerServerHTTP(client, ingressRoute.Namespace, service) if err != nil { logger. WithField("serviceName", service.Name). @@ -123,6 +123,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli if ingressRoute.Spec.TLS != nil { tlsConf := &dynamic.RouterTLSConfig{ CertResolver: ingressRoute.Spec.TLS.CertResolver, + Domains: ingressRoute.Spec.TLS.Domains, } if ingressRoute.Spec.TLS.Options != nil && len(ingressRoute.Spec.TLS.Options.Name) > 0 { @@ -144,15 +145,14 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli } conf.Routers[serviceName].TLS = tlsConf } - } } return conf } -func createLoadBalancerServerHTTP(client Client, ingressRoute *v1alpha1.IngressRoute, service v1alpha1.Service) (*dynamic.Service, error) { - servers, err := loadServers(client, ingressRoute.Namespace, service) +func createLoadBalancerServerHTTP(client Client, namespace string, service v1alpha1.Service) (*dynamic.Service, error) { + servers, err := loadServers(client, namespace, service) if err != nil { return nil, err } @@ -181,7 +181,7 @@ func loadServers(client Client, namespace string, svc v1alpha1.Service) ([]dynam } if !exists { - return nil, errors.New("service not found") + return nil, fmt.Errorf("service not found %s/%s", namespace, svc.Name) } var portSpec *corev1.ServicePort diff --git a/pkg/provider/kubernetes/crd/kubernetes_tcp.go b/pkg/provider/kubernetes/crd/kubernetes_tcp.go index ea64d89ca..33744a95b 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_tcp.go +++ b/pkg/provider/kubernetes/crd/kubernetes_tcp.go @@ -80,6 +80,7 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client conf.Routers[serviceName].TLS = &dynamic.RouterTCPTLSConfig{ Passthrough: ingressRouteTCP.Spec.TLS.Passthrough, CertResolver: ingressRouteTCP.Spec.TLS.CertResolver, + Domains: ingressRouteTCP.Spec.TLS.Domains, } if ingressRouteTCP.Spec.TLS.Options != nil && len(ingressRouteTCP.Spec.TLS.Options.Name) > 0 { diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index cb9d87408..6dc17b5e0 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -1461,6 +1461,81 @@ func TestLoadIngressRoutes(t *testing.T) { }, }, }, + { + desc: "Simple Ingress Route, with basic auth middleware", + paths: []string{"services.yml", "with_auth.yml"}, + expected: &dynamic.Configuration{ + TLS: &dynamic.TLSConfiguration{}, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{ + "default/basicauth": { + BasicAuth: &dynamic.BasicAuth{ + Users: dynamic.Users{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, + }, + }, + "default/digestauth": { + DigestAuth: &dynamic.DigestAuth{ + Users: dynamic.Users{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, + }, + }, + "default/forwardauth": { + ForwardAuth: &dynamic.ForwardAuth{ + Address: "test.com", + TLS: &dynamic.ClientTLS{ + CA: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----", + Cert: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----", + Key: "-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----", + }, + }, + }, + }, + Services: map[string]*dynamic.Service{}, + }, + }, + }, + { + desc: "Simple Ingress Route, with error page middleware", + paths: []string{"services.yml", "with_error_page.yml"}, + expected: &dynamic.Configuration{ + TLS: &dynamic.TLSConfiguration{}, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{ + "default/errorpage": { + Errors: &dynamic.ErrorPage{ + Status: []string{"404", "500"}, + Service: "default/errorpage-errorpage-service", + Query: "query", + }, + }, + }, + Services: map[string]*dynamic.Service{ + "default/errorpage-errorpage-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: true, + }, + }, + }, + }, + }, + }, { desc: "port selected by name (TODO)", }, @@ -1469,6 +1544,9 @@ func TestLoadIngressRoutes(t *testing.T) { for _, test := range testCases { test := test + if test.desc != "Simple Ingress Route, with error page middleware" { + continue + } t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go index bf6074039..6a1a56782 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go @@ -1,6 +1,7 @@ package v1alpha1 import ( + "github.com/containous/traefik/v2/pkg/types" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -32,8 +33,9 @@ type TLS struct { // certificate details. SecretName string `json:"secretName"` // Options is a reference to a TLSOption, that specifies the parameters of the TLS connection. - Options *TLSOptionRef `json:"options"` - CertResolver string `json:"certResolver"` + Options *TLSOptionRef `json:"options,omitempty"` + CertResolver string `json:"certResolver,omitempty"` + Domains []types.Domain `json:"domains,omitempty"` } // TLSOptionRef is a ref to the TLSOption resources. diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go index c97bb109e..dde55c694 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go @@ -1,6 +1,7 @@ package v1alpha1 import ( + "github.com/containous/traefik/v2/pkg/types" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -32,6 +33,7 @@ type TLSTCP struct { // Options is a reference to a TLSOption, that specifies the parameters of the TLS connection. Options *TLSOptionTCPRef `json:"options"` CertResolver string `json:"certResolver"` + Domains []types.Domain `json:"domains,omitempty"` } // TLSOptionTCPRef is a ref to the TLSOption resources. diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go index c01f12c99..f1e0213d5 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go @@ -13,7 +13,88 @@ type Middleware struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata"` - Spec dynamic.Middleware `json:"spec"` + Spec MiddlewareSpec `json:"spec"` +} + +// +k8s:deepcopy-gen=true + +// MiddlewareSpec holds the Middleware configuration. +type MiddlewareSpec struct { + AddPrefix *dynamic.AddPrefix `json:"addPrefix,omitempty"` + StripPrefix *dynamic.StripPrefix `json:"stripPrefix,omitempty"` + StripPrefixRegex *dynamic.StripPrefixRegex `json:"stripPrefixRegex,omitempty"` + ReplacePath *dynamic.ReplacePath `json:"replacePath,omitempty"` + ReplacePathRegex *dynamic.ReplacePathRegex `json:"replacePathRegex,omitempty"` + Chain *Chain `json:"chain,omitempty"` + IPWhiteList *dynamic.IPWhiteList `json:"ipWhiteList,omitempty"` + Headers *dynamic.Headers `json:"headers,omitempty"` + Errors *ErrorPage `json:"errors,omitempty"` + RateLimit *dynamic.RateLimit `json:"rateLimit,omitempty"` + RedirectRegex *dynamic.RedirectRegex `json:"redirectRegex,omitempty"` + RedirectScheme *dynamic.RedirectScheme `json:"redirectScheme,omitempty"` + BasicAuth *BasicAuth `json:"basicAuth,omitempty"` + DigestAuth *DigestAuth `json:"digestAuth,omitempty"` + ForwardAuth *ForwardAuth `json:"forwardAuth,omitempty"` + InFlightReq *dynamic.InFlightReq `json:"inFlightReq,omitempty"` + Buffering *dynamic.Buffering `json:"buffering,omitempty"` + CircuitBreaker *dynamic.CircuitBreaker `json:"circuitBreaker,omitempty"` + Compress *dynamic.Compress `json:"compress,omitempty"` + PassTLSClientCert *dynamic.PassTLSClientCert `json:"passTLSClientCert,omitempty"` + Retry *dynamic.Retry `json:"retry,omitempty"` +} + +// +k8s:deepcopy-gen=true + +// ErrorPage holds the custom error page configuration. +type ErrorPage struct { + Status []string `json:"status,omitempty"` + Service Service `json:"service,omitempty"` + Query string `json:"query,omitempty"` +} + +// +k8s:deepcopy-gen=true + +// Chain holds a chain of middlewares +type Chain struct { + Middlewares []MiddlewareRef `json:"middlewares,omitempty"` +} + +// +k8s:deepcopy-gen=true + +// BasicAuth holds the HTTP basic authentication configuration. +type BasicAuth struct { + Secret string `json:"secret,omitempty"` + Realm string `json:"realm,omitempty"` + RemoveHeader bool `json:"removeHeader,omitempty"` + HeaderField string `json:"headerField,omitempty"` +} + +// +k8s:deepcopy-gen=true + +// DigestAuth holds the Digest HTTP authentication configuration. +type DigestAuth struct { + Secret string `json:"secret,omitempty"` + RemoveHeader bool `json:"removeHeader,omitempty"` + Realm string `json:"realm,omitempty"` + HeaderField string `json:"headerField,omitempty"` +} + +// +k8s:deepcopy-gen=true + +// ForwardAuth holds the http forward authentication configuration. +type ForwardAuth struct { + Address string `json:"address,omitempty"` + TrustForwardHeader bool `json:"trustForwardHeader,omitempty"` + AuthResponseHeaders []string `json:"authResponseHeaders,omitempty"` + TLS *ClientTLS `json:"tls,omitempty"` +} + +// ClientTLS holds TLS specific configurations as client. +type ClientTLS struct { + CASecret string `json:"caSecret,omitempty"` + CAOptional bool `json:"caOptional,omitempty"` + CertSecret string `json:"certSecret,omitempty"` + InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go index 047f0fcaa..813b8a6b5 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go @@ -29,9 +29,48 @@ THE SOFTWARE. package v1alpha1 import ( + dynamic "github.com/containous/traefik/v2/pkg/config/dynamic" + types "github.com/containous/traefik/v2/pkg/types" runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BasicAuth) DeepCopyInto(out *BasicAuth) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BasicAuth. +func (in *BasicAuth) DeepCopy() *BasicAuth { + if in == nil { + return nil + } + out := new(BasicAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Chain) DeepCopyInto(out *Chain) { + *out = *in + if in.Middlewares != nil { + in, out := &in.Middlewares, &out.Middlewares + *out = make([]MiddlewareRef, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Chain. +func (in *Chain) DeepCopy() *Chain { + if in == nil { + return nil + } + out := new(Chain) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClientAuth) DeepCopyInto(out *ClientAuth) { *out = *in @@ -53,6 +92,86 @@ func (in *ClientAuth) DeepCopy() *ClientAuth { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClientTLS) DeepCopyInto(out *ClientTLS) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientTLS. +func (in *ClientTLS) DeepCopy() *ClientTLS { + if in == nil { + return nil + } + out := new(ClientTLS) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DigestAuth) DeepCopyInto(out *DigestAuth) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DigestAuth. +func (in *DigestAuth) DeepCopy() *DigestAuth { + if in == nil { + return nil + } + out := new(DigestAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ErrorPage) DeepCopyInto(out *ErrorPage) { + *out = *in + if in.Status != nil { + in, out := &in.Status, &out.Status + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.Service.DeepCopyInto(&out.Service) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ErrorPage. +func (in *ErrorPage) DeepCopy() *ErrorPage { + if in == nil { + return nil + } + out := new(ErrorPage) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ForwardAuth) DeepCopyInto(out *ForwardAuth) { + *out = *in + if in.AuthResponseHeaders != nil { + in, out := &in.AuthResponseHeaders, &out.AuthResponseHeaders + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(ClientTLS) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForwardAuth. +func (in *ForwardAuth) DeepCopy() *ForwardAuth { + if in == nil { + return nil + } + out := new(ForwardAuth) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HealthCheck) DeepCopyInto(out *HealthCheck) { *out = *in @@ -338,6 +457,127 @@ func (in *MiddlewareRef) DeepCopy() *MiddlewareRef { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) { + *out = *in + if in.AddPrefix != nil { + in, out := &in.AddPrefix, &out.AddPrefix + *out = new(dynamic.AddPrefix) + **out = **in + } + if in.StripPrefix != nil { + in, out := &in.StripPrefix, &out.StripPrefix + *out = new(dynamic.StripPrefix) + (*in).DeepCopyInto(*out) + } + if in.StripPrefixRegex != nil { + in, out := &in.StripPrefixRegex, &out.StripPrefixRegex + *out = new(dynamic.StripPrefixRegex) + (*in).DeepCopyInto(*out) + } + if in.ReplacePath != nil { + in, out := &in.ReplacePath, &out.ReplacePath + *out = new(dynamic.ReplacePath) + **out = **in + } + if in.ReplacePathRegex != nil { + in, out := &in.ReplacePathRegex, &out.ReplacePathRegex + *out = new(dynamic.ReplacePathRegex) + **out = **in + } + if in.Chain != nil { + in, out := &in.Chain, &out.Chain + *out = new(Chain) + (*in).DeepCopyInto(*out) + } + if in.IPWhiteList != nil { + in, out := &in.IPWhiteList, &out.IPWhiteList + *out = new(dynamic.IPWhiteList) + (*in).DeepCopyInto(*out) + } + if in.Headers != nil { + in, out := &in.Headers, &out.Headers + *out = new(dynamic.Headers) + (*in).DeepCopyInto(*out) + } + if in.Errors != nil { + in, out := &in.Errors, &out.Errors + *out = new(ErrorPage) + (*in).DeepCopyInto(*out) + } + if in.RateLimit != nil { + in, out := &in.RateLimit, &out.RateLimit + *out = new(dynamic.RateLimit) + (*in).DeepCopyInto(*out) + } + if in.RedirectRegex != nil { + in, out := &in.RedirectRegex, &out.RedirectRegex + *out = new(dynamic.RedirectRegex) + **out = **in + } + if in.RedirectScheme != nil { + in, out := &in.RedirectScheme, &out.RedirectScheme + *out = new(dynamic.RedirectScheme) + **out = **in + } + if in.BasicAuth != nil { + in, out := &in.BasicAuth, &out.BasicAuth + *out = new(BasicAuth) + **out = **in + } + if in.DigestAuth != nil { + in, out := &in.DigestAuth, &out.DigestAuth + *out = new(DigestAuth) + **out = **in + } + if in.ForwardAuth != nil { + in, out := &in.ForwardAuth, &out.ForwardAuth + *out = new(ForwardAuth) + (*in).DeepCopyInto(*out) + } + if in.InFlightReq != nil { + in, out := &in.InFlightReq, &out.InFlightReq + *out = new(dynamic.InFlightReq) + (*in).DeepCopyInto(*out) + } + if in.Buffering != nil { + in, out := &in.Buffering, &out.Buffering + *out = new(dynamic.Buffering) + **out = **in + } + if in.CircuitBreaker != nil { + in, out := &in.CircuitBreaker, &out.CircuitBreaker + *out = new(dynamic.CircuitBreaker) + **out = **in + } + if in.Compress != nil { + in, out := &in.Compress, &out.Compress + *out = new(dynamic.Compress) + **out = **in + } + if in.PassTLSClientCert != nil { + in, out := &in.PassTLSClientCert, &out.PassTLSClientCert + *out = new(dynamic.PassTLSClientCert) + (*in).DeepCopyInto(*out) + } + if in.Retry != nil { + in, out := &in.Retry, &out.Retry + *out = new(dynamic.Retry) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MiddlewareSpec. +func (in *MiddlewareSpec) DeepCopy() *MiddlewareSpec { + if in == nil { + return nil + } + out := new(MiddlewareSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Route) DeepCopyInto(out *Route) { *out = *in @@ -437,6 +677,13 @@ func (in *TLS) DeepCopyInto(out *TLS) { *out = new(TLSOptionRef) **out = **in } + if in.Domains != nil { + in, out := &in.Domains, &out.Domains + *out = make([]types.Domain, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -572,6 +819,13 @@ func (in *TLSTCP) DeepCopyInto(out *TLSTCP) { *out = new(TLSOptionTCPRef) **out = **in } + if in.Domains != nil { + in, out := &in.Domains, &out.Domains + *out = make([]types.Domain, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } diff --git a/pkg/provider/kubernetes/ingress/kubernetes.go b/pkg/provider/kubernetes/ingress/kubernetes.go index 8f17bf174..7a2d87a0b 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes.go +++ b/pkg/provider/kubernetes/ingress/kubernetes.go @@ -18,6 +18,7 @@ import ( "github.com/containous/traefik/v2/pkg/log" "github.com/containous/traefik/v2/pkg/safe" "github.com/containous/traefik/v2/pkg/tls" + "github.com/containous/traefik/v2/pkg/types" corev1 "k8s.io/api/core/v1" "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/labels" @@ -39,6 +40,7 @@ type Provider struct { LabelSelector string `description:"Kubernetes Ingress label selector to use." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"` IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"` IngressEndpoint *EndpointIngress `description:"Kubernetes Ingress Endpoint." json:"ingressEndpoint,omitempty" toml:"ingressEndpoint,omitempty" yaml:"ingressEndpoint,omitempty"` + ThrottleDuration types.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty"` lastConfiguration safe.Safe } @@ -118,11 +120,22 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. } } + throttleDuration := time.Duration(p.ThrottleDuration) + throttledChan := throttleEvents(ctxLog, throttleDuration, stop, eventsChan) + if throttledChan != nil { + eventsChan = throttledChan + } + for { select { case <-stop: return nil case event := <-eventsChan: + // Note that event is the *first* event that came in during this + // throttling interval -- if we're hitting our throttle, we may have + // dropped events. This is fine, because we don't treat different + // event types differently. But if we do in the future, we'll need to + // track more information about the dropped events. conf := p.loadConfigurationFromIngresses(ctxLog, k8sClient) if reflect.DeepEqual(p.lastConfiguration.Get(), conf) { @@ -134,6 +147,11 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. Configuration: conf, } } + + // If we're throttling, we sleep here for the throttle duration to + // enforce that we don't refresh faster than our throttle. time.Sleep + // returns immediately if p.ThrottleDuration is 0 (no throttle). + time.Sleep(throttleDuration) } } } @@ -478,3 +496,36 @@ func (p *Provider) updateIngressStatus(i *v1beta1.Ingress, k8sClient Client) err return k8sClient.UpdateIngressStatus(i.Namespace, i.Name, service.Status.LoadBalancer.Ingress[0].IP, service.Status.LoadBalancer.Ingress[0].Hostname) } + +func throttleEvents(ctx context.Context, throttleDuration time.Duration, stop chan bool, eventsChan <-chan interface{}) chan interface{} { + if throttleDuration == 0 { + return nil + } + // Create a buffered channel to hold the pending event (if we're delaying processing the event due to throttling) + eventsChanBuffered := make(chan interface{}, 1) + + // Run a goroutine that reads events from eventChan and does a + // non-blocking write to pendingEvent. This guarantees that writing to + // eventChan will never block, and that pendingEvent will have + // something in it if there's been an event since we read from that channel. + go func() { + for { + select { + case <-stop: + return + case nextEvent := <-eventsChan: + select { + case eventsChanBuffered <- nextEvent: + default: + // We already have an event in eventsChanBuffered, so we'll + // do a refresh as soon as our throttle allows us to. It's fine + // to drop the event and keep whatever's in the buffer -- we + // don't do different things for different events + log.FromContext(ctx).Debugf("Dropping event kind %T due to throttling", nextEvent) + } + } + } + }() + + return eventsChanBuffered +} diff --git a/pkg/provider/rest/rest.go b/pkg/provider/rest/rest.go index 8c3c082a6..ed62466de 100644 --- a/pkg/provider/rest/rest.go +++ b/pkg/provider/rest/rest.go @@ -18,6 +18,7 @@ var _ provider.Provider = (*Provider)(nil) // Provider is a provider.Provider implementation that provides a Rest API. type Provider struct { + Insecure bool `description:"Activate REST Provider directly on the entryPoint named traefik." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"` configurationChan chan<- dynamic.Message } @@ -32,6 +33,13 @@ func (p *Provider) Init() error { return nil } +// Handler creates an http.Handler for the Rest API +func (p *Provider) Handler() http.Handler { + router := mux.NewRouter() + p.Append(router) + return router +} + // Append add rest provider routes on a router. func (p *Provider) Append(systemRouter *mux.Router) { systemRouter. diff --git a/pkg/responsemodifiers/response_modifier.go b/pkg/responsemodifiers/response_modifier.go index 507627d69..97c83e895 100644 --- a/pkg/responsemodifiers/response_modifier.go +++ b/pkg/responsemodifiers/response_modifier.go @@ -23,6 +23,11 @@ func (f *Builder) Build(ctx context.Context, names []string) func(*http.Response for _, middleName := range names { if conf, ok := f.configs[middleName]; ok { + if conf == nil || conf.Middleware == nil { + getLogger(ctx, middleName, "undefined").Error("Invalid Middleware configuration (ResponseModifier)") + continue + } + if conf.Headers != nil { getLogger(ctx, middleName, "Headers").Debug("Creating Middleware (ResponseModifier)") diff --git a/pkg/responsemodifiers/response_modifier_test.go b/pkg/responsemodifiers/response_modifier_test.go index 79c1f1132..9b3d7806b 100644 --- a/pkg/responsemodifiers/response_modifier_test.go +++ b/pkg/responsemodifiers/response_modifier_test.go @@ -47,7 +47,7 @@ func TestBuilderBuild(t *testing.T) { assertResponse: func(t *testing.T, resp *http.Response) { t.Helper() - assert.Equal(t, resp.Header.Get("X-Foo"), "foo") + assert.Equal(t, "foo", resp.Header.Get("X-Foo")) }, }, { @@ -85,7 +85,7 @@ func TestBuilderBuild(t *testing.T) { assertResponse: func(t *testing.T, resp *http.Response) { t.Helper() - assert.Equal(t, resp.Header.Get("Referrer-Policy"), "no-referrer") + assert.Equal(t, "no-referrer", resp.Header.Get("Referrer-Policy")) }, }, { @@ -107,8 +107,8 @@ func TestBuilderBuild(t *testing.T) { assertResponse: func(t *testing.T, resp *http.Response) { t.Helper() - assert.Equal(t, resp.Header.Get("X-Foo"), "foo") - assert.Equal(t, resp.Header.Get("X-Bar"), "bar") + assert.Equal(t, "foo", resp.Header.Get("X-Foo")) + assert.Equal(t, "bar", resp.Header.Get("X-Bar")) }, }, { @@ -130,7 +130,7 @@ func TestBuilderBuild(t *testing.T) { assertResponse: func(t *testing.T, resp *http.Response) { t.Helper() - assert.Equal(t, resp.Header.Get("X-Foo"), "foo") + assert.Equal(t, "foo", resp.Header.Get("X-Foo")) }, }, { @@ -157,9 +157,18 @@ func TestBuilderBuild(t *testing.T) { assertResponse: func(t *testing.T, resp *http.Response) { t.Helper() - assert.Equal(t, resp.Header.Get("X-Foo"), "foo") + assert.Equal(t, "foo", resp.Header.Get("X-Foo")) }, }, + { + desc: "nil middleware", + middlewares: []string{"foo"}, + buildResponse: stubResponse, + conf: map[string]*dynamic.Middleware{ + "foo": nil, + }, + assertResponse: func(t *testing.T, resp *http.Response) {}, + }, } for _, test := range testCases { diff --git a/pkg/server/middleware/middlewares.go b/pkg/server/middleware/middlewares.go index 4fd747780..e13bee412 100644 --- a/pkg/server/middleware/middlewares.go +++ b/pkg/server/middleware/middlewares.go @@ -102,6 +102,10 @@ func checkRecursion(ctx context.Context, middlewareName string) (context.Context // it is the responsibility of the caller to make sure that b.configs[middlewareName].Middleware exists func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (alice.Constructor, error) { config := b.configs[middlewareName] + if config == nil || config.Middleware == nil { + return nil, fmt.Errorf("invalid middleware %q configuration", middlewareName) + } + var middleware alice.Constructor badConf := errors.New("cannot create middleware: multi-types middleware not supported, consider declaring two different pieces of middleware instead") @@ -123,7 +127,7 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) ( } // Buffering - if config.Buffering != nil && config.InFlightReq.Amount != 0 { + if config.Buffering != nil { if middleware != nil { return nil, badConf } @@ -137,6 +141,12 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) ( if middleware != nil { return nil, badConf } + + qualifiedNames := make([]string, len(config.Chain.Middlewares)) + for i, name := range config.Chain.Middlewares { + qualifiedNames[i] = internal.GetQualifiedName(ctx, name) + } + config.Chain.Middlewares = qualifiedNames middleware = func(next http.Handler) (http.Handler, error) { return chain.New(ctx, next, *config.Chain, b, middlewareName) } @@ -213,7 +223,7 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) ( } // InFlightReq - if config.InFlightReq != nil && config.InFlightReq.Amount != 0 { + if config.InFlightReq != nil { if middleware != nil { return nil, badConf } @@ -314,7 +324,7 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) ( } if middleware == nil { - return nil, errors.New("middleware does not exist") + return nil, fmt.Errorf("middleware %q does not exist", middlewareName) } return tracing.Wrap(ctx, middleware), nil diff --git a/pkg/server/middleware/middlewares_test.go b/pkg/server/middleware/middlewares_test.go index a3d855c44..52a89484b 100644 --- a/pkg/server/middleware/middlewares_test.go +++ b/pkg/server/middleware/middlewares_test.go @@ -314,6 +314,14 @@ func TestBuilder_buildConstructor(t *testing.T) { Prefix: "foo/", }, }, + "buff-foo": { + Buffering: &dynamic.Buffering{ + MaxRequestBodyBytes: 1, + MemRequestBodyBytes: 2, + MaxResponseBodyBytes: 3, + MemResponseBodyBytes: 5, + }, + }, } rtConf := runtime.NewConfig(dynamic.Configuration{ @@ -338,6 +346,11 @@ func TestBuilder_buildConstructor(t *testing.T) { middlewareID: "cb-foo", expectedError: false, }, + { + desc: "Should create a buffering middleware", + middlewareID: "buff-foo", + expectedError: false, + }, { desc: "Should not create an empty AddPrefix middleware when given an empty prefix", middlewareID: "ap-empty", @@ -356,7 +369,6 @@ func TestBuilder_buildConstructor(t *testing.T) { t.Parallel() constructor, err := middlewaresBuilder.buildConstructor(context.Background(), test.middlewareID) - require.NoError(t, err) middleware, err2 := constructor(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})) diff --git a/pkg/server/router/route_appender_aggregator.go b/pkg/server/router/route_appender_aggregator.go index 71702a6d6..c6f50545d 100644 --- a/pkg/server/router/route_appender_aggregator.go +++ b/pkg/server/router/route_appender_aggregator.go @@ -13,36 +13,31 @@ import ( "github.com/gorilla/mux" ) -// chainBuilder The contract of the middleware builder -type chainBuilder interface { - BuildChain(ctx context.Context, middlewares []string) *alice.Chain -} - // NewRouteAppenderAggregator Creates a new RouteAppenderAggregator -func NewRouteAppenderAggregator(ctx context.Context, chainBuilder chainBuilder, conf static.Configuration, +func NewRouteAppenderAggregator(ctx context.Context, conf static.Configuration, entryPointName string, runtimeConfiguration *runtime.Configuration) *RouteAppenderAggregator { aggregator := &RouteAppenderAggregator{} + if conf.Ping != nil && conf.Ping.EntryPoint == entryPointName { + aggregator.AddAppender(conf.Ping) + } + + if conf.Metrics != nil && conf.Metrics.Prometheus != nil && conf.Metrics.Prometheus.EntryPoint == entryPointName { + aggregator.AddAppender(metrics.PrometheusHandler{}) + } + if entryPointName != "traefik" { return aggregator } - if conf.Providers != nil && conf.Providers.Rest != nil { + if conf.Providers != nil && conf.Providers.Rest != nil && conf.Providers.Rest.Insecure { aggregator.AddAppender(conf.Providers.Rest) } - if conf.API != nil { + if conf.API != nil && conf.API.Insecure { aggregator.AddAppender(api.New(conf, runtimeConfiguration)) } - if conf.Ping != nil { - aggregator.AddAppender(conf.Ping) - } - - if conf.Metrics != nil && conf.Metrics.Prometheus != nil { - aggregator.AddAppender(metrics.PrometheusHandler{}) - } - return aggregator } diff --git a/pkg/server/router/route_appender_aggregator_test.go b/pkg/server/router/route_appender_aggregator_test.go index ffc9c9d6f..bfa27ae58 100644 --- a/pkg/server/router/route_appender_aggregator_test.go +++ b/pkg/server/router/route_appender_aggregator_test.go @@ -6,72 +6,23 @@ import ( "net/http/httptest" "testing" - "github.com/containous/alice" "github.com/containous/traefik/v2/pkg/config/static" - "github.com/containous/traefik/v2/pkg/ping" "github.com/gorilla/mux" "github.com/stretchr/testify/assert" ) -type ChainBuilderMock struct { - middles map[string]alice.Constructor -} - -func (c *ChainBuilderMock) BuildChain(ctx context.Context, middles []string) *alice.Chain { - chain := alice.New() - - for _, mName := range middles { - if constructor, ok := c.middles[mName]; ok { - chain = chain.Append(constructor) - } - } - - return &chain -} - func TestNewRouteAppenderAggregator(t *testing.T) { - t.Skip("Waiting for new api handler implementation") testCases := []struct { desc string staticConf static.Configuration - middles map[string]alice.Constructor expected map[string]int }{ { - desc: "API with auth, ping without auth", + desc: "Secure API", staticConf: static.Configuration{ Global: &static.Global{}, - API: &static.API{ - // EntryPoint: "traefik", - // Middlewares: []string{"dumb"}, - }, - Ping: &ping.Handler{ - // EntryPoint: "traefik", - }, - EntryPoints: static.EntryPoints{ - "traefik": {}, - }, - }, - middles: map[string]alice.Constructor{ - "dumb": func(_ http.Handler) (http.Handler, error) { - return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusUnauthorized) - }), nil - }, - }, - expected: map[string]int{ - "/wrong": http.StatusBadGateway, - "/ping": http.StatusOK, - // "/.well-known/acme-challenge/token": http.StatusNotFound, // FIXME - "/api/rawdata": http.StatusUnauthorized, - }, - }, - { - desc: "Wrong entrypoint name", - staticConf: static.Configuration{ - Global: &static.Global{}, - API: &static.API{ - // EntryPoint: "no", + API: &static.API{ + Insecure: false, }, EntryPoints: static.EntryPoints{ "traefik": {}, @@ -81,6 +32,21 @@ func TestNewRouteAppenderAggregator(t *testing.T) { "/api/providers": http.StatusBadGateway, }, }, + { + desc: "Insecure API", + staticConf: static.Configuration{ + Global: &static.Global{}, + API: &static.API{ + Insecure: true, + }, + EntryPoints: static.EntryPoints{ + "traefik": {}, + }, + }, + expected: map[string]int{ + "/api/rawdata": http.StatusOK, + }, + }, } for _, test := range testCases { @@ -88,11 +54,9 @@ func TestNewRouteAppenderAggregator(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - chainBuilder := &ChainBuilderMock{middles: test.middles} - ctx := context.Background() - router := NewRouteAppenderAggregator(ctx, chainBuilder, test.staticConf, "traefik", nil) + router := NewRouteAppenderAggregator(ctx, test.staticConf, "traefik", nil) internalMuxRouter := mux.NewRouter() router.Append(internalMuxRouter) diff --git a/pkg/server/router/route_appender_factory.go b/pkg/server/router/route_appender_factory.go index 7831e3b34..21be5d4c9 100644 --- a/pkg/server/router/route_appender_factory.go +++ b/pkg/server/router/route_appender_factory.go @@ -6,7 +6,6 @@ import ( "github.com/containous/traefik/v2/pkg/config/runtime" "github.com/containous/traefik/v2/pkg/config/static" "github.com/containous/traefik/v2/pkg/provider/acme" - "github.com/containous/traefik/v2/pkg/server/middleware" "github.com/containous/traefik/v2/pkg/types" ) @@ -27,8 +26,8 @@ type RouteAppenderFactory struct { } // NewAppender Creates a new RouteAppender -func (r *RouteAppenderFactory) NewAppender(ctx context.Context, middlewaresBuilder *middleware.Builder, runtimeConfiguration *runtime.Configuration) types.RouteAppender { - aggregator := NewRouteAppenderAggregator(ctx, middlewaresBuilder, r.staticConfiguration, r.entryPointName, runtimeConfiguration) +func (r *RouteAppenderFactory) NewAppender(ctx context.Context, runtimeConfiguration *runtime.Configuration) types.RouteAppender { + aggregator := NewRouteAppenderAggregator(ctx, r.staticConfiguration, r.entryPointName, runtimeConfiguration) for _, p := range r.acmeProvider { if p != nil && p.HTTPChallenge != nil && p.HTTPChallenge.EntryPoint == r.entryPointName { diff --git a/pkg/server/router/router.go b/pkg/server/router/router.go index 9b08cff95..c27d42229 100644 --- a/pkg/server/router/router.go +++ b/pkg/server/router/router.go @@ -147,6 +147,7 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn for i, name := range router.Middlewares { qualifiedNames[i] = internal.GetQualifiedName(ctx, name) } + router.Middlewares = qualifiedNames rm := m.modifierBuilder.Build(ctx, qualifiedNames) if router.Service == "" { diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go index 1ff4c43f4..36ee65a6e 100644 --- a/pkg/server/router/router_test.go +++ b/pkg/server/router/router_test.go @@ -306,7 +306,7 @@ func TestRouterManager_Get(t *testing.T) { Middlewares: test.middlewaresConfig, }, }) - serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil) + serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil, nil, nil) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager) responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares) routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory) @@ -407,7 +407,7 @@ func TestAccessLog(t *testing.T) { Middlewares: test.middlewaresConfig, }, }) - serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil) + serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil, nil, nil) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager) responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares) routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory) @@ -693,7 +693,7 @@ func TestRuntimeConfiguration(t *testing.T) { Middlewares: test.middlewareConfig, }, }) - serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil) + serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil, nil, nil) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager) responseModifierFactory := responsemodifiers.NewBuilder(map[string]*runtime.MiddlewareInfo{}) routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory) @@ -725,6 +725,57 @@ func TestRuntimeConfiguration(t *testing.T) { } +func TestProviderOnMiddlewares(t *testing.T) { + entryPoints := []string{"web"} + + rtConf := runtime.NewConfig(dynamic.Configuration{ + HTTP: &dynamic.HTTPConfiguration{ + Services: map[string]*dynamic.Service{ + "test@file": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{}, + }, + }, + }, + Routers: map[string]*dynamic.Router{ + "router@file": { + Rule: "Host(`test`)", + Service: "test@file", + Middlewares: []string{"chain@file", "m1"}, + }, + "router@docker": { + Rule: "Host(`test`)", + Service: "test@file", + Middlewares: []string{"chain", "m1@file"}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{ + "chain@file": { + Chain: &dynamic.Chain{Middlewares: []string{"m1", "m2", "m1@file"}}, + }, + "chain@docker": { + Chain: &dynamic.Chain{Middlewares: []string{"m1", "m2", "m1@file"}}, + }, + "m1@file": {AddPrefix: &dynamic.AddPrefix{Prefix: "/m1"}}, + "m2@file": {AddPrefix: &dynamic.AddPrefix{Prefix: "/m2"}}, + "m1@docker": {AddPrefix: &dynamic.AddPrefix{Prefix: "/m1"}}, + "m2@docker": {AddPrefix: &dynamic.AddPrefix{Prefix: "/m2"}}, + }, + }, + }) + serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil, nil, nil) + middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager) + responseModifierFactory := responsemodifiers.NewBuilder(map[string]*runtime.MiddlewareInfo{}) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory) + + _ = routerManager.BuildHandlers(context.Background(), entryPoints, false) + + assert.Equal(t, []string{"chain@file", "m1@file"}, rtConf.Routers["router@file"].Middlewares) + assert.Equal(t, []string{"m1@file", "m2@file", "m1@file"}, rtConf.Middlewares["chain@file"].Chain.Middlewares) + assert.Equal(t, []string{"chain@docker", "m1@file"}, rtConf.Routers["router@docker"].Middlewares) + assert.Equal(t, []string{"m1@docker", "m2@docker", "m1@file"}, rtConf.Middlewares["chain@docker"].Chain.Middlewares) +} + type staticTransport struct { res *http.Response } @@ -767,7 +818,7 @@ func BenchmarkRouterServe(b *testing.B) { Middlewares: map[string]*dynamic.Middleware{}, }, }) - serviceManager := service.NewManager(rtConf.Services, &staticTransport{res}, nil, nil) + serviceManager := service.NewManager(rtConf.Services, &staticTransport{res}, nil, nil, nil, nil) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager) responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares) routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory) @@ -808,7 +859,7 @@ func BenchmarkService(b *testing.B) { Services: serviceConfig, }, }) - serviceManager := service.NewManager(rtConf.Services, &staticTransport{res}, nil, nil) + serviceManager := service.NewManager(rtConf.Services, &staticTransport{res}, nil, nil, nil, nil) w := httptest.NewRecorder() req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil) diff --git a/pkg/server/router/tcp/router.go b/pkg/server/router/tcp/router.go index bf4931695..55b42ba9d 100644 --- a/pkg/server/router/tcp/router.go +++ b/pkg/server/router/tcp/router.go @@ -177,6 +177,13 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string continue } + if routerConfig.Rule == "" { + err := errors.New("router has no rule") + routerConfig.AddError(err, true) + logger.Error(err) + continue + } + handler, err := m.serviceManager.BuildTCP(ctxRouter, routerConfig.Service) if err != nil { routerConfig.AddError(err, true) @@ -188,7 +195,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string if err != nil { routerErr := fmt.Errorf("unknown rule %s", routerConfig.Rule) routerConfig.AddError(routerErr, true) - logger.Debug(routerErr) + logger.Error(routerErr) continue } diff --git a/pkg/server/server.go b/pkg/server/server.go index 59dca31b3..7a67e46f9 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/containous/traefik/v2/pkg/api" "github.com/containous/traefik/v2/pkg/config/dynamic" "github.com/containous/traefik/v2/pkg/config/runtime" "github.com/containous/traefik/v2/pkg/config/static" @@ -18,7 +19,6 @@ import ( "github.com/containous/traefik/v2/pkg/middlewares/requestdecorator" "github.com/containous/traefik/v2/pkg/provider" "github.com/containous/traefik/v2/pkg/safe" - "github.com/containous/traefik/v2/pkg/server/middleware" "github.com/containous/traefik/v2/pkg/tls" "github.com/containous/traefik/v2/pkg/tracing" "github.com/containous/traefik/v2/pkg/tracing/jaeger" @@ -44,11 +44,13 @@ type Server struct { requestDecorator *requestdecorator.RequestDecorator providersThrottleDuration time.Duration tlsManager *tls.Manager + api func(configuration *runtime.Configuration) http.Handler + restHandler http.Handler } // RouteAppenderFactory the route appender factory interface type RouteAppenderFactory interface { - NewAppender(ctx context.Context, middlewaresBuilder *middleware.Builder, runtimeConfiguration *runtime.Configuration) types.RouteAppender + NewAppender(ctx context.Context, runtimeConfiguration *runtime.Configuration) types.RouteAppender } func setupTracing(conf *static.Tracing) tracing.Backend { @@ -66,11 +68,11 @@ func setupTracing(conf *static.Tracing) tracing.Backend { } } - if conf.DataDog != nil { + if conf.Datadog != nil { if backend != nil { - log.WithoutContext().Error("Multiple tracing backend are not supported: cannot create DataDog backend.") + log.WithoutContext().Error("Multiple tracing backend are not supported: cannot create Datadog backend.") } else { - backend = conf.DataDog + backend = conf.Datadog } } @@ -103,6 +105,14 @@ func setupTracing(conf *static.Tracing) tracing.Backend { func NewServer(staticConfiguration static.Configuration, provider provider.Provider, entryPoints TCPEntryPoints, tlsManager *tls.Manager) *Server { server := &Server{} + if staticConfiguration.API != nil { + server.api = api.NewBuilder(staticConfiguration) + } + + if staticConfiguration.Providers != nil && staticConfiguration.Providers.Rest != nil { + server.restHandler = staticConfiguration.Providers.Rest.Handler() + } + server.provider = provider server.entryPointsTCP = entryPoints server.configurationChan = make(chan dynamic.Message, 100) @@ -307,11 +317,11 @@ func registerMetricClients(metricsConfig *types.Metrics) metrics.Registry { } } - if metricsConfig.DataDog != nil { + if metricsConfig.Datadog != nil { ctx := log.With(context.Background(), log.Str(log.MetricsProviderName, "datadog")) - registries = append(registries, metrics.RegisterDatadog(ctx, metricsConfig.DataDog)) - log.FromContext(ctx).Debugf("Configured DataDog metrics: pushing to %s once every %s", - metricsConfig.DataDog.Address, metricsConfig.DataDog.PushInterval) + registries = append(registries, metrics.RegisterDatadog(ctx, metricsConfig.Datadog)) + log.FromContext(ctx).Debugf("Configured Datadog metrics: pushing to %s once every %s", + metricsConfig.Datadog.Address, metricsConfig.Datadog.PushInterval) } if metricsConfig.StatsD != nil { diff --git a/pkg/server/server_configuration.go b/pkg/server/server_configuration.go index cc9acc707..c4b703504 100644 --- a/pkg/server/server_configuration.go +++ b/pkg/server/server_configuration.go @@ -97,7 +97,12 @@ func (s *Server) createTCPRouters(ctx context.Context, configuration *runtime.Co // createHTTPHandlers returns, for the given configuration and entryPoints, the HTTP handlers for non-TLS connections, and for the TLS ones. the given configuration must not be nil. its fields will get mutated. func (s *Server) createHTTPHandlers(ctx context.Context, configuration *runtime.Configuration, entryPoints []string) (map[string]http.Handler, map[string]http.Handler) { - serviceManager := service.NewManager(configuration.Services, s.defaultRoundTripper, s.metricsRegistry, s.routinesPool) + var apiHandler http.Handler + if s.api != nil { + apiHandler = s.api(configuration) + } + + serviceManager := service.NewManager(configuration.Services, s.defaultRoundTripper, s.metricsRegistry, s.routinesPool, apiHandler, s.restHandler) middlewaresBuilder := middleware.NewBuilder(configuration.Middlewares, serviceManager) responseModifierFactory := responsemodifiers.NewBuilder(configuration.Middlewares) routerManager := router.NewManager(configuration, serviceManager, middlewaresBuilder, responseModifierFactory) @@ -114,7 +119,7 @@ func (s *Server) createHTTPHandlers(ctx context.Context, configuration *runtime. factory := s.entryPointsTCP[entryPointName].RouteAppenderFactory if factory != nil { // FIXME remove currentConfigurations - appender := factory.NewAppender(ctx, middlewaresBuilder, configuration) + appender := factory.NewAppender(ctx, configuration) appender.Append(internalMuxRouter) } diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index 17dbc5c81..c3c4c43b8 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -3,6 +3,7 @@ package server import ( "context" "fmt" + stdlog "log" "net" "net/http" "sync" @@ -16,10 +17,13 @@ import ( "github.com/containous/traefik/v2/pkg/middlewares/forwardedheaders" "github.com/containous/traefik/v2/pkg/safe" "github.com/containous/traefik/v2/pkg/tcp" + "github.com/sirupsen/logrus" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" ) +var httpServerLogger = stdlog.New(log.WithoutContext().WriterLevel(logrus.DebugLevel), "", 0) + type httpForwarder struct { net.Listener connChan chan net.Conn @@ -352,7 +356,8 @@ func createHTTPServer(ln net.Listener, configuration *static.EntryPoint, withH2c } serverHTTP := &http.Server{ - Handler: handler, + Handler: handler, + ErrorLog: httpServerLogger, } listener := newHTTPForwarder(ln) diff --git a/pkg/server/service/loadbalancer/mirror/mirror.go b/pkg/server/service/loadbalancer/mirror/mirror.go index 593f20858..f20b630ca 100644 --- a/pkg/server/service/loadbalancer/mirror/mirror.go +++ b/pkg/server/service/loadbalancer/mirror/mirror.go @@ -1,8 +1,10 @@ package mirror import ( + "bufio" "context" "errors" + "net" "net/http" "sync" @@ -61,7 +63,7 @@ func (m *Mirroring) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if handler.count*100 < total*uint64(handler.percent) { handler.count++ handler.lock.Unlock() - // When a request served by m.handler is successful, req.Context will be cancelled, + // When a request served by m.handler is successful, req.Context will be canceled, // which would trigger a cancellation of the ongoing mirrored requests. // Therefore, we give a new, non-cancellable context to each of the mirrored calls, // so they can terminate by themselves. @@ -75,7 +77,7 @@ func (m *Mirroring) ServeHTTP(rw http.ResponseWriter, req *http.Request) { // AddMirror adds an httpHandler to mirror to. func (m *Mirroring) AddMirror(handler http.Handler, percent int) error { - if percent < 0 || percent >= 100 { + if percent < 0 || percent > 100 { return errors.New("percent must be between 0 and 100") } m.mirrorHandlers = append(m.mirrorHandlers, &mirrorHandler{Handler: handler, percent: percent}) @@ -84,6 +86,12 @@ func (m *Mirroring) AddMirror(handler http.Handler, percent int) error { type blackholeResponseWriter struct{} +func (b blackholeResponseWriter) Flush() {} + +func (b blackholeResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return nil, nil, errors.New("you can hijack connection on blackholeResponseWriter") +} + func (b blackholeResponseWriter) Header() http.Header { return http.Header{} } @@ -92,8 +100,7 @@ func (b blackholeResponseWriter) Write(bytes []byte) (int, error) { return len(bytes), nil } -func (b blackholeResponseWriter) WriteHeader(statusCode int) { -} +func (b blackholeResponseWriter) WriteHeader(statusCode int) {} type contextStopPropagation struct { context.Context diff --git a/pkg/server/service/loadbalancer/mirror/mirror_test.go b/pkg/server/service/loadbalancer/mirror/mirror_test.go index fb2c516c5..f223d72fd 100644 --- a/pkg/server/service/loadbalancer/mirror/mirror_test.go +++ b/pkg/server/service/loadbalancer/mirror/mirror_test.go @@ -76,4 +76,58 @@ func TestInvalidPercent(t *testing.T) { err = mirror.AddMirror(nil, 101) assert.Error(t, err) + + err = mirror.AddMirror(nil, 100) + assert.NoError(t, err) + + err = mirror.AddMirror(nil, 0) + assert.NoError(t, err) +} + +func TestHijack(t *testing.T) { + handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusOK) + }) + pool := safe.NewPool(context.Background()) + mirror := New(handler, pool) + + var mirrorRequest bool + err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + hijacker, ok := rw.(http.Hijacker) + assert.Equal(t, true, ok) + + _, _, err := hijacker.Hijack() + assert.Error(t, err) + mirrorRequest = true + }), 100) + assert.NoError(t, err) + + mirror.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", nil)) + + pool.Stop() + assert.Equal(t, true, mirrorRequest) +} + +func TestFlush(t *testing.T) { + handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusOK) + }) + pool := safe.NewPool(context.Background()) + mirror := New(handler, pool) + + var mirrorRequest bool + err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + hijacker, ok := rw.(http.Flusher) + assert.Equal(t, true, ok) + + hijacker.Flush() + + mirrorRequest = true + }), 100) + assert.NoError(t, err) + + mirror.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", nil)) + + pool.Stop() + assert.Equal(t, true, mirrorRequest) } diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go index 1595d0452..1181b2584 100644 --- a/pkg/server/service/service.go +++ b/pkg/server/service/service.go @@ -34,7 +34,7 @@ const ( ) // NewManager creates a new Manager -func NewManager(configs map[string]*runtime.ServiceInfo, defaultRoundTripper http.RoundTripper, metricsRegistry metrics.Registry, routinePool *safe.Pool) *Manager { +func NewManager(configs map[string]*runtime.ServiceInfo, defaultRoundTripper http.RoundTripper, metricsRegistry metrics.Registry, routinePool *safe.Pool, api http.Handler, rest http.Handler) *Manager { return &Manager{ routinePool: routinePool, metricsRegistry: metricsRegistry, @@ -42,6 +42,8 @@ func NewManager(configs map[string]*runtime.ServiceInfo, defaultRoundTripper htt defaultRoundTripper: defaultRoundTripper, balancers: make(map[string][]healthcheck.BalancerHandler), configs: configs, + api: api, + rest: rest, } } @@ -53,10 +55,26 @@ type Manager struct { defaultRoundTripper http.RoundTripper balancers map[string][]healthcheck.BalancerHandler configs map[string]*runtime.ServiceInfo + api http.Handler + rest http.Handler } // BuildHTTP Creates a http.Handler for a service configuration. func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error) { + if serviceName == "api@internal" { + if m.api == nil { + return nil, errors.New("api is not enabled") + } + return m.api, nil + } + + if serviceName == "rest@internal" { + if m.rest == nil { + return nil, errors.New("rest is not enabled") + } + return m.rest, nil + } + ctx := log.With(rootCtx, log.Str(log.ServiceName, serviceName)) serviceName = internal.GetQualifiedName(ctx, serviceName) diff --git a/pkg/server/service/service_test.go b/pkg/server/service/service_test.go index c2bb707b7..9fb0edd09 100644 --- a/pkg/server/service/service_test.go +++ b/pkg/server/service/service_test.go @@ -80,7 +80,7 @@ func TestGetLoadBalancer(t *testing.T) { } func TestGetLoadBalancerServiceHandler(t *testing.T) { - sm := NewManager(nil, http.DefaultTransport, nil, nil) + sm := NewManager(nil, http.DefaultTransport, nil, nil, nil, nil) server1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-From", "first") @@ -332,7 +332,7 @@ func TestManager_Build(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - manager := NewManager(test.configs, http.DefaultTransport, nil, nil) + manager := NewManager(test.configs, http.DefaultTransport, nil, nil, nil, nil) ctx := context.Background() if len(test.providerName) > 0 { @@ -353,7 +353,7 @@ func TestMultipleTypeOnBuildHTTP(t *testing.T) { Weighted: &dynamic.WeightedRoundRobin{}, }, }, - }, http.DefaultTransport, nil, nil) + }, http.DefaultTransport, nil, nil, nil, nil) _, err := manager.BuildHTTP(context.Background(), "test@file", nil) assert.Error(t, err, "cannot create service: multi-types service not supported, consider declaring two different pieces of service instead") diff --git a/pkg/tracing/datadog/datadog.go b/pkg/tracing/datadog/datadog.go index d297fe916..71589a92a 100644 --- a/pkg/tracing/datadog/datadog.go +++ b/pkg/tracing/datadog/datadog.go @@ -17,7 +17,7 @@ const Name = "datadog" type Config struct { LocalAgentHostPort string `description:"Set datadog-agent's host:port that the reporter will used." json:"localAgentHostPort,omitempty" toml:"localAgentHostPort,omitempty" yaml:"localAgentHostPort,omitempty"` GlobalTag string `description:"Key:Value tag to be set on all the spans." json:"globalTag,omitempty" toml:"globalTag,omitempty" yaml:"globalTag,omitempty" export:"true"` - Debug bool `description:"Enable DataDog debug." json:"debug,omitempty" toml:"debug,omitempty" yaml:"debug,omitempty" export:"true"` + Debug bool `description:"Enable Datadog debug." json:"debug,omitempty" toml:"debug,omitempty" yaml:"debug,omitempty" export:"true"` PrioritySampling bool `description:"Enable priority sampling. When using distributed tracing, this option must be enabled in order to get all the parts of a distributed trace sampled." json:"prioritySampling,omitempty" toml:"prioritySampling,omitempty" yaml:"prioritySampling,omitempty"` TraceIDHeaderName string `description:"Specifies the header name that will be used to store the trace ID." json:"traceIDHeaderName,omitempty" toml:"traceIDHeaderName,omitempty" yaml:"traceIDHeaderName,omitempty" export:"true"` ParentIDHeaderName string `description:"Specifies the header name that will be used to store the parent ID." json:"parentIDHeaderName,omitempty" toml:"parentIDHeaderName,omitempty" yaml:"parentIDHeaderName,omitempty" export:"true"` @@ -62,7 +62,7 @@ func (c *Config) Setup(serviceName string) (opentracing.Tracer, io.Closer, error // Without this, child spans are getting the NOOP tracer opentracing.SetGlobalTracer(tracer) - log.WithoutContext().Debug("DataDog tracer configured") + log.WithoutContext().Debug("Datadog tracer configured") return tracer, nil, nil } diff --git a/pkg/tracing/haystack/haystack.go b/pkg/tracing/haystack/haystack.go index aa48375de..47b6266e6 100644 --- a/pkg/tracing/haystack/haystack.go +++ b/pkg/tracing/haystack/haystack.go @@ -63,7 +63,7 @@ func (c *Config) Setup(serviceName string) (opentracing.Tracer, io.Closer, error // Without this, child spans are getting the NOOP tracer opentracing.SetGlobalTracer(tracer) - log.WithoutContext().Debug("DataDog tracer configured") + log.WithoutContext().Debug("haystack tracer configured") return tracer, closer, nil } diff --git a/pkg/tracing/zipkin/zipkin.go b/pkg/tracing/zipkin/zipkin.go index 52885ca4a..7a6548fb3 100644 --- a/pkg/tracing/zipkin/zipkin.go +++ b/pkg/tracing/zipkin/zipkin.go @@ -6,7 +6,9 @@ import ( "github.com/containous/traefik/v2/pkg/log" "github.com/opentracing/opentracing-go" - zipkin "github.com/openzipkin-contrib/zipkin-go-opentracing" + zipkinot "github.com/openzipkin-contrib/zipkin-go-opentracing" + "github.com/openzipkin/zipkin-go" + "github.com/openzipkin/zipkin-go/reporter/http" ) // Name sets the name of this tracer. @@ -17,43 +19,53 @@ type Config struct { HTTPEndpoint string `description:"HTTP Endpoint to report traces to." json:"httpEndpoint,omitempty" toml:"httpEndpoint,omitempty" yaml:"httpEndpoint,omitempty"` SameSpan bool `description:"Use Zipkin SameSpan RPC style traces." json:"sameSpan,omitempty" toml:"sameSpan,omitempty" yaml:"sameSpan,omitempty" export:"true"` ID128Bit bool `description:"Use Zipkin 128 bit root span IDs." json:"id128Bit,omitempty" toml:"id128Bit,omitempty" yaml:"id128Bit,omitempty" export:"true"` - Debug bool `description:"Enable Zipkin debug." json:"debug,omitempty" toml:"debug,omitempty" yaml:"debug,omitempty" export:"true"` SampleRate float64 `description:"The rate between 0.0 and 1.0 of requests to trace." json:"sampleRate,omitempty" toml:"sampleRate,omitempty" yaml:"sampleRate,omitempty" export:"true"` } // SetDefaults sets the default values. func (c *Config) SetDefaults() { - c.HTTPEndpoint = "http://localhost:9411/api/v1/spans" + c.HTTPEndpoint = "http://localhost:9411/api/v2/spans" c.SameSpan = false c.ID128Bit = true - c.Debug = false c.SampleRate = 1.0 } // Setup sets up the tracer func (c *Config) Setup(serviceName string) (opentracing.Tracer, io.Closer, error) { - collector, err := zipkin.NewHTTPCollector(c.HTTPEndpoint) + // create our local endpoint + endpoint, err := zipkin.NewEndpoint(serviceName, "0.0.0.0:0") if err != nil { return nil, nil, err } - recorder := zipkin.NewRecorder(collector, c.Debug, "0.0.0.0:0", serviceName) + // create our sampler + sampler, err := zipkin.NewBoundarySampler(c.SampleRate, time.Now().Unix()) + if err != nil { + return nil, nil, err + } - tracer, err := zipkin.NewTracer( - recorder, - zipkin.ClientServerSameSpan(c.SameSpan), - zipkin.TraceID128Bit(c.ID128Bit), - zipkin.DebugMode(c.Debug), - zipkin.WithSampler(zipkin.NewBoundarySampler(c.SampleRate, time.Now().Unix())), + // create the span reporter + reporter := http.NewReporter(c.HTTPEndpoint) + + // create the native Zipkin tracer + nativeTracer, err := zipkin.NewTracer( + reporter, + zipkin.WithLocalEndpoint(endpoint), + zipkin.WithSharedSpans(c.SameSpan), + zipkin.WithTraceID128Bit(c.ID128Bit), + zipkin.WithSampler(sampler), ) if err != nil { return nil, nil, err } + // wrap the Zipkin native tracer with the OpenTracing Bridge + tracer := zipkinot.Wrap(nativeTracer) + // Without this, child spans are getting the NOOP tracer opentracing.SetGlobalTracer(tracer) log.WithoutContext().Debug("Zipkin tracer configured") - return tracer, collector, nil + return tracer, reporter, nil } diff --git a/pkg/types/metrics.go b/pkg/types/metrics.go index f05d1a77c..4fcebf550 100644 --- a/pkg/types/metrics.go +++ b/pkg/types/metrics.go @@ -7,7 +7,7 @@ import ( // Metrics provides options to expose and send Traefik metrics to different third party monitoring systems. type Metrics struct { Prometheus *Prometheus `description:"Prometheus metrics exporter type." json:"prometheus,omitempty" toml:"prometheus,omitempty" yaml:"prometheus,omitempty" export:"true" label:"allowEmpty"` - DataDog *DataDog `description:"DataDog metrics exporter type." json:"dataDog,omitempty" toml:"dataDog,omitempty" yaml:"dataDog,omitempty" export:"true" label:"allowEmpty"` + Datadog *Datadog `description:"Datadog metrics exporter type." json:"datadog,omitempty" toml:"datadog,omitempty" yaml:"datadog,omitempty" export:"true" label:"allowEmpty"` StatsD *Statsd `description:"StatsD metrics exporter type." json:"statsD,omitempty" toml:"statsD,omitempty" yaml:"statsD,omitempty" export:"true" label:"allowEmpty"` InfluxDB *InfluxDB `description:"InfluxDB metrics exporter type." json:"influxDB,omitempty" toml:"influxDB,omitempty" yaml:"influxDB,omitempty" label:"allowEmpty"` } @@ -17,6 +17,7 @@ type Prometheus struct { Buckets []float64 `description:"Buckets for latency metrics." json:"buckets,omitempty" toml:"buckets,omitempty" yaml:"buckets,omitempty" export:"true"` AddEntryPointsLabels bool `description:"Enable metrics on entry points." json:"addEntryPointsLabels,omitempty" toml:"addEntryPointsLabels,omitempty" yaml:"addEntryPointsLabels,omitempty" export:"true"` AddServicesLabels bool `description:"Enable metrics on services." json:"addServicesLabels,omitempty" toml:"addServicesLabels,omitempty" yaml:"addServicesLabels,omitempty" export:"true"` + EntryPoint string `description:"EntryPoint" export:"true" json:"entryPoint,omitempty" toml:"entryPoint,omitempty" yaml:"entryPoint,omitempty"` } // SetDefaults sets the default values. @@ -24,18 +25,19 @@ func (p *Prometheus) SetDefaults() { p.Buckets = []float64{0.1, 0.3, 1.2, 5} p.AddEntryPointsLabels = true p.AddServicesLabels = true + p.EntryPoint = "traefik" } -// DataDog contains address and metrics pushing interval configuration. -type DataDog struct { - Address string `description:"DataDog's address." json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"` - PushInterval Duration `description:"DataDog push interval." json:"pushInterval,omitempty" toml:"pushInterval,omitempty" yaml:"pushInterval,omitempty" export:"true"` +// Datadog contains address and metrics pushing interval configuration. +type Datadog struct { + Address string `description:"Datadog's address." json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"` + PushInterval Duration `description:"Datadog push interval." json:"pushInterval,omitempty" toml:"pushInterval,omitempty" yaml:"pushInterval,omitempty" export:"true"` AddEntryPointsLabels bool `description:"Enable metrics on entry points." json:"addEntryPointsLabels,omitempty" toml:"addEntryPointsLabels,omitempty" yaml:"addEntryPointsLabels,omitempty" export:"true"` AddServicesLabels bool `description:"Enable metrics on services." json:"addServicesLabels,omitempty" toml:"addServicesLabels,omitempty" yaml:"addServicesLabels,omitempty" export:"true"` } // SetDefaults sets the default values. -func (d *DataDog) SetDefaults() { +func (d *Datadog) SetDefaults() { d.Address = "localhost:8125" d.PushInterval = Duration(10 * time.Second) d.AddEntryPointsLabels = true diff --git a/pkg/version/version.go b/pkg/version/version.go index 478c4f132..c208f650e 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -6,7 +6,7 @@ import ( "net/url" "github.com/containous/traefik/v2/pkg/log" - "github.com/google/go-github/v27/github" + "github.com/google/go-github/v28/github" "github.com/gorilla/mux" goversion "github.com/hashicorp/go-version" "github.com/unrolled/render" @@ -54,7 +54,7 @@ func CheckNewVersion() { return } client := github.NewClient(nil) - updateURL, err := url.Parse("https://update.traefik.io") + updateURL, err := url.Parse("https://update.traefik.io/") if err != nil { log.Warnf("Error checking new version: %s", err) return diff --git a/webui/package-lock.json b/webui/package-lock.json index ce0c1d3aa..881323f68 100644 --- a/webui/package-lock.json +++ b/webui/package-lock.json @@ -1,6 +1,6 @@ { "name": "traefik-ui", - "version": "0.1.0", + "version": "2.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1465,6 +1465,7 @@ "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, "requires": { "mime-types": "~2.1.24", "negotiator": "0.6.2" @@ -1619,7 +1620,8 @@ "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true }, "array-includes": { "version": "3.0.3", @@ -2091,6 +2093,7 @@ "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dev": true, "requires": { "bytes": "3.1.0", "content-type": "~1.0.4", @@ -2108,6 +2111,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "requires": { "ms": "2.0.0" } @@ -2115,7 +2119,8 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -2297,7 +2302,8 @@ "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true }, "cacache": { "version": "11.3.3", @@ -2929,7 +2935,8 @@ "connect-history-api-fallback": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==" + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true }, "console-browserify": { "version": "1.1.0", @@ -2971,6 +2978,7 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, "requires": { "safe-buffer": "5.1.2" } @@ -2978,7 +2986,8 @@ "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true }, "convert-source-map": { "version": "1.6.0", @@ -2992,12 +3001,14 @@ "cookie": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "dev": true }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true }, "copy-concurrently": { "version": "1.0.5", @@ -3575,7 +3586,8 @@ "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true }, "des.js": { "version": "1.0.0", @@ -3590,7 +3602,8 @@ "destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true }, "detect-node": { "version": "2.0.4", @@ -3751,7 +3764,8 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true }, "ejs": { "version": "2.6.2", @@ -3812,7 +3826,8 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true }, "end-of-stream": { "version": "1.4.1", @@ -3895,7 +3910,8 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true }, "escape-string-regexp": { "version": "1.0.5", @@ -4296,7 +4312,8 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true }, "eventemitter3": { "version": "3.1.2", @@ -4398,6 +4415,7 @@ "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dev": true, "requires": { "accepts": "~1.3.7", "array-flatten": "1.1.1", @@ -4435,6 +4453,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "requires": { "ms": "2.0.0" } @@ -4442,7 +4461,8 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -4681,6 +4701,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -4695,6 +4716,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "requires": { "ms": "2.0.0" } @@ -4702,7 +4724,8 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -4844,7 +4867,8 @@ "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true }, "fragment-cache": { "version": "0.2.1", @@ -4858,7 +4882,8 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true }, "friendly-errors-webpack-plugin": { "version": "1.7.0", @@ -5515,6 +5540,7 @@ "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dev": true, "requires": { "depd": "~1.1.2", "inherits": "2.0.3", @@ -5526,7 +5552,8 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true } } }, @@ -5700,6 +5727,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -5873,7 +5901,8 @@ "ipaddr.js": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", - "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==", + "dev": true }, "is-absolute-url": { "version": "2.1.0", @@ -6704,7 +6733,8 @@ "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true }, "mem": { "version": "4.3.0", @@ -6846,7 +6876,8 @@ "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true }, "merge-source-map": { "version": "1.1.0", @@ -6874,7 +6905,8 @@ "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true }, "micromatch": { "version": "4.0.2", @@ -6899,17 +6931,20 @@ "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true }, "mime-db": { "version": "1.40.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true }, "mime-types": { "version": "2.1.24", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dev": true, "requires": { "mime-db": "1.40.0" } @@ -7128,7 +7163,8 @@ "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true }, "neo-async": { "version": "2.6.1", @@ -7540,6 +7576,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, "requires": { "ee-first": "1.1.1" } @@ -7803,7 +7840,8 @@ "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true }, "pascalcase": { "version": "0.1.1", @@ -7856,7 +7894,8 @@ "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true }, "path-type": { "version": "3.0.0", @@ -8719,6 +8758,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "dev": true, "requires": { "forwarded": "~0.1.2", "ipaddr.js": "1.9.0" @@ -8804,7 +8844,8 @@ "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true }, "quasar": { "version": "1.0.5", @@ -8861,12 +8902,14 @@ "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true }, "raw-body": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dev": true, "requires": { "bytes": "3.1.0", "http-errors": "1.7.2", @@ -9382,7 +9425,8 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true }, "safe-regex": { "version": "1.1.0", @@ -9396,7 +9440,8 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "sass-graph": { "version": "2.2.4", @@ -9713,6 +9758,7 @@ "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dev": true, "requires": { "debug": "2.6.9", "depd": "~1.1.2", @@ -9733,6 +9779,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "requires": { "ms": "2.0.0" }, @@ -9740,14 +9787,16 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true } } }, @@ -9817,6 +9866,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dev": true, "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -9862,7 +9912,8 @@ "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true }, "sha.js": { "version": "2.4.11", @@ -10321,7 +10372,8 @@ "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true }, "stdout-stream": { "version": "1.4.1", @@ -10792,7 +10844,8 @@ "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true }, "toposort": { "version": "1.0.7", @@ -10885,6 +10938,7 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, "requires": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -10993,7 +11047,8 @@ "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true }, "unquote": { "version": "1.1.1", @@ -11163,7 +11218,8 @@ "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true }, "uuid": { "version": "3.3.2", @@ -11184,7 +11240,8 @@ "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true }, "vendors": { "version": "1.0.3", diff --git a/webui/package.json b/webui/package.json index 7553c88db..544f6748c 100644 --- a/webui/package.json +++ b/webui/package.json @@ -20,8 +20,6 @@ "axios": "^0.19.0", "bowser": "^2.5.2", "chart.js": "^2.8.0", - "connect-history-api-fallback": "^1.6.0", - "express": "^4.17.1", "lodash": "^4.17.15", "moment": "^2.24.0", "quasar": "^1.0.0", diff --git a/webui/quasar.conf.js b/webui/quasar.conf.js index 7fb3c3cfd..109fa6130 100644 --- a/webui/quasar.conf.js +++ b/webui/quasar.conf.js @@ -85,7 +85,8 @@ module.exports = function (ctx) { 'QDialog', 'QUploader', 'QTree', - 'QChip' + 'QChip', + 'QBtnToggle' ], directives: [ diff --git a/webui/src/_middleware/Boot.js b/webui/src/_middleware/Boot.js index 8f0a5349f..9fccf11df 100644 --- a/webui/src/_middleware/Boot.js +++ b/webui/src/_middleware/Boot.js @@ -1,4 +1,5 @@ import { APP } from '../_helpers/APP' +import Helps from '../_helpers/Helps' const Boot = { install (Vue, options) { @@ -13,11 +14,23 @@ const Boot = { }, env () { return APP.config.env + }, + appThumbStyle () { + return { + right: '2px', + borderRadius: '2px', + backgroundColor: '#dcdcdc', + width: '6px', + opacity: 0.75 + } } }, methods: { }, filters: { + capFirstLetter (value) { + return Helps.capFirstLetter(value) + } }, created () { } diff --git a/webui/src/_services/EntrypointsService.js b/webui/src/_services/EntrypointsService.js index 71d564759..8dfaffaed 100644 --- a/webui/src/_services/EntrypointsService.js +++ b/webui/src/_services/EntrypointsService.js @@ -10,6 +10,15 @@ function getAll () { }) } -export default { - getAll +function getByName (name) { + return APP.api.get(`${apiBase}/${name}`) + .then(body => { + console.log('Success -> EntrypointsService -> getByName', body.data) + return body.data + }) +} + +export default { + getAll, + getByName } diff --git a/webui/src/_services/HttpService.js b/webui/src/_services/HttpService.js new file mode 100644 index 000000000..19deca0b6 --- /dev/null +++ b/webui/src/_services/HttpService.js @@ -0,0 +1,75 @@ +import { APP } from '../_helpers/APP' + +const apiBase = '/http' + +function getAllRouters (params) { + return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}`) + .then(body => { + const total = body.data ? body.data.length : 0 + return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`) + .then(body => { + console.log('Success -> HttpService -> getAllRouters', body.data) + // TODO - suggestion: add the total-pages in api response to optimize the query + return { data: body.data || [], total } + }) + }) +} + +function getRouterByName (name) { + return APP.api.get(`${apiBase}/routers/${name}`) + .then(body => { + console.log('Success -> HttpService -> getRouterByName', body.data) + return body.data + }) +} + +function getAllServices (params) { + return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}`) + .then(body => { + const total = body.data ? body.data.length : 0 + return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`) + .then(body => { + console.log('Success -> HttpService -> getAllServices', body.data) + // TODO - suggestion: add the total-pages in api response to optimize the query + return { data: body.data || [], total } + }) + }) +} + +function getServiceByName (name) { + return APP.api.get(`${apiBase}/services/${name}`) + .then(body => { + console.log('Success -> HttpService -> getServiceByName', body.data) + return body.data + }) +} + +function getAllMiddlewares (params) { + return APP.api.get(`${apiBase}/middlewares?search=${params.query}&status=${params.status}`) + .then(body => { + const total = body.data ? body.data.length : 0 + return APP.api.get(`${apiBase}/middlewares?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`) + .then(body => { + console.log('Success -> HttpService -> getAllMiddlewares', body.data) + // TODO - suggestion: add the total-pages in api response to optimize the query + return { data: body.data || [], total } + }) + }) +} + +function getMiddlewareByName (name) { + return APP.api.get(`${apiBase}/middlewares/${name}`) + .then(body => { + console.log('Success -> HttpService -> getMiddlewareByName', body.data) + return body.data + }) +} + +export default { + getAllRouters, + getRouterByName, + getAllServices, + getServiceByName, + getAllMiddlewares, + getMiddlewareByName +} diff --git a/webui/src/_services/TcpService.js b/webui/src/_services/TcpService.js new file mode 100644 index 000000000..08f1b6131 --- /dev/null +++ b/webui/src/_services/TcpService.js @@ -0,0 +1,52 @@ +import { APP } from '../_helpers/APP' + +const apiBase = '/tcp' + +function getAllRouters (params) { + return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}`) + .then(body => { + const total = body.data ? body.data.length : 0 + return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`) + .then(body => { + console.log('Success -> HttpService -> getAllRouters', body.data) + // TODO - suggestion: add the total-pages in api response to optimize the query + return { data: body.data || [], total } + }) + }) +} + +function getRouterByName (name) { + return APP.api.get(`${apiBase}/routers/${name}`) + .then(body => { + console.log('Success -> HttpService -> getRouterByName', body.data) + return body.data + }) +} + +function getAllServices (params) { + return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}`) + .then(body => { + const total = body.data ? body.data.length : 0 + return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`) + .then(body => { + console.log('Success -> HttpService -> getAllServices', body.data) + // TODO - suggestion: add the total-pages in api response to optimize the query + return { data: body.data || [], total } + }) + }) +} + +function getServiceByName (name) { + return APP.api.get(`${apiBase}/services/${name}`) + .then(body => { + console.log('Success -> HttpService -> getServiceByName', body.data) + return body.data + }) +} + +export default { + getAllRouters, + getRouterByName, + getAllServices, + getServiceByName +} diff --git a/webui/src/assets/middlewares-empty.svg b/webui/src/assets/middlewares-empty.svg new file mode 100644 index 000000000..172ad3225 --- /dev/null +++ b/webui/src/assets/middlewares-empty.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/webui/src/components/_commons/BooleanState.vue b/webui/src/components/_commons/BooleanState.vue new file mode 100644 index 000000000..7091b099c --- /dev/null +++ b/webui/src/components/_commons/BooleanState.vue @@ -0,0 +1,53 @@ + + + + + + + {{value ? 'True' : 'False'}} + + + + + + diff --git a/webui/src/components/_commons/MainTable.vue b/webui/src/components/_commons/MainTable.vue new file mode 100644 index 000000000..40ac29e40 --- /dev/null +++ b/webui/src/components/_commons/MainTable.vue @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + + {{ props.row.rule }} + + + + + + {{ entryPoints }} + + + + + + {{ props.row.name }} + + + + + {{ props.row.type }} + + + + {{ props.row | servers }} + + + + {{ props.row.service }} + + + + + + + + + + + + + + + + + diff --git a/webui/src/components/_commons/NavBar.vue b/webui/src/components/_commons/NavBar.vue index 2e1e92d1e..0818f6135 100644 --- a/webui/src/components/_commons/NavBar.vue +++ b/webui/src/components/_commons/NavBar.vue @@ -8,6 +8,8 @@ + + diff --git a/webui/src/components/_commons/PanelHealthCheck.vue b/webui/src/components/_commons/PanelHealthCheck.vue new file mode 100644 index 000000000..7b385d820 --- /dev/null +++ b/webui/src/components/_commons/PanelHealthCheck.vue @@ -0,0 +1,125 @@ + + + + + + + SCHEME + + {{ data.scheme }} + + + + INTERVAL + + {{ data.interval }} + + + + + + + + PATH + + {{ data.path }} + + + + TIMEOUT + + {{ data.timeout }} + + + + + + + + PORT + + {{ data.port }} + + + + HOSTNAME + + {{ data.hostname }} + + + + + + + + HEADERS + + + + {{ index }}: {{ header }} + + + + + + + + + + + diff --git a/webui/src/components/_commons/PanelMiddlewares.vue b/webui/src/components/_commons/PanelMiddlewares.vue new file mode 100644 index 000000000..a961e2871 --- /dev/null +++ b/webui/src/components/_commons/PanelMiddlewares.vue @@ -0,0 +1,1204 @@ + + + + + + {{ middleware.name }} + + + + + + Type + + {{ middleware.type }} + + + + PROVIDER + + + + + {{middleware.provider}} + + + + + + + + STATUS + + + {{middleware.status | statusLabel}} + + + + + + + + + ERRORS + + {{ errorMsg }} + + + + + + + + + + PREFIX + + {{ exData(middleware).prefix }} + + + + + + + + + + USERS + + {{ user }} + + + + + + + + + Users File + + {{ exData(middleware).usersFile }} + + + + + + + + + Realm + + {{ exData(middleware).realm }} + + + + + + + + + Remove Header + + + + + + + + + Header Field + + {{ exData(middleware).headerField }} + + + + + + + + + + Chain + + {{ mi }} + + + + + + + + + + Max Request Body Bytes + + {{ exData(middleware).maxRequestBodyBytes }} + + + + Mem Request Body Bytes + + {{ exData(middleware).memRequestBodyBytes }} + + + + + + + + + Max Response Body Bytes + + {{ exData(middleware).maxResponseBodyBytes }} + + + + Mem Response Body Bytes + + {{ exData(middleware).memResponseBodyBytes }} + + + + + + + + + Retry Expression + + {{ exData(middleware).retryExpression }} + + + + + + + + + + Expression + + {{ exData(middleware).expression }} + + + + + + + + + + Compress + + + + + + + + + + Service + + {{ exData(middleware).service }} + + + + + + + + + Query + + {{ exData(middleware).query }} + + + + + + + + + Status + + {{ st }} + + + + + + + + + + Address + + {{ exData(middleware).address }} + + + + + + + + + TLS + + + + Trust Forward Headers + + + + + + + + + Auth Response Headers + + {{ respHeader }} + + + + + + + + + + Custom Request Headers + + {{ val }} + + + + + + + + + Custom Response Headers + + {{ val }} + + + + + + + + + Access Control Allow Credentials + + + + + + + + + Access Control Allow Headers + + {{ val }} + + + + + + + + + Access Control Allow Methods + + {{ val }} + + + + + + + + + Access Control Allow Origin + + {{ exData(middleware).accessControlAllowOrigin }} + + + + + + + + + Access Control Expose Headers + + {{ val }} + + + + + + + + + Access Control Max Age + + {{ exData(middleware).accessControlMaxAge }} + + + + + + + + + Add Vary Header + + + + + + + + + Allowed Hosts + + {{ val }} + + + + + + + + + Hosts Proxy Headers + + {{ val }} + + + + + + + + + SSL Redirect + + + + + + + + + SSL Temporary Redirect + + + + + + + + + SSL Host + + {{ exData(middleware).sslHost }} + + + + + + + + + SSL Proxy Headers + + {{ val }} + + + + + + + + + SSL Force Host + + + + + + + + + STS Seconds + + {{ exData(middleware).stsSeconds }} + + + + + + + + + STS Include Subdomains + + + + + + + + + STS Preload + + + + + + + + + Force STS Header + + + + + + + + + Frame Deny + + + + + + + + + Custom Frame Options Value + + {{ exData(middleware).customFrameOptionsValue }} + + + + + + + + + Content Type Nosniff + + + + + + + + + Browser XSS Filter + + + + + + + + + Custom Browser XSS Value + + {{ exData(middleware).customBrowserXSSValue }} + + + + + + + + + Content Security Policy + + {{ exData(middleware).contentSecurityPolicy }} + + + + + + + + + Public Key + + {{ exData(middleware).publicKey }} + + + + + + + + + Referrer Policy + + {{ exData(middleware).referrerPolicy }} + + + + + + + + + Feature Policy + + {{ exData(middleware).featurePolicy }} + + + + + + + + + Is Development + + + + + + + + + + Source Range + + {{ range }} + + + + + + + + + IP Strategy + + + Depth : + + {{ exData(middleware).ipStrategy.depth }} + + + + + + Excluded IPs: + + + {{ excludedIPs }} + + + + + + + + + + + Average + + {{ exData(middleware).average }} + + + + Burst + + {{ exData(middleware).burst }} + + + + + + + + + + AMOUNT + + {{ exData(middleware).amount }} + + + + + + + + + + IP STRATEGY + + + Depth : + + {{ exData(middleware).sourceCriterion.ipStrategy.depth }} + + + + + + Excluded IPs: + + + {{ excludedIPs }} + + + + + + + + + + REQUEST HEADER NAME + + {{ exData(middleware).sourceCriterion.requestHeaderName }} + + + + REQUEST HOST + + + + + + + + + + PEM + + + + + + + Info: + + + Not After + + + + Not Before + + + + Sans + + + + + + + Info Subject: + + + country + + + + Province + + + + + + + + Locality + + + + Organization + + + + + + + + Common Name + + + + Serial Number + + + + + + + + Domain Component + + + + + + + Info Issuer: + + + country + + + + Province + + + + + + + + Locality + + + + Organization + + + + + + + + Common Name + + + + Serial Number + + + + + + + + Domain Component + + + + + + + + + + Regex + + {{ exData(middleware).regex }} + + + + + + + + + Replacement + + {{ exData(middleware).replacement }} + + + + + + + + + Permanent + + + + + + + + + + Scheme + + {{ exData(middleware).scheme }} + + + + + + + + + + Path + + {{ exData(middleware).path }} + + + + + + + + + + Regex + + {{ exData(middleware).regex }} + + + + + + + + + Replacement + + {{ exData(middleware).replacement }} + + + + + + + + + + Attempts + + {{ exData(middleware).attempts }} + + + + + + + + + + Prefixes + + {{ prefix }} + + + + + + + + + + Regex + + {{ exp }} + + + + + + + + + + + + + + + + There are noMiddlewares configured + + + + + + + + + diff --git a/webui/src/components/_commons/PanelMirroringServices.vue b/webui/src/components/_commons/PanelMirroringServices.vue new file mode 100644 index 000000000..defa0eb74 --- /dev/null +++ b/webui/src/components/_commons/PanelMirroringServices.vue @@ -0,0 +1,133 @@ + + + + + + + Name + + + Percent + + + Provider + + + + + + + + + + {{ service.name }} + + + + {{ service.percent }} + + + + + + + + + + + + + + + + + diff --git a/webui/src/components/_commons/PanelRouterDetails.vue b/webui/src/components/_commons/PanelRouterDetails.vue new file mode 100644 index 000000000..b5c2aec2a --- /dev/null +++ b/webui/src/components/_commons/PanelRouterDetails.vue @@ -0,0 +1,204 @@ + + + + + + + STATUS + + + {{data.status | statusLabel}} + + + + PROVIDER + + + + + {{data.provider}} + + + + + + + + RULE + + {{ data.rule }} + + + + + + + + NAME + + {{ data.name }} + + + + + + + + ENTRYPOINTS + + {{ entryPoint }} + + + + + + + + SERVICE + + {{ data.service }} + + + + + + + + ERRORS + + {{ errorMsg }} + + + + + + + + + + + diff --git a/webui/src/components/_commons/PanelServers.vue b/webui/src/components/_commons/PanelServers.vue new file mode 100644 index 000000000..36968b476 --- /dev/null +++ b/webui/src/components/_commons/PanelServers.vue @@ -0,0 +1,123 @@ + + + + + + + Status + + + URL + + + + + + + + + + + + + + + + {{ server.url || server.address}} + + + + + + + + + + + + + diff --git a/webui/src/components/_commons/PanelServiceDetails.vue b/webui/src/components/_commons/PanelServiceDetails.vue new file mode 100644 index 000000000..f173c9b69 --- /dev/null +++ b/webui/src/components/_commons/PanelServiceDetails.vue @@ -0,0 +1,192 @@ + + + + + + + TYPE + + {{ data.type }} + + + + PROVIDER + + + + + {{data.provider}} + + + + + + + + STATUS + + + {{data.status | statusLabel}} + + + + + + + + Main Service + + {{ data.mirroring.service }} + + + + + + + + Pass Host Header + + + + + + + + + Sticky: Cookie + + + + + + NAME + + {{ data.weighted.sticky.cookie.name }} + + + + + + + + SECURE + + + + + HTTP Only + + + + + + + + + + + diff --git a/webui/src/components/_commons/PanelTLS.vue b/webui/src/components/_commons/PanelTLS.vue new file mode 100644 index 000000000..e79ffd52c --- /dev/null +++ b/webui/src/components/_commons/PanelTLS.vue @@ -0,0 +1,141 @@ + + + + + + + TLS + + + + + + + + OPTIONS + + {{ data.options }} + + + + + + + + PASSTHROUGH + + + + + + + + CERTIFICATE RESOLVER + + {{ data.certResolver }} + + + + + + + + DOMAINS + + + {{ domain.main }} + + + {{ domain }} + + + + + + + + + + + + + + There are noTLS configured + + + + + + + + + diff --git a/webui/src/components/_commons/PanelWeightedServices.vue b/webui/src/components/_commons/PanelWeightedServices.vue new file mode 100644 index 000000000..fb9147913 --- /dev/null +++ b/webui/src/components/_commons/PanelWeightedServices.vue @@ -0,0 +1,124 @@ + + + + + + + Name + + + Weight + + + Provider + + + + + + + + + + {{ service.name }} + + + + {{ service.weight }} + + + + + + + + + + + + + + + + + diff --git a/webui/src/components/_commons/TLSState.vue b/webui/src/components/_commons/TLSState.vue new file mode 100644 index 000000000..7f5f76138 --- /dev/null +++ b/webui/src/components/_commons/TLSState.vue @@ -0,0 +1,26 @@ + + + + + + + + + diff --git a/webui/src/components/_commons/ToolBar.vue b/webui/src/components/_commons/ToolBar.vue new file mode 100644 index 000000000..c69f3465f --- /dev/null +++ b/webui/src/components/_commons/ToolBar.vue @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + diff --git a/webui/src/components/_commons/ToolBarTable.vue b/webui/src/components/_commons/ToolBarTable.vue new file mode 100644 index 000000000..0d7bb4f16 --- /dev/null +++ b/webui/src/components/_commons/ToolBarTable.vue @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + diff --git a/webui/src/components/dashboard/PanelChart.vue b/webui/src/components/dashboard/PanelChart.vue index f0eb524db..7f72d71d6 100644 --- a/webui/src/components/dashboard/PanelChart.vue +++ b/webui/src/components/dashboard/PanelChart.vue @@ -5,6 +5,9 @@ {{getName}} + + + @@ -104,7 +107,7 @@ export default { } else { result = num } - return isNaN(result) ? 0 : result + return isNaN(result) || result < 0 ? 0 : result }, getWarnings (inPercent = false) { const num = this.data.warnings @@ -114,7 +117,7 @@ export default { } else { result = num } - return isNaN(result) ? 0 : result + return isNaN(result) || result < 0 ? 0 : result }, getErrors (inPercent = false) { const num = this.data.errors @@ -124,7 +127,7 @@ export default { } else { result = num } - return isNaN(result) ? 0 : result + return isNaN(result) || result < 0 ? 0 : result }, getData () { return [this.getSuccess(), this.getWarnings(), this.getErrors()] diff --git a/webui/src/components/dashboard/PanelEntry.vue b/webui/src/components/dashboard/PanelEntry.vue index b4d6767fb..2ae06af45 100644 --- a/webui/src/components/dashboard/PanelEntry.vue +++ b/webui/src/components/dashboard/PanelEntry.vue @@ -1,9 +1,9 @@ - + - {{name}} + {{name}} @@ -16,10 +16,41 @@ - diff --git a/webui/src/components/dashboard/PanelFeature.vue b/webui/src/components/dashboard/PanelFeature.vue index 85d80c9e9..d9c3c488a 100644 --- a/webui/src/components/dashboard/PanelFeature.vue +++ b/webui/src/components/dashboard/PanelFeature.vue @@ -1,18 +1,15 @@ - + - {{featureKey}} + {{featureKey}} {{getVal}} @@ -51,26 +48,37 @@ export default { diff --git a/webui/src/components/tcp/ToolBar.vue b/webui/src/components/tcp/ToolBar.vue deleted file mode 100644 index 275d671bd..000000000 --- a/webui/src/components/tcp/ToolBar.vue +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/webui/src/css/sass/_variables.scss b/webui/src/css/sass/_variables.scss index a4c13ac51..1a25552f6 100644 --- a/webui/src/css/sass/_variables.scss +++ b/webui/src/css/sass/_variables.scss @@ -12,6 +12,10 @@ $app-text-caption-dark: rgba(255,255,255,0.7); // Custom colors $app-text-white: #ffffff; $app-text-grey: #737373; +$app-text-purple: #9d0fb0; +$app-text-green: #24a1c1; +$app-text-green-2: #06a21d; +$app-text-marine: #06102a; // Quasar $primary: #06102a; diff --git a/webui/src/css/sass/app.scss b/webui/src/css/sass/app.scss index a6495a21c..002da20c5 100644 --- a/webui/src/css/sass/app.scss +++ b/webui/src/css/sass/app.scss @@ -39,6 +39,10 @@ body { color: $app-text-grey; } +.bg-app-toggle { + background-color: rgba( $accent, .1 ); +} + // Helps .xs-text-center { @include respond-to(sm, max) { @@ -60,6 +64,10 @@ body { &-label { font-size: inherit; font-weight: inherit; + &-sub { + font-size: 16px; + font-weight: 600; + } } .q-icon + &-label { margin-left: 8px; @@ -107,3 +115,45 @@ body { .q-card { border-radius: 8px; } + +// Chips +.app-chip { + border-radius: 8px; + font-weight: 600; + font-size: 14px; + &-wrap { + height: 100%; + flex-wrap: wrap; + .q-chip__content{ + white-space: normal; + } + } + &-accent, &-rule { + color: $accent; + background-color: rgba($accent, 0.1); + } + &-green, &-entry-points { + color: $app-text-green; + background-color: rgba($app-text-green, 0.1); + } + &-purple, &-name { + color: $app-text-purple; + background-color: rgba($app-text-purple, 0.1); + } + &-warning, &-service { + color: $warning; + background-color: rgba($warning, 0.1); + } + &-negative, &-error { + color: $negative; + background-color: rgba($negative, 0.1); + } + &-green-2, &-options { + color: $app-text-green-2; + background-color: rgba($app-text-green-2, 0.1); + } + &-marine, &-interval { + color: $app-text-marine; + background-color: rgba($app-text-grey, 0.1); + } +} diff --git a/webui/src/pages/_commons/MiddlewareDetail.vue b/webui/src/pages/_commons/MiddlewareDetail.vue new file mode 100644 index 000000000..2cc7876cf --- /dev/null +++ b/webui/src/pages/_commons/MiddlewareDetail.vue @@ -0,0 +1,161 @@ + + + + + + + {{ middlewareByName.item.name }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Used by Routers + + + + + + + + + + + + + + + diff --git a/webui/src/pages/_commons/RouterDetail.vue b/webui/src/pages/_commons/RouterDetail.vue new file mode 100644 index 000000000..0d3097156 --- /dev/null +++ b/webui/src/pages/_commons/RouterDetail.vue @@ -0,0 +1,293 @@ + + + + + + + + + + + Entrypoints + + + + + + + + + + + + + + + + + + + {{ routerType }} + + + + + + + + + + + + + + + + + + + HTTP Middlewares + + + + + + + + + + + + + + + + + + + Service + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Router Details + + + + + + + + + + + + + + + + TLS + + + + + + + + + + + + + + + + Middlewares + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/webui/src/pages/_commons/ServiceDetail.vue b/webui/src/pages/_commons/ServiceDetail.vue new file mode 100644 index 000000000..d4decb2fc --- /dev/null +++ b/webui/src/pages/_commons/ServiceDetail.vue @@ -0,0 +1,239 @@ + + + + + + + {{ serviceByName.item.name }} + + + + + + + + + + + + Service Details + + + + + + + + + + + + + + + + Health Check + + + + + + + + + + + + + + + + Servers + + + + + + + + + + + + + + + + Services + + + + + + + + + + + + + + + + Mirror Services + + + + + + + + + + + + + + + + + + + + + + + + + + + Used by Routers + + + + + + + + + + + + + + + diff --git a/webui/src/pages/http/Middlewares.vue b/webui/src/pages/http/Middlewares.vue index f8438e980..1415ba962 100644 --- a/webui/src/pages/http/Middlewares.vue +++ b/webui/src/pages/http/Middlewares.vue @@ -1,14 +1,108 @@ - - HTTP Middlewares - + + + + + + + + + + + + + + + + - - + + diff --git a/webui/src/pages/http/Routers.vue b/webui/src/pages/http/Routers.vue index 7995b0c1a..08330bd6d 100644 --- a/webui/src/pages/http/Routers.vue +++ b/webui/src/pages/http/Routers.vue @@ -1,14 +1,108 @@ - - HTTP Routers - + + + + + + + + + + + + + + + + - - + + diff --git a/webui/src/pages/http/Services.vue b/webui/src/pages/http/Services.vue index fbedfcb8b..c41ebb8a1 100644 --- a/webui/src/pages/http/Services.vue +++ b/webui/src/pages/http/Services.vue @@ -1,14 +1,108 @@ - - HTTP Services - + + + + + + + + + + + + + + + + - - + + diff --git a/webui/src/pages/tcp/Routers.vue b/webui/src/pages/tcp/Routers.vue index 17331131d..8e120b143 100644 --- a/webui/src/pages/tcp/Routers.vue +++ b/webui/src/pages/tcp/Routers.vue @@ -1,14 +1,108 @@ - - TCP Routers - + + + + + + + + + + + + + + + + - - + + diff --git a/webui/src/pages/tcp/Services.vue b/webui/src/pages/tcp/Services.vue index 2cfb7311e..698ef19b4 100644 --- a/webui/src/pages/tcp/Services.vue +++ b/webui/src/pages/tcp/Services.vue @@ -1,14 +1,108 @@ - - TCP Services - + + + + + + + + + + + + + + + + - - + + diff --git a/webui/src/router/routes.js b/webui/src/router/routes.js index 1e46a65c0..28ac34a0b 100644 --- a/webui/src/router/routes.js +++ b/webui/src/router/routes.js @@ -14,6 +14,150 @@ const routes = [ } } ] + }, + { + path: '/http', + redirect: '/http/routers', + component: LayoutDefault, + children: [ + { + path: 'routers', + name: 'httpRouters', + components: { + default: () => import('pages/http/Routers.vue'), + NavBar: () => import('components/_commons/ToolBar.vue') + }, + props: { default: true, NavBar: true }, + meta: { + protocol: 'http', + title: 'HTTP Routers' + } + }, + { + path: 'routers/:name', + name: 'httpRouterDetail', + components: { + default: () => import('pages/_commons/RouterDetail.vue'), + NavBar: () => import('components/_commons/ToolBar.vue') + }, + props: { default: true, NavBar: true }, + meta: { + protocol: 'http', + title: 'HTTP Router Detail' + } + }, + { + path: 'services', + name: 'httpServices', + components: { + default: () => import('pages/http/Services.vue'), + NavBar: () => import('components/_commons/ToolBar.vue') + }, + props: { default: true, NavBar: true }, + meta: { + protocol: 'http', + title: 'HTTP Services' + } + }, + { + path: 'services/:name', + name: 'httpServiceDetail', + components: { + default: () => import('pages/_commons/ServiceDetail.vue'), + NavBar: () => import('components/_commons/ToolBar.vue') + }, + props: { default: true, NavBar: true }, + meta: { + protocol: 'http', + title: 'HTTP Service Detail' + } + }, + { + path: 'middlewares', + name: 'httpMiddlewares', + components: { + default: () => import('pages/http/Middlewares.vue'), + NavBar: () => import('components/_commons/ToolBar.vue') + }, + props: { default: true, NavBar: true }, + meta: { + protocol: 'http', + title: 'HTTP Middlewares' + } + }, + { + path: 'middlewares/:name', + name: 'httpMiddlewareDetail', + components: { + default: () => import('pages/_commons/MiddlewareDetail.vue'), + NavBar: () => import('components/_commons/ToolBar.vue') + }, + props: { default: true, NavBar: true }, + meta: { + protocol: 'http', + title: 'HTTP Middleware Detail' + } + } + ] + }, + { + path: '/tcp', + redirect: '/tcp/routers', + component: LayoutDefault, + children: [ + { + path: 'routers', + name: 'tcpRouters', + components: { + default: () => import('pages/tcp/Routers.vue'), + NavBar: () => import('components/_commons/ToolBar.vue') + }, + props: { default: true, NavBar: true }, + meta: { + protocol: 'tcp', + title: 'TCP Routers' + } + }, + { + path: 'routers/:name', + name: 'tcpRouterDetail', + components: { + default: () => import('pages/_commons/RouterDetail.vue'), + NavBar: () => import('components/_commons/ToolBar.vue') + }, + props: { default: true, NavBar: true }, + meta: { + protocol: 'tcp', + title: 'TCP Router Detail' + } + }, + { + path: 'services', + name: 'tcpServices', + components: { + default: () => import('pages/tcp/Services.vue'), + NavBar: () => import('components/_commons/ToolBar.vue') + }, + props: { default: true, NavBar: true }, + meta: { + protocol: 'tcp', + title: 'TCP Services' + } + }, + { + path: 'services/:name', + name: 'tcpServiceDetail', + components: { + default: () => import('pages/_commons/ServiceDetail.vue'), + NavBar: () => import('components/_commons/ToolBar.vue') + }, + props: { default: true, NavBar: true }, + meta: { + protocol: 'tcp', + title: 'TCP Service Detail' + } + } + ] } ] diff --git a/webui/src/statics/app-logo-128x128.png b/webui/src/statics/app-logo-128x128.png old mode 100644 new mode 100755 index 0a3012a9e..af9348b10 Binary files a/webui/src/statics/app-logo-128x128.png and b/webui/src/statics/app-logo-128x128.png differ diff --git a/webui/src/statics/icons/apple-icon-120x120.png b/webui/src/statics/icons/apple-icon-120x120.png old mode 100644 new mode 100755 index 387499a81..2f400b009 Binary files a/webui/src/statics/icons/apple-icon-120x120.png and b/webui/src/statics/icons/apple-icon-120x120.png differ diff --git a/webui/src/statics/icons/apple-icon-152x152.png b/webui/src/statics/icons/apple-icon-152x152.png old mode 100644 new mode 100755 index 5b76f91cd..a2e9cdff8 Binary files a/webui/src/statics/icons/apple-icon-152x152.png and b/webui/src/statics/icons/apple-icon-152x152.png differ diff --git a/webui/src/statics/icons/apple-icon-167x167.png b/webui/src/statics/icons/apple-icon-167x167.png old mode 100644 new mode 100755 index 0d743a659..c24bd69a9 Binary files a/webui/src/statics/icons/apple-icon-167x167.png and b/webui/src/statics/icons/apple-icon-167x167.png differ diff --git a/webui/src/statics/icons/apple-icon-180x180.png b/webui/src/statics/icons/apple-icon-180x180.png old mode 100644 new mode 100755 index f91040da4..e3c67b1ec Binary files a/webui/src/statics/icons/apple-icon-180x180.png and b/webui/src/statics/icons/apple-icon-180x180.png differ diff --git a/webui/src/statics/icons/favicon-16x16.png b/webui/src/statics/icons/favicon-16x16.png old mode 100644 new mode 100755 index 55e18225e..36020af3e Binary files a/webui/src/statics/icons/favicon-16x16.png and b/webui/src/statics/icons/favicon-16x16.png differ diff --git a/webui/src/statics/icons/favicon-32x32.png b/webui/src/statics/icons/favicon-32x32.png old mode 100644 new mode 100755 index 886724313..4c42d44ef Binary files a/webui/src/statics/icons/favicon-32x32.png and b/webui/src/statics/icons/favicon-32x32.png differ diff --git a/webui/src/statics/icons/favicon-96x96.png b/webui/src/statics/icons/favicon-96x96.png old mode 100644 new mode 100755 index c0c651b2e..003f02915 Binary files a/webui/src/statics/icons/favicon-96x96.png and b/webui/src/statics/icons/favicon-96x96.png differ diff --git a/webui/src/statics/icons/favicon.ico b/webui/src/statics/icons/favicon.ico index 5c1b67817..b5b93f32d 100644 Binary files a/webui/src/statics/icons/favicon.ico and b/webui/src/statics/icons/favicon.ico differ diff --git a/webui/src/statics/icons/icon-128x128.png b/webui/src/statics/icons/icon-128x128.png old mode 100644 new mode 100755 index 0a3012a9e..af9348b10 Binary files a/webui/src/statics/icons/icon-128x128.png and b/webui/src/statics/icons/icon-128x128.png differ diff --git a/webui/src/statics/icons/icon-192x192.png b/webui/src/statics/icons/icon-192x192.png old mode 100644 new mode 100755 index 144d47494..8a4decd77 Binary files a/webui/src/statics/icons/icon-192x192.png and b/webui/src/statics/icons/icon-192x192.png differ diff --git a/webui/src/statics/icons/icon-256x256.png b/webui/src/statics/icons/icon-256x256.png old mode 100644 new mode 100755 index 9ab309bbc..25ef0429d Binary files a/webui/src/statics/icons/icon-256x256.png and b/webui/src/statics/icons/icon-256x256.png differ diff --git a/webui/src/statics/icons/icon-384x384.png b/webui/src/statics/icons/icon-384x384.png old mode 100644 new mode 100755 index 8bb494e1a..6bfa9990f Binary files a/webui/src/statics/icons/icon-384x384.png and b/webui/src/statics/icons/icon-384x384.png differ diff --git a/webui/src/statics/icons/icon-512x512.png b/webui/src/statics/icons/icon-512x512.png old mode 100644 new mode 100755 index 9b9ff56cb..be87a7a6b Binary files a/webui/src/statics/icons/icon-512x512.png and b/webui/src/statics/icons/icon-512x512.png differ diff --git a/webui/src/statics/icons/ms-icon-144x144.png b/webui/src/statics/icons/ms-icon-144x144.png old mode 100644 new mode 100755 index 29fd72a34..654ac3223 Binary files a/webui/src/statics/icons/ms-icon-144x144.png and b/webui/src/statics/icons/ms-icon-144x144.png differ diff --git a/webui/src/statics/icons/safari-pinned-tab.svg b/webui/src/statics/icons/safari-pinned-tab.svg old mode 100644 new mode 100755 index 1473927c3..8bd46a154 --- a/webui/src/statics/icons/safari-pinned-tab.svg +++ b/webui/src/statics/icons/safari-pinned-tab.svg @@ -1 +1,3 @@ - \ No newline at end of file + + + diff --git a/webui/src/statics/providers/kubernetescrd.svg b/webui/src/statics/providers/kubernetescrd.svg new file mode 100644 index 000000000..b6670fe5d --- /dev/null +++ b/webui/src/statics/providers/kubernetescrd.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/webui/src/statics/providers/kubernetesingress.svg b/webui/src/statics/providers/kubernetesingress.svg new file mode 100644 index 000000000..b6670fe5d --- /dev/null +++ b/webui/src/statics/providers/kubernetesingress.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/webui/src/statics/providers/rest-api.svg b/webui/src/statics/providers/rest.svg similarity index 100% rename from webui/src/statics/providers/rest-api.svg rename to webui/src/statics/providers/rest.svg diff --git a/webui/src/store/entrypoints/actions.js b/webui/src/store/entrypoints/actions.js index 79b009bf3..5b67b8ae1 100644 --- a/webui/src/store/entrypoints/actions.js +++ b/webui/src/store/entrypoints/actions.js @@ -1,8 +1,8 @@ -import entrypointsService from '../../_services/EntrypointsService' +import EntrypointsService from '../../_services/EntrypointsService' export function getAll ({ commit }) { commit('getAllRequest') - return entrypointsService.getAll() + return EntrypointsService.getAll() .then(body => { commit('getAllSuccess', body) return body @@ -12,3 +12,16 @@ export function getAll ({ commit }) { return Promise.reject(error) }) } + +export function getByName ({ commit }, name) { + commit('getByNameRequest') + return EntrypointsService.getByName(name) + .then(body => { + commit('getByNameSuccess', body) + return body + }) + .catch(error => { + commit('getByNameFailure', error) + return Promise.reject(error) + }) +} diff --git a/webui/src/store/entrypoints/getters.js b/webui/src/store/entrypoints/getters.js index b906a35e8..52431bc75 100644 --- a/webui/src/store/entrypoints/getters.js +++ b/webui/src/store/entrypoints/getters.js @@ -4,3 +4,10 @@ export function all (state) { return state.all } + +// ---------------------------- +// byName +// ---------------------------- +export function byName (state) { + return state.byName +} diff --git a/webui/src/store/entrypoints/mutations.js b/webui/src/store/entrypoints/mutations.js index 5d6d48329..5a80fb35e 100644 --- a/webui/src/store/entrypoints/mutations.js +++ b/webui/src/store/entrypoints/mutations.js @@ -16,3 +16,22 @@ export function getAllFailure (state, error) { export function getAllClear (state) { state.all = {} } + +// ---------------------------- +// Get By Name +// ---------------------------- +export function getByNameRequest (state) { + state.byName.loading = true +} + +export function getByNameSuccess (state, body) { + state.byName = { item: body, loading: false } +} + +export function getByNameFailure (state, error) { + state.byName = { error } +} + +export function getByNameClear (state) { + state.byName = {} +} diff --git a/webui/src/store/http/actions.js b/webui/src/store/http/actions.js new file mode 100644 index 000000000..e2deed265 --- /dev/null +++ b/webui/src/store/http/actions.js @@ -0,0 +1,79 @@ +import HttpService from '../../_services/HttpService' + +export function getAllRouters ({ commit }, params) { + commit('getAllRoutersRequest') + return HttpService.getAllRouters(params) + .then(body => { + commit('getAllRoutersSuccess', body) + return body + }) + .catch(error => { + commit('getAllRoutersFailure', error) + return Promise.reject(error) + }) +} + +export function getRouterByName ({ commit }, name) { + commit('getRouterByNameRequest') + return HttpService.getRouterByName(name) + .then(body => { + commit('getRouterByNameSuccess', body) + return body + }) + .catch(error => { + commit('getRouterByNameFailure', error) + return Promise.reject(error) + }) +} + +export function getAllServices ({ commit }, params) { + commit('getAllServicesRequest') + return HttpService.getAllServices(params) + .then(body => { + commit('getAllServicesSuccess', body) + return body + }) + .catch(error => { + commit('getAllServicesFailure', error) + return Promise.reject(error) + }) +} + +export function getServiceByName ({ commit }, name) { + commit('getServiceByNameRequest') + return HttpService.getServiceByName(name) + .then(body => { + commit('getServiceByNameSuccess', body) + return body + }) + .catch(error => { + commit('getServiceByNameFailure', error) + return Promise.reject(error) + }) +} + +export function getAllMiddlewares ({ commit }, params) { + commit('getAllMiddlewaresRequest') + return HttpService.getAllMiddlewares(params) + .then(body => { + commit('getAllMiddlewaresSuccess', body) + return body + }) + .catch(error => { + commit('getAllMiddlewaresFailure', error) + return Promise.reject(error) + }) +} + +export function getMiddlewareByName ({ commit }, name) { + commit('getMiddlewareByNameRequest') + return HttpService.getMiddlewareByName(name) + .then(body => { + commit('getMiddlewareByNameSuccess', body) + return body + }) + .catch(error => { + commit('getMiddlewareByNameFailure', error) + return Promise.reject(error) + }) +} diff --git a/webui/src/store/http/getters.js b/webui/src/store/http/getters.js new file mode 100644 index 000000000..2f822b452 --- /dev/null +++ b/webui/src/store/http/getters.js @@ -0,0 +1,41 @@ +// ---------------------------- +// all Routers +// ---------------------------- +export function allRouters (state) { + return state.allRouters +} + +// ---------------------------- +// Router by Name +// ---------------------------- +export function routerByName (state) { + return state.routerByName +} + +// ---------------------------- +// all Services +// ---------------------------- +export function allServices (state) { + return state.allServices +} + +// ---------------------------- +// Service by Name +// ---------------------------- +export function serviceByName (state) { + return state.serviceByName +} + +// ---------------------------- +// all Middlewares +// ---------------------------- +export function allMiddlewares (state) { + return state.allMiddlewares +} + +// ---------------------------- +// Middleware by Name +// ---------------------------- +export function middlewareByName (state) { + return state.middlewareByName +} diff --git a/webui/src/store/http/index.js b/webui/src/store/http/index.js new file mode 100644 index 000000000..babab8ec5 --- /dev/null +++ b/webui/src/store/http/index.js @@ -0,0 +1,12 @@ +import state from './state' +import * as getters from './getters' +import * as mutations from './mutations' +import * as actions from './actions' + +export default { + namespaced: true, + getters, + mutations, + actions, + state +} diff --git a/webui/src/store/http/mutations.js b/webui/src/store/http/mutations.js new file mode 100644 index 000000000..064b753c2 --- /dev/null +++ b/webui/src/store/http/mutations.js @@ -0,0 +1,113 @@ +// ---------------------------- +// Get All Routers +// ---------------------------- +export function getAllRoutersRequest (state) { + state.allRouters.loading = true +} + +export function getAllRoutersSuccess (state, body) { + state.allRouters = { items: body.data, total: body.total, loading: false } +} + +export function getAllRoutersFailure (state, error) { + state.allRouters = { error } +} + +export function getAllRoutersClear (state) { + state.allRouters = {} +} + +// ---------------------------- +// Get Router By Name +// ---------------------------- +export function getRouterByNameRequest (state) { + state.routerByName.loading = true +} + +export function getRouterByNameSuccess (state, body) { + state.routerByName = { item: body, loading: false } +} + +export function getRouterByNameFailure (state, error) { + state.routerByName = { error } +} + +export function getRouterByNameClear (state) { + state.routerByName = {} +} + +// ---------------------------- +// Get All Services +// ---------------------------- +export function getAllServicesRequest (state) { + state.allServices.loading = true +} + +export function getAllServicesSuccess (state, body) { + state.allServices = { items: body.data, total: body.total, loading: false } +} + +export function getAllServicesFailure (state, error) { + state.allServices = { error } +} + +export function getAllServicesClear (state) { + state.allServices = {} +} + +// ---------------------------- +// Get Service By Name +// ---------------------------- +export function getServiceByNameRequest (state) { + state.serviceByName.loading = true +} + +export function getServiceByNameSuccess (state, body) { + state.serviceByName = { item: body, loading: false } +} + +export function getServiceByNameFailure (state, error) { + state.serviceByName = { error } +} + +export function getServiceByNameClear (state) { + state.serviceByName = {} +} + +// ---------------------------- +// Get All Middlewares +// ---------------------------- +export function getAllMiddlewaresRequest (state) { + state.allMiddlewares.loading = true +} + +export function getAllMiddlewaresSuccess (state, body) { + state.allMiddlewares = { items: body.data, total: body.total, loading: false } +} + +export function getAllMiddlewaresFailure (state, error) { + state.allMiddlewares = { error } +} + +export function getAllMiddlewaresClear (state) { + state.allMiddlewares = {} +} + +// ---------------------------- +// Get Middleware By Name +// ---------------------------- +export function getMiddlewareByNameRequest (state) { + state.middlewareByName.loading = true +} + +export function getMiddlewareByNameSuccess (state, body) { + state.middlewareByName = { item: body, loading: false } +} + +export function getMiddlewareByNameFailure (state, error) { + state.middlewareByName = { error } +} + +export function getMiddlewareByNameClear (state) { + state.middlewareByName = {} +} diff --git a/webui/src/store/http/state.js b/webui/src/store/http/state.js new file mode 100644 index 000000000..0557a4fdd --- /dev/null +++ b/webui/src/store/http/state.js @@ -0,0 +1,8 @@ +export default { + allRouters: {}, + routerByName: {}, + allServices: {}, + serviceByName: {}, + allMiddlewares: {}, + middlewareByName: {} +} diff --git a/webui/src/store/index.js b/webui/src/store/index.js index 5d5fb3278..190ad6594 100644 --- a/webui/src/store/index.js +++ b/webui/src/store/index.js @@ -3,6 +3,8 @@ import Vuex from 'vuex' import core from './core' import entrypoints from './entrypoints' +import http from './http' +import tcp from './tcp' Vue.use(Vuex) @@ -15,7 +17,9 @@ export default function (/* { ssrContext } */) { const Store = new Vuex.Store({ modules: { core, - entrypoints + entrypoints, + http, + tcp }, // enable strict mode (adds overhead!) diff --git a/webui/src/store/tcp/actions.js b/webui/src/store/tcp/actions.js new file mode 100644 index 000000000..196723fed --- /dev/null +++ b/webui/src/store/tcp/actions.js @@ -0,0 +1,53 @@ +import TcpService from '../../_services/TcpService' + +export function getAllRouters ({ commit }, params) { + commit('getAllRoutersRequest') + return TcpService.getAllRouters(params) + .then(body => { + commit('getAllRoutersSuccess', body) + return body + }) + .catch(error => { + commit('getAllRoutersFailure', error) + return Promise.reject(error) + }) +} + +export function getRouterByName ({ commit }, name) { + commit('getRouterByNameRequest') + return TcpService.getRouterByName(name) + .then(body => { + commit('getRouterByNameSuccess', body) + return body + }) + .catch(error => { + commit('getRouterByNameFailure', error) + return Promise.reject(error) + }) +} + +export function getAllServices ({ commit }, params) { + commit('getAllServicesRequest') + return TcpService.getAllServices(params) + .then(body => { + commit('getAllServicesSuccess', body) + return body + }) + .catch(error => { + commit('getAllServicesFailure', error) + return Promise.reject(error) + }) +} + +export function getServiceByName ({ commit }, name) { + commit('getServiceByNameRequest') + return TcpService.getServiceByName(name) + .then(body => { + commit('getServiceByNameSuccess', body) + return body + }) + .catch(error => { + commit('getServiceByNameFailure', error) + return Promise.reject(error) + }) +} diff --git a/webui/src/store/tcp/getters.js b/webui/src/store/tcp/getters.js new file mode 100644 index 000000000..2b9611e94 --- /dev/null +++ b/webui/src/store/tcp/getters.js @@ -0,0 +1,27 @@ +// ---------------------------- +// all Routers +// ---------------------------- +export function allRouters (state) { + return state.allRouters +} + +// ---------------------------- +// Router by Name +// ---------------------------- +export function routerByName (state) { + return state.routerByName +} + +// ---------------------------- +// all Services +// ---------------------------- +export function allServices (state) { + return state.allServices +} + +// ---------------------------- +// Service by Name +// ---------------------------- +export function serviceByName (state) { + return state.serviceByName +} diff --git a/webui/src/store/tcp/index.js b/webui/src/store/tcp/index.js new file mode 100644 index 000000000..babab8ec5 --- /dev/null +++ b/webui/src/store/tcp/index.js @@ -0,0 +1,12 @@ +import state from './state' +import * as getters from './getters' +import * as mutations from './mutations' +import * as actions from './actions' + +export default { + namespaced: true, + getters, + mutations, + actions, + state +} diff --git a/webui/src/store/tcp/mutations.js b/webui/src/store/tcp/mutations.js new file mode 100644 index 000000000..bb6042f90 --- /dev/null +++ b/webui/src/store/tcp/mutations.js @@ -0,0 +1,75 @@ +// ---------------------------- +// Get All Routers +// ---------------------------- +export function getAllRoutersRequest (state) { + state.allRouters.loading = true +} + +export function getAllRoutersSuccess (state, body) { + state.allRouters = { items: body.data, total: body.total, loading: false } +} + +export function getAllRoutersFailure (state, error) { + state.allRouters = { error } +} + +export function getAllRoutersClear (state) { + state.allRouters = {} +} + +// ---------------------------- +// Get Router By Name +// ---------------------------- +export function getRouterByNameRequest (state) { + state.routerByName.loading = true +} + +export function getRouterByNameSuccess (state, body) { + state.routerByName = { item: body, loading: false } +} + +export function getRouterByNameFailure (state, error) { + state.routerByName = { error } +} + +export function getRouterByNameClear (state) { + state.routerByName = {} +} + +// ---------------------------- +// Get All Services +// ---------------------------- +export function getAllServicesRequest (state) { + state.allServices.loading = true +} + +export function getAllServicesSuccess (state, body) { + state.allServices = { items: body.data, total: body.total, loading: false } +} + +export function getAllServicesFailure (state, error) { + state.allServices = { error } +} + +export function getAllServicesClear (state) { + state.allServices = {} +} + +// ---------------------------- +// Get Service By Name +// ---------------------------- +export function getServiceByNameRequest (state) { + state.serviceByName.loading = true +} + +export function getServiceByNameSuccess (state, body) { + state.serviceByName = { item: body, loading: false } +} + +export function getServiceByNameFailure (state, error) { + state.serviceByName = { error } +} + +export function getServiceByNameClear (state) { + state.serviceByName = {} +} diff --git a/webui/src/store/tcp/state.js b/webui/src/store/tcp/state.js new file mode 100644 index 000000000..0eb429a34 --- /dev/null +++ b/webui/src/store/tcp/state.js @@ -0,0 +1,6 @@ +export default { + allRouters: {}, + routerByName: {}, + allServices: {}, + serviceByName: {} +}
+ +