From ee04f52a1691f21d8f56930e205986c7c03775c1 Mon Sep 17 00:00:00 2001 From: Mikhail Vasin Date: Fri, 8 Dec 2017 18:12:04 +0300 Subject: [PATCH 01/11] Fix broken links and improve ResponseCodeRatio() description --- docs/basics.md | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/basics.md b/docs/basics.md index 76fc323c3..15d3f977b 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -356,7 +356,7 @@ A backend is responsible to load-balance the traffic coming from one or more fro Various methods of load-balancing are supported: -- `wrr`: Weighted Round Robin +- `wrr`: Weighted Round Robin. - `drr`: Dynamic Round Robin: increases weights on servers that perform better than others. It also rolls back to original weights if the servers have changed. @@ -373,16 +373,13 @@ It can be configured using: For example: -- `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window for a frontend +- `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window for a frontend. - `LatencyAtQuantileMS(50.0) > 50`: watch latency at quantile in milliseconds. -- `ResponseCodeRatio(500, 600, 0, 600) > 0.5`: ratio of response codes in range [500-600) to [0-600) +- `ResponseCodeRatio(500, 600, 0, 600) > 0.5`: ratio of response codes in ranges [500-600) and [0-600). -To proactively prevent backends from being overwhelmed with high load, a maximum connection limit can -also be applied to each backend. +To proactively prevent backends from being overwhelmed with high load, a maximum connection limit can also be applied to each backend. -Maximum connections can be configured by specifying an integer value for `maxconn.amount` and -`maxconn.extractorfunc` which is a strategy used to determine how to categorize requests in order to -evaluate the maximum connections. +Maximum connections can be configured by specifying an integer value for `maxconn.amount` and `maxconn.extractorfunc` which is a strategy used to determine how to categorize requests in order to evaluate the maximum connections. For example: ```toml @@ -499,8 +496,8 @@ Here is an example of backends and servers definition: Træfik's configuration has two parts: -- The [static Træfik configuration](/basics#static-trfk-configuration) which is loaded only at the beginning. -- The [dynamic Træfik configuration](/basics#dynamic-trfk-configuration) which can be hot-reloaded (no need to restart the process). +- The [static Træfik configuration](/basics#static-trfik-configuration) which is loaded only at the beginning. +- The [dynamic Træfik configuration](/basics#dynamic-trfik-configuration) which can be hot-reloaded (no need to restart the process). ### Static Træfik configuration @@ -585,7 +582,7 @@ traefik [command] [--flag=flag_argument] List of Træfik available commands with description : - `version` : Print version -- `storeconfig` : Store the static Traefik configuration into a Key-value stores. Please refer to the [Store Træfik configuration](/user-guide/kv-config/#store-trfk-configuration) section to get documentation on it. +- `storeconfig` : Store the static Traefik configuration into a Key-value stores. Please refer to the [Store Træfik configuration](/user-guide/kv-config/#store-configuration-in-key-value-store) section to get documentation on it. - `bug`: The easiest way to submit a pre-filled issue. - `healthcheck`: Calls Traefik `/ping` to check health. From 709c7e5707ea3cae7e3b6486fe6995943bb1383f Mon Sep 17 00:00:00 2001 From: Michael MATUR Date: Mon, 11 Dec 2017 13:56:46 +0100 Subject: [PATCH 02/11] Improve documentation for Cloudflare API key --- docs/configuration/acme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/acme.md b/docs/configuration/acme.md index 99f293e1b..8308e7d41 100644 --- a/docs/configuration/acme.md +++ b/docs/configuration/acme.md @@ -135,7 +135,7 @@ Select the provider that matches the DNS domain that will host the challenge TXT |--------------------------------------------------------|----------------|---------------------------------------------------------------------------------------------------------------------------| | [Auroradns](https://www.pcextreme.com/aurora/dns) | `auroradns` | `AURORA_USER_ID`, `AURORA_KEY`, `AURORA_ENDPOINT` | | [Azure](https://azure.microsoft.com/services/dns/) | `azure` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `AZURE_TENANT_ID`, `AZURE_RESOURCE_GROUP` | -| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CLOUDFLARE_EMAIL`, `CLOUDFLARE_API_KEY` | +| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CLOUDFLARE_EMAIL`, `CLOUDFLARE_API_KEY` - The Cloudflare `Global API Key` needs to be used and not the `Origin CA Key` | | [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | `DO_AUTH_TOKEN` | | [DNSimple](https://dnsimple.com) | `dnsimple` | `DNSIMPLE_OAUTH_TOKEN`, `DNSIMPLE_BASE_URL` | | [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET`, `DNSMADEEASY_SANDBOX` | From 623a7dc7e669afbe09adff556e314f87796ba1eb Mon Sep 17 00:00:00 2001 From: Michael MATUR Date: Mon, 11 Dec 2017 18:01:44 +0100 Subject: [PATCH 03/11] Fix small missing property in documentation for consul catalog --- docs/configuration/backends/consul.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/configuration/backends/consul.md b/docs/configuration/backends/consul.md index 25d809906..e59d6ca18 100644 --- a/docs/configuration/backends/consul.md +++ b/docs/configuration/backends/consul.md @@ -88,6 +88,12 @@ endpoint = "127.0.0.1:8500" # exposedByDefault = false +# Default domain used. +# +# Optional +# +domain = "consul.localhost" + # Prefix for Consul catalog tags. # # Optional From b0c12e24220226216cbc49ea930f2c9dcb9edadc Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 13 Dec 2017 17:02:04 +0100 Subject: [PATCH 04/11] Fix: frontend redirect --- server/server.go | 75 +++++++++------ server/server_test.go | 213 +++++++++++++++++++++++++++++++++++------- 2 files changed, 223 insertions(+), 65 deletions(-) diff --git a/server/server.go b/server/server.go index 3fa94268b..33e8d2b27 100644 --- a/server/server.go +++ b/server/server.go @@ -49,6 +49,10 @@ import ( "golang.org/x/net/http2" ) +const ( + defaultRedirectRegex = `^(?:https?:\/\/)?([\w\._-]+)(?::\d+)?(.*)$` +) + var ( httpServerLogger = stdlog.New(log.WriterLevel(logrus.DebugLevel), "", 0) ) @@ -940,7 +944,7 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura if entryPoint.Redirect != nil { if redirectHandlers[entryPointName] != nil { n.Use(redirectHandlers[entryPointName]) - } else if handler, err := s.loadEntryPointConfig(entryPointName, entryPoint); err != nil { + } else if handler, err := s.buildEntryPointRedirect(entryPointName, entryPoint); err != nil { log.Errorf("Error loading entrypoint configuration for frontend %s: %v", frontendName, err) log.Errorf("Skipping frontend %s...", frontendName) continue frontend @@ -1121,25 +1125,19 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura ipWhitelistMiddleware, err := configureIPWhitelistMiddleware(frontend.WhitelistSourceRange) if err != nil { - log.Fatalf("Error creating IP Whitelister: %s", err) + log.Errorf("Error creating IP Whitelister: %s", err) } else if ipWhitelistMiddleware != nil { n.Use(ipWhitelistMiddleware) log.Infof("Configured IP Whitelists: %s", frontend.WhitelistSourceRange) } if len(frontend.Redirect) > 0 { - proto := "http" - if s.globalConfiguration.EntryPoints[frontend.Redirect].TLS != nil { - proto = "https" - } - - regex, replacement, err := s.buildRedirect(proto, entryPoint) - rewrite, err := middlewares.NewRewrite(regex, replacement, true) + rewrite, err := s.buildRedirectRewrite(entryPointName, frontend.Redirect) if err != nil { - log.Fatalf("Error creating Frontend Redirect: %v", err) + log.Errorf("Error creating Frontend Redirect: %v", err) } n.Use(rewrite) - log.Debugf("Creating frontend %s redirect to %s", frontendName, proto) + log.Debugf("Frontend %s redirect created", frontendName) } if len(frontend.BasicAuth) > 0 { @@ -1289,38 +1287,57 @@ func (s *Server) wireFrontendBackend(serverRoute *serverRoute, handler http.Hand serverRoute.route.Handler(handler) } -func (s *Server) loadEntryPointConfig(entryPointName string, entryPoint *configuration.EntryPoint) (negroni.Handler, error) { +func (s *Server) buildEntryPointRedirect(srcEntryPointName string, entryPoint *configuration.EntryPoint) (*middlewares.Rewrite, error) { + if len(entryPoint.Redirect.EntryPoint) > 0 { + return s.buildRedirectRewrite(srcEntryPointName, entryPoint.Redirect.EntryPoint) + } + regex := entryPoint.Redirect.Regex replacement := entryPoint.Redirect.Replacement - var err error - if len(entryPoint.Redirect.EntryPoint) > 0 { - var protocol = "http" - if s.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint].TLS != nil { - protocol = "https" - } - regex, replacement, err = s.buildRedirect(protocol, entryPoint) - } rewrite, err := middlewares.NewRewrite(regex, replacement, true) if err != nil { return nil, err } - log.Debugf("Creating entryPoint redirect %s -> %s : %s -> %s", entryPointName, entryPoint.Redirect.EntryPoint, regex, replacement) + log.Debugf("Creating entryPoint redirect %s -> %s : %s -> %s", srcEntryPointName, entryPoint.Redirect.EntryPoint, regex, replacement) return rewrite, nil } -func (s *Server) buildRedirect(protocol string, entryPoint *configuration.EntryPoint) (string, string, error) { - regex := `^(?:https?:\/\/)?([\w\._-]+)(?::\d+)?(.*)$` - if s.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint] == nil { - return "", "", fmt.Errorf("unknown target entrypoint %q", entryPoint.Redirect.EntryPoint) +func (s *Server) buildRedirectRewrite(srcEntryPointName string, redirectEntryPoint string) (*middlewares.Rewrite, error) { + regex, replacement, err := s.buildRedirect(redirectEntryPoint) + if err != nil { + return nil, err } - r, _ := regexp.Compile(`(:\d+)`) - match := r.FindStringSubmatch(s.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint].Address) + + rewrite, err := middlewares.NewRewrite(regex, replacement, true) + if err != nil { + // Impossible case because error is always nil + return nil, err + } + log.Debugf("Creating entryPoint redirect %s -> %s : %s -> %s", srcEntryPointName, redirectEntryPoint, regex, replacement) + + return rewrite, nil +} + +func (s *Server) buildRedirect(entryPointName string) (string, string, error) { + entryPoint := s.globalConfiguration.EntryPoints[entryPointName] + if entryPoint == nil { + return "", "", fmt.Errorf("unknown target entrypoint %q", entryPointName) + } + + exp := regexp.MustCompile(`(:\d+)`) + match := exp.FindStringSubmatch(entryPoint.Address) if len(match) == 0 { - return "", "", fmt.Errorf("bad Address format %q", s.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint].Address) + return "", "", fmt.Errorf("bad Address format %q", entryPoint.Address) } + + var protocol = "http" + if s.globalConfiguration.EntryPoints[entryPointName].TLS != nil { + protocol = "https" + } + replacement := protocol + "://$1" + match[0] + "$2" - return regex, replacement, nil + return defaultRedirectRegex, replacement, nil } func (s *Server) buildDefaultHTTPRouter() *mux.Router { diff --git a/server/server_test.go b/server/server_test.go index c93587370..ac8737b90 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -903,55 +903,196 @@ func TestServerResponseEmptyBackend(t *testing.T) { } } -func TestServerLoadConfigBuildRedirect(t *testing.T) { +func TestBuildEntryPointRedirect(t *testing.T) { + srv := Server{ + globalConfiguration: configuration.GlobalConfiguration{ + EntryPoints: configuration.EntryPoints{ + "http": &configuration.EntryPoint{Address: ":80"}, + "https": &configuration.EntryPoint{Address: ":443", TLS: &tls.TLS{}}, + }, + }, + } + testCases := []struct { - desc string - replacementProtocol string - globalConfiguration configuration.GlobalConfiguration - originEntryPointName string - expectedReplacement string + desc string + srcEntryPointName string + url string + entryPoint *configuration.EntryPoint + expectedURL string }{ { - desc: "Redirect endpoint http to https with HTTPS protocol", - replacementProtocol: "https", - originEntryPointName: "http", - globalConfiguration: configuration.GlobalConfiguration{ - EntryPoints: configuration.EntryPoints{ - "http": &configuration.EntryPoint{ - Address: ":80", - Redirect: &configuration.Redirect{ - EntryPoint: "https", - }, - }, - "https": &configuration.EntryPoint{ - Address: ":443", - TLS: &tls.TLS{}, - }, + desc: "redirect regex", + srcEntryPointName: "http", + url: "http://foo.com", + entryPoint: &configuration.EntryPoint{ + Address: ":80", + Redirect: &configuration.Redirect{ + Regex: `^(?:http?:\/\/)(foo)(\.com)$`, + Replacement: "https://$1{{\"bar\"}}$2", }, }, + expectedURL: "https://foobar.com", + }, + { + desc: "redirect entry point", + srcEntryPointName: "http", + url: "http://foo:80", + entryPoint: &configuration.EntryPoint{ + Address: ":80", + Redirect: &configuration.Redirect{ + EntryPoint: "https", + }, + }, + expectedURL: "https://foo:443", + }, + { + desc: "redirect entry point with regex (ignored)", + srcEntryPointName: "http", + url: "http://foo.com:80", + entryPoint: &configuration.EntryPoint{ + Address: ":80", + Redirect: &configuration.Redirect{ + EntryPoint: "https", + Regex: `^(?:http?:\/\/)(foo)(\.com)$`, + Replacement: "https://$1{{\"bar\"}}$2", + }, + }, + expectedURL: "https://foo.com:443", + }, + } + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + rewrite, err := srv.buildEntryPointRedirect(test.srcEntryPointName, test.entryPoint) + require.NoError(t, err) + + req := testhelpers.MustNewRequest(http.MethodGet, test.url, nil) + recorder := httptest.NewRecorder() + + rewrite.ServeHTTP(recorder, req, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Location", "fail") + })) + + location, err := recorder.Result().Location() + require.NoError(t, err) + assert.Equal(t, test.expectedURL, location.String()) + }) + } +} + +func TestServerBuildRedirectRewrite(t *testing.T) { + srv := Server{ + globalConfiguration: configuration.GlobalConfiguration{ + EntryPoints: configuration.EntryPoints{ + "http": &configuration.EntryPoint{Address: ":80"}, + "https": &configuration.EntryPoint{Address: ":443", TLS: &tls.TLS{}}, + }, + }, + } + + testCases := []struct { + desc string + srcEntryPointName string + redirectEntryPoint string + url string + expectedURL string + errorExpected bool + }{ + { + desc: "existing redirect entry point", + srcEntryPointName: "http", + redirectEntryPoint: "https", + url: "http://foo:80", + expectedURL: "https://foo:443", + }, + { + desc: "non-existing redirect entry point", + srcEntryPointName: "http", + redirectEntryPoint: "foo", + url: "http://foo:80", + errorExpected: true, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + rewrite, err := srv.buildRedirectRewrite(test.srcEntryPointName, test.redirectEntryPoint) + if test.errorExpected { + require.Error(t, err) + } else { + require.NoError(t, err) + + recorder := httptest.NewRecorder() + r := testhelpers.MustNewRequest(http.MethodGet, test.url, nil) + rewrite.ServeHTTP(recorder, r, nil) + + location, err := recorder.Result().Location() + require.NoError(t, err) + + assert.Equal(t, test.expectedURL, location.String()) + } + }) + } +} + +func TestServerBuildRedirect(t *testing.T) { + testCases := []struct { + desc string + globalConfiguration configuration.GlobalConfiguration + redirectEntryPointName string + expectedReplacement string + errorExpected bool + }{ + { + desc: "Redirect endpoint http to https with HTTPS protocol", + redirectEntryPointName: "https", + globalConfiguration: configuration.GlobalConfiguration{ + EntryPoints: configuration.EntryPoints{ + "http": &configuration.EntryPoint{Address: ":80"}, + "https": &configuration.EntryPoint{Address: ":443", TLS: &tls.TLS{}}, + }, + }, expectedReplacement: "https://$1:443$2", }, { - desc: "Redirect endpoint http to http02 with HTTP protocol", - replacementProtocol: "http", - originEntryPointName: "http", + desc: "Redirect endpoint http to http02 with HTTP protocol", + redirectEntryPointName: "http02", globalConfiguration: configuration.GlobalConfiguration{ EntryPoints: configuration.EntryPoints{ - "http": &configuration.EntryPoint{ - Address: ":80", - Redirect: &configuration.Redirect{ - EntryPoint: "http02", - }, - }, - "http02": &configuration.EntryPoint{ - Address: ":88", - }, + "http": &configuration.EntryPoint{Address: ":80"}, + "http02": &configuration.EntryPoint{Address: ":88"}, }, }, - expectedReplacement: "http://$1:88$2", }, + { + desc: "Redirect endpoint to non-existent entry point", + redirectEntryPointName: "foobar", + globalConfiguration: configuration.GlobalConfiguration{ + EntryPoints: configuration.EntryPoints{ + "http": &configuration.EntryPoint{Address: ":80"}, + "http02": &configuration.EntryPoint{Address: ":88"}, + }, + }, + errorExpected: true, + }, + { + desc: "Redirect endpoint to an entry point with a malformed address", + redirectEntryPointName: "http02", + globalConfiguration: configuration.GlobalConfiguration{ + EntryPoints: configuration.EntryPoints{ + "http": &configuration.EntryPoint{Address: ":80"}, + "http02": &configuration.EntryPoint{Address: "88"}, + }, + }, + errorExpected: true, + }, } for _, test := range testCases { @@ -961,9 +1102,9 @@ func TestServerLoadConfigBuildRedirect(t *testing.T) { srv := Server{globalConfiguration: test.globalConfiguration} - _, replacement, err := srv.buildRedirect(test.replacementProtocol, srv.globalConfiguration.EntryPoints[test.originEntryPointName]) + _, replacement, err := srv.buildRedirect(test.redirectEntryPointName) - require.NoError(t, err, "build redirect sent an unexpected error") + require.Equal(t, test.errorExpected, err != nil, "Expected an error but don't have error, or Expected no error but have an error: %v", err) assert.Equal(t, test.expectedReplacement, replacement, "build redirect does not return the right replacement pattern") }) } From b6f5a66fabdfb6350d955a784844f45f2705c9f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rald=20Cro=C3=ABs?= Date: Wed, 13 Dec 2017 18:22:05 +0100 Subject: [PATCH 05/11] Grammar --- README.md | 2 +- docs/index.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b8c09ca96..8e2b7809a 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ If you want your users to access some of your microservices from the Internet, y - path `domain.com/web` will point the microservice `web` in your private network - domain `backoffice.domain.com` will point the microservices `backoffice` in your private network, load-balancing between your multiple instances -But a microservices architecture is dynamic... Services are added, removed, killed or upgraded often, eventually several times a day. +Microservices are often deployed in dynamic environments where services are added, removed, killed, upgraded or scaled many times a day. Traditional reverse-proxies are not natively dynamic. You can't change their configuration and hot-reload easily. diff --git a/docs/index.md b/docs/index.md index 0ee4f79d9..4ff7d5dcf 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,7 +22,7 @@ If you want your users to access some of your microservices from the Internet, y - path `domain.com/web` will point the microservice `web` in your private network - domain `backoffice.domain.com` will point the microservices `backoffice` in your private network, load-balancing between your multiple instances -But a microservices architecture is dynamic... Services are added, removed, killed or upgraded often, eventually several times a day. +Microservices are often deployed in dynamic environments where services are added, removed, killed, upgraded or scaled many times a day. Traditional reverse-proxies are not natively dynamic. You can't change their configuration and hot-reload easily. @@ -129,7 +129,7 @@ Start it from within the `traefik` folder: docker-compose up -d ``` -In a browser you may open [http://localhost:8080](http://localhost:8080) to access Træfik's dashboard and observe the following magic. +In a browser, you may open [http://localhost:8080](http://localhost:8080) to access Træfik's dashboard and observe the following magic. Now, create a folder named `test` and create a `docker-compose.yml` in it with this content: From 350d61b4a633dae239286a38e299aacdf5c64333 Mon Sep 17 00:00:00 2001 From: Timo Reimann Date: Thu, 14 Dec 2017 16:06:03 +0100 Subject: [PATCH 06/11] Fix github.com/containous/traefik-extra-service-fabric dep to v1.0.1. --- glide.lock | 2 +- glide.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/glide.lock b/glide.lock index 17a9a2040..d3d61ab95 100644 --- a/glide.lock +++ b/glide.lock @@ -1,4 +1,4 @@ -hash: 4595cac0a682ce8e36b78630f12a3cfab75307fc58cb4a1f5e416017d3ae20d6 +hash: 798765d68f54df534f091b4963f76f3e26d8dac2cb444368c2fa1c572e3c1ea6 updated: 2017-11-30T10:34:41.246378337+01:00 imports: - name: cloud.google.com/go diff --git a/glide.yaml b/glide.yaml index 0bf2656a3..5e387f1aa 100644 --- a/glide.yaml +++ b/glide.yaml @@ -12,7 +12,7 @@ import: - package: github.com/cenk/backoff - package: github.com/containous/flaeg - package: github.com/containous/traefik-extra-service-fabric - version: ^v1.0.1 + version: v1.0.1 - package: github.com/vulcand/oxy version: 7b6e758ab449705195df638765c4ca472248908a repo: https://github.com/containous/oxy.git From 799136a714216a1bf9189b6d4862117b8bd02998 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 15 Dec 2017 01:22:03 +0100 Subject: [PATCH 07/11] fix: backend name for Stateful services. (Service Fabric) --- glide.lock | 8 +- glide.yaml | 2 +- .../traefik-extra-service-fabric/labels.go | 62 ++++-- .../servicefabric.go | 188 ++++++++---------- .../servicefabric_tmpl.go | 64 +++--- .../jjcollinge/servicefabric/servicefabric.go | 1 + 6 files changed, 174 insertions(+), 151 deletions(-) diff --git a/glide.lock b/glide.lock index d3d61ab95..f35752694 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 798765d68f54df534f091b4963f76f3e26d8dac2cb444368c2fa1c572e3c1ea6 -updated: 2017-11-30T10:34:41.246378337+01:00 +hash: 03cd7f5ecab087e73cc395cb61b58b82cefef55969aa368f93fc17095b92815f +updated: 2017-12-15T10:34:41.246378337+01:00 imports: - name: cloud.google.com/go version: 2e6a95edb1071d750f6d7db777bf66cd2997af6c @@ -94,7 +94,7 @@ imports: - name: github.com/containous/staert version: af517d5b70db9c4b0505e0144fcc62b054057d2a - name: github.com/containous/traefik-extra-service-fabric - version: 8076098dbfe814cba9e895ecbd896f1896b6b2d5 + version: c01c1ef60ed612c5e42c1ceae0c6f92e67619cc3 - name: github.com/coreos/bbolt version: 3c6cbfb299c11444eb2f8c9d48f0d2ce09157423 - name: github.com/coreos/etcd @@ -348,7 +348,7 @@ imports: subpackages: - lib - name: github.com/jjcollinge/servicefabric - version: 93a44e59fc887cda489913c6fc5bda834989f3bd + version: 8026935326c842b71dee8e2329c1fda41a7a92f4 - name: github.com/jmespath/go-jmespath version: bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d - name: github.com/jonboulle/clockwork diff --git a/glide.yaml b/glide.yaml index 5e387f1aa..f582a9187 100644 --- a/glide.yaml +++ b/glide.yaml @@ -12,7 +12,7 @@ import: - package: github.com/cenk/backoff - package: github.com/containous/flaeg - package: github.com/containous/traefik-extra-service-fabric - version: v1.0.1 + version: v1.0.4 - package: github.com/vulcand/oxy version: 7b6e758ab449705195df638765c4ca472248908a repo: https://github.com/containous/oxy.git diff --git a/vendor/github.com/containous/traefik-extra-service-fabric/labels.go b/vendor/github.com/containous/traefik-extra-service-fabric/labels.go index 2dea665ae..f2878d20a 100644 --- a/vendor/github.com/containous/traefik-extra-service-fabric/labels.go +++ b/vendor/github.com/containous/traefik-extra-service-fabric/labels.go @@ -1,23 +1,59 @@ package servicefabric -import "strings" +import ( + "strconv" + "strings" +) -func hasServiceLabel(service ServiceItemExtended, key string) bool { - _, exists := service.Labels[key] - return exists -} - -func getFuncBoolLabel(labelName string) func(service ServiceItemExtended) bool { +func getFuncBoolLabel(labelName string, defaultValue bool) func(service ServiceItemExtended) bool { return func(service ServiceItemExtended) bool { - return getBoolLabel(service, labelName) + return getBoolValue(service.Labels, labelName, defaultValue) } } -func getBoolLabel(service ServiceItemExtended, labelName string) bool { - value, exists := service.Labels[labelName] - return exists && strings.EqualFold(strings.TrimSpace(value), "true") +func getFuncServiceStringLabel(service ServiceItemExtended, labelName string, defaultValue string) string { + return getStringValue(service.Labels, labelName, defaultValue) } -func getServiceLabelValue(service ServiceItemExtended, key string) string { - return service.Labels[key] +func hasFuncService(service ServiceItemExtended, labelName string) bool { + return hasLabel(service.Labels, labelName) +} + +func getServiceLabelsWithPrefix(service ServiceItemExtended, prefix string) map[string]string { + results := make(map[string]string) + for k, v := range service.Labels { + if strings.HasPrefix(k, prefix) { + results[k] = v + } + } + return results +} + +// must be replace by label.Has() +// Deprecated +func hasLabel(labels map[string]string, labelName string) bool { + value, ok := labels[labelName] + return ok && len(value) > 0 +} + +// must be replace by label.GetStringValue() +// Deprecated +func getStringValue(labels map[string]string, labelName string, defaultValue string) string { + if value, ok := labels[labelName]; ok && len(value) > 0 { + return value + } + return defaultValue +} + +// must be replace by label.GetBoolValue() +// Deprecated +func getBoolValue(labels map[string]string, labelName string, defaultValue bool) bool { + rawValue, ok := labels[labelName] + if ok { + v, err := strconv.ParseBool(rawValue) + if err == nil { + return v + } + } + return defaultValue } diff --git a/vendor/github.com/containous/traefik-extra-service-fabric/servicefabric.go b/vendor/github.com/containous/traefik-extra-service-fabric/servicefabric.go index 79ed5ef57..8183d9480 100644 --- a/vendor/github.com/containous/traefik-extra-service-fabric/servicefabric.go +++ b/vendor/github.com/containous/traefik-extra-service-fabric/servicefabric.go @@ -69,34 +69,7 @@ func (p *Provider) updateConfig(configurationChan chan<- types.ConfigMessage, po log.Info("Checking service fabric config") } - services, err := getClusterServices(sfClient) - if err != nil { - return err - } - - templateObjects := struct { - Services []ServiceItemExtended - }{ - services, - } - - var sfFuncMap = template.FuncMap{ - "isPrimary": isPrimary, - "getDefaultEndpoint": p.getDefaultEndpoint, - "getNamedEndpoint": p.getNamedEndpoint, - "getApplicationParameter": p.getApplicationParameter, - "doesAppParamContain": p.doesAppParamContain, - "hasServiceLabel": hasServiceLabel, - "getServiceLabelValue": getServiceLabelValue, - "getServiceLabelValueWithDefault": getServiceLabelValueWithDefault, - "getServiceLabelsWithPrefix": getServiceLabelsWithPrefix, - "getServicesWithLabelValueMap": getServicesWithLabelValueMap, - "getServicesWithLabelValue": getServicesWithLabelValue, - "isExposed": getFuncBoolLabel("expose"), - } - - configuration, err := p.GetConfiguration(tmpl, sfFuncMap, templateObjects) - + configuration, err := p.buildConfiguration(sfClient) if err != nil { return err } @@ -120,24 +93,39 @@ func (p *Provider) updateConfig(configurationChan chan<- types.ConfigMessage, po return nil } -func (p Provider) doesAppParamContain(app sf.ApplicationItem, key, shouldContain string) bool { - value := p.getApplicationParameter(app, key) - return strings.Contains(value, shouldContain) -} - -func (p Provider) getApplicationParameter(app sf.ApplicationItem, key string) string { - for _, param := range app.Parameters { - if param.Key == key { - return param.Value - } +func (p *Provider) buildConfiguration(sfClient sfClient) (*types.Configuration, error) { + var sfFuncMap = template.FuncMap{ + "getServices": getServices, + "hasLabel": hasFuncService, + "getLabelValue": getFuncServiceStringLabel, + "getLabelsWithPrefix": getServiceLabelsWithPrefix, + "isPrimary": isPrimary, + "isExposed": getFuncBoolLabel("expose", false), + "getBackendName": getBackendName, + "getDefaultEndpoint": getDefaultEndpoint, + "getNamedEndpoint": getNamedEndpoint, // FIXME unused + "getApplicationParameter": getApplicationParameter, // FIXME unused + "doesAppParamContain": doesAppParamContain, // FIXME unused + "filterServicesByLabelValue": filterServicesByLabelValue, // FIXME unused } - log.Errorf("Parameter %s doesn't exist in app %s", key, app.Name) - return "" + + services, err := getClusterServices(sfClient) + if err != nil { + return nil, err + } + + templateObjects := struct { + Services []ServiceItemExtended + }{ + Services: services, + } + + return p.GetConfiguration(tmpl, sfFuncMap, templateObjects) } -func (p Provider) getDefaultEndpoint(instance replicaInstance) string { +func getDefaultEndpoint(instance replicaInstance) string { id, data := instance.GetReplicaData() - endpoint, err := getDefaultEndpoint(data.Address) + endpoint, err := getReplicaDefaultEndpoint(data) if err != nil { log.Warnf("No default endpoint for replica %s in service %s endpointData: %s", id, data.Address) return "" @@ -145,16 +133,64 @@ func (p Provider) getDefaultEndpoint(instance replicaInstance) string { return endpoint } -func (p Provider) getNamedEndpoint(instance replicaInstance, endpointName string) string { - id, data := instance.GetReplicaData() - endpoint, err := getNamedEndpoint(data.Address, endpointName) +func getReplicaDefaultEndpoint(replicaData *sf.ReplicaItemBase) (string, error) { + endpoints, err := decodeEndpointData(replicaData.Address) if err != nil { - log.Warnf("No names endpoint of %s for replica %s in endpointData: %s", endpointName, id, data.Address) + return "", err + } + + var defaultHTTPEndpoint string + for _, v := range endpoints { + if strings.Contains(v, "http") { + defaultHTTPEndpoint = v + break + } + } + + if len(defaultHTTPEndpoint) == 0 { + return "", errors.New("no default endpoint found") + } + return defaultHTTPEndpoint, nil +} + +func getNamedEndpoint(instance replicaInstance, endpointName string) string { + id, data := instance.GetReplicaData() + endpoint, err := getReplicaNamedEndpoint(data, endpointName) + if err != nil { + log.Warnf("No names endpoint of %s for replica %s in endpointData: %s. Error: %v", endpointName, id, data.Address, err) return "" } return endpoint } +func getReplicaNamedEndpoint(replicaData *sf.ReplicaItemBase, endpointName string) (string, error) { + endpoints, err := decodeEndpointData(replicaData.Address) + if err != nil { + return "", err + } + + endpoint, exists := endpoints[endpointName] + if !exists { + return "", errors.New("endpoint doesn't exist") + } + return endpoint, nil +} + +func doesAppParamContain(app sf.ApplicationItem, key, shouldContain string) bool { + value := getApplicationParameter(app, key) + return strings.Contains(value, shouldContain) +} + +func getApplicationParameter(app sf.ApplicationItem, key string) string { + for _, param := range app.Parameters { + if param.Key == key { + return param.Value + } + } + log.Errorf("Parameter %s doesn't exist in app %s", key, app.Name) + return "" +} + func getClusterServices(sfClient sfClient) ([]ServiceItemExtended, error) { apps, err := sfClient.GetApplications() if err != nil { @@ -236,7 +272,7 @@ func getValidInstances(sfClient sfClient, app sf.ApplicationItem, service sf.Ser return validInstances } -func getServicesWithLabelValueMap(services []ServiceItemExtended, key string) map[string][]ServiceItemExtended { +func getServices(services []ServiceItemExtended, key string) map[string][]ServiceItemExtended { result := map[string][]ServiceItemExtended{} for _, service := range services { if value, exists := service.Labels[key]; exists { @@ -250,7 +286,7 @@ func getServicesWithLabelValueMap(services []ServiceItemExtended, key string) ma return result } -func getServicesWithLabelValue(services []ServiceItemExtended, key, expectedValue string) []ServiceItemExtended { +func filterServicesByLabelValue(services []ServiceItemExtended, key, expectedValue string) []ServiceItemExtended { var srvWithLabel []ServiceItemExtended for _, service := range services { value, exists := service.Labels[key] @@ -261,25 +297,6 @@ func getServicesWithLabelValue(services []ServiceItemExtended, key, expectedValu return srvWithLabel } -func getServiceLabelValueWithDefault(service ServiceItemExtended, key, defaultValue string) string { - value, exists := service.Labels[key] - - if !exists { - return defaultValue - } - return value -} - -func getServiceLabelsWithPrefix(service ServiceItemExtended, prefix string) map[string]string { - results := make(map[string]string) - for k, v := range service.Labels { - if strings.HasPrefix(k, prefix) { - results[k] = v - } - } - return results -} - func isPrimary(instance replicaInstance) bool { _, data := instance.GetReplicaData() return data.ReplicaRole == "Primary" @@ -290,7 +307,7 @@ func isHealthy(instanceData *sf.ReplicaItemBase) bool { } func hasHTTPEndpoint(instanceData *sf.ReplicaItemBase) bool { - _, err := getDefaultEndpoint(instanceData.Address) + _, err := getReplicaDefaultEndpoint(instanceData) return err == nil } @@ -314,37 +331,6 @@ func decodeEndpointData(endpointData string) (map[string]string, error) { return endpoints, nil } -func getDefaultEndpoint(endpointData string) (string, error) { - endpoints, err := decodeEndpointData(endpointData) - if err != nil { - return "", err - } - - var defaultHTTPEndpointExists bool - var defaultHTTPEndpoint string - for _, v := range endpoints { - if strings.Contains(v, "http") { - defaultHTTPEndpoint = v - defaultHTTPEndpointExists = true - break - } - } - - if !defaultHTTPEndpointExists { - return "", errors.New("no default endpoint found") - } - return defaultHTTPEndpoint, nil -} - -func getNamedEndpoint(endpointData string, endpointName string) (string, error) { - endpoints, err := decodeEndpointData(endpointData) - if err != nil { - return "", err - } - - endpoint, exists := endpoints[endpointName] - if !exists { - return "", errors.New("endpoint doesn't exist") - } - return endpoint, nil +func getBackendName(service ServiceItemExtended, partition PartitionItemExtended) string { + return provider.Normalize(service.Name + partition.PartitionInformation.ID) } diff --git a/vendor/github.com/containous/traefik-extra-service-fabric/servicefabric_tmpl.go b/vendor/github.com/containous/traefik-extra-service-fabric/servicefabric_tmpl.go index d7af11912..74be5033d 100644 --- a/vendor/github.com/containous/traefik-extra-service-fabric/servicefabric_tmpl.go +++ b/vendor/github.com/containous/traefik-extra-service-fabric/servicefabric_tmpl.go @@ -1,8 +1,8 @@ package servicefabric const tmpl = ` +{{$groupedServiceMap := getServices .Services "backend.group.name"}} [backends] - {{$groupedServiceMap := getServicesWithLabelValueMap .Services "backend.group.name"}} {{range $aggName, $aggServices := $groupedServiceMap }} [backends."{{$aggName}}"] {{range $service := $aggServices}} @@ -10,7 +10,7 @@ const tmpl = ` {{range $instance := $partition.Instances}} [backends."{{$aggName}}".servers."{{$service.ID}}-{{$instance.ID}}"] url = "{{getDefaultEndpoint $instance}}" - weight = {{getServiceLabelValueWithDefault $service "backend.group.weight" "1"}} + weight = {{getLabelValue $service "backend.group.weight" "1"}} {{end}} {{end}} {{end}} @@ -20,45 +20,45 @@ const tmpl = ` {{if eq $partition.ServiceKind "Stateless"}} [backends."{{$service.Name}}"] [backends."{{$service.Name}}".LoadBalancer] - {{if hasServiceLabel $service "backend.loadbalancer.method"}} - method = "{{getServiceLabelValue $service "backend.loadbalancer.method" }}" + {{if hasLabel $service "backend.loadbalancer.method"}} + method = "{{getLabelValue $service "backend.loadbalancer.method" "" }}" {{else}} method = "drr" {{end}} - {{if hasServiceLabel $service "backend.healthcheck"}} + {{if hasLabel $service "backend.healthcheck"}} [backends."{{$service.Name}}".healthcheck] - path = "{{getServiceLabelValue $service "backend.healthcheck"}}" - interval = "{{getServiceLabelValueWithDefault $service "backend.healthcheck.interval" "10s"}}" + path = "{{getLabelValue $service "backend.healthcheck" ""}}" + interval = "{{getLabelValue $service "backend.healthcheck.interval" "10s"}}" {{end}} - {{if hasServiceLabel $service "backend.loadbalancer.stickiness"}} + {{if hasLabel $service "backend.loadbalancer.stickiness"}} [backends."{{$service.Name}}".LoadBalancer.stickiness] {{end}} - {{if hasServiceLabel $service "backend.circuitbreaker"}} + {{if hasLabel $service "backend.circuitbreaker"}} [backends."{{$service.Name}}".circuitbreaker] - expression = "{{getServiceLabelValue $service "backend.circuitbreaker"}}" + expression = "{{getLabelValue $service "backend.circuitbreaker" ""}}" {{end}} - {{if hasServiceLabel $service "backend.maxconn.amount"}} + {{if hasLabel $service "backend.maxconn.amount"}} [backends."{{$service.Name}}".maxconn] - amount = {{getServiceLabelValue $service "backend.maxconn.amount"}} - {{if hasServiceLabel $service "backend.maxconn.extractorfunc"}} - extractorfunc = "{{getServiceLabelValue $service "backend.maxconn.extractorfunc"}}" + amount = {{getLabelValue $service "backend.maxconn.amount" ""}} + {{if hasLabel $service "backend.maxconn.extractorfunc"}} + extractorfunc = "{{getLabelValue $service "backend.maxconn.extractorfunc" ""}}" {{end}} {{end}} {{range $instance := $partition.Instances}} [backends."{{$service.Name}}".servers."{{$instance.ID}}"] url = "{{getDefaultEndpoint $instance}}" - weight = {{getServiceLabelValueWithDefault $service "backend.weight" "1"}} + weight = {{getLabelValue $service "backend.weight" "1"}} {{end}} {{else if eq $partition.ServiceKind "Stateful"}} {{range $replica := $partition.Replicas}} {{if isPrimary $replica}} - {{$backendName := (print $service.Name $partition.PartitionInformation.ID)}} + {{$backendName := getBackendName $service.Name $partition}} [backends."{{$backendName}}".servers."{{$replica.ID}}"] url = "{{getDefaultEndpoint $replica}}" weight = 1 @@ -81,11 +81,11 @@ const tmpl = ` [frontends."{{$groupName}}"] backend = "{{$groupName}}" - {{if hasServiceLabel $service "frontend.priority"}} + {{if hasLabel $service "frontend.priority"}} priority = 100 {{end}} - {{range $key, $value := getServiceLabelsWithPrefix $service "frontend.rule"}} + {{range $key, $value := getLabelsWithPrefix $service "frontend.rule"}} [frontends."{{$groupName}}".routes."{{$key}}"] rule = "{{$value}}" {{end}} @@ -97,27 +97,27 @@ const tmpl = ` [frontends."{{$service.Name}}"] backend = "{{$service.Name}}" - {{if hasServiceLabel $service "frontend.passHostHeader"}} - passHostHeader = {{getServiceLabelValue $service "frontend.passHostHeader" }} + {{if hasLabel $service "frontend.passHostHeader"}} + passHostHeader = {{getLabelValue $service "frontend.passHostHeader" ""}} {{end}} - {{if hasServiceLabel $service "frontend.whitelistSourceRange"}} - whitelistSourceRange = {{getServiceLabelValue $service "frontend.whitelistSourceRange" }} + {{if hasLabel $service "frontend.whitelistSourceRange"}} + whitelistSourceRange = {{getLabelValue $service "frontend.whitelistSourceRange" ""}} {{end}} - {{if hasServiceLabel $service "frontend.priority"}} - priority = {{getServiceLabelValue $service "frontend.priority"}} + {{if hasLabel $service "frontend.priority"}} + priority = {{getLabelValue $service "frontend.priority" ""}} {{end}} - {{if hasServiceLabel $service "frontend.basicAuth"}} - basicAuth = {{getServiceLabelValue $service "frontend.basicAuth"}} + {{if hasLabel $service "frontend.basicAuth"}} + basicAuth = {{getLabelValue $service "frontend.basicAuth" ""}} {{end}} - {{if hasServiceLabel $service "frontend.entryPoints"}} - entryPoints = {{getServiceLabelValue $service "frontend.entryPoints"}} + {{if hasLabel $service "frontend.entryPoints"}} + entryPoints = {{getLabelValue $service "frontend.entryPoints" ""}} {{end}} - {{range $key, $value := getServiceLabelsWithPrefix $service "frontend.rule"}} + {{range $key, $value := getLabelsWithPrefix $service "frontend.rule"}} [frontends."{{$service.Name}}".routes."{{$key}}"] rule = "{{$value}}" {{end}} @@ -126,11 +126,11 @@ const tmpl = ` {{range $partition := $service.Partitions}} {{$partitionId := $partition.PartitionInformation.ID}} - {{if hasServiceLabel $service "frontend.rule"}} + {{if hasLabel $service "frontend.rule"}} [frontends."{{$service.Name}}/{{$partitionId}}"] - backend = "{{$service.Name}}/{{$partitionId}}" + backend = "{{getBackendName $service.Name $partition}}" [frontends."{{$service.Name}}/{{$partitionId}}".routes.default] - rule = {{getServiceLabelValue $service "frontend.rule.partition.$partitionId"}} + rule = {{getLabelValue $service "frontend.rule.partition.$partitionId" ""}} {{end}} {{end}} diff --git a/vendor/github.com/jjcollinge/servicefabric/servicefabric.go b/vendor/github.com/jjcollinge/servicefabric/servicefabric.go index a46b3ef56..7500ee658 100644 --- a/vendor/github.com/jjcollinge/servicefabric/servicefabric.go +++ b/vendor/github.com/jjcollinge/servicefabric/servicefabric.go @@ -12,6 +12,7 @@ import ( "strings" ) +// DefaultAPIVersion is a default Service Fabric REST API version const DefaultAPIVersion = "3.0" // Client for Service Fabric. From bddad57a7bbd92e6e91db1e05f7376dbec3cb695 Mon Sep 17 00:00:00 2001 From: Kevin Risden Date: Thu, 14 Dec 2017 20:50:07 -0600 Subject: [PATCH 08/11] Fix RawPath handling in addPrefix --- middlewares/addPrefix.go | 3 ++ middlewares/addPrefix_test.go | 61 +++++++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/middlewares/addPrefix.go b/middlewares/addPrefix.go index 467ca7cf3..306903ae2 100644 --- a/middlewares/addPrefix.go +++ b/middlewares/addPrefix.go @@ -12,6 +12,9 @@ type AddPrefix struct { func (s *AddPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) { r.URL.Path = s.Prefix + r.URL.Path + if r.URL.RawPath != "" { + r.URL.RawPath = s.Prefix + r.URL.RawPath + } r.RequestURI = r.URL.RequestURI() s.Handler.ServeHTTP(w, r) } diff --git a/middlewares/addPrefix_test.go b/middlewares/addPrefix_test.go index a3af7a7a4..53a1b84c0 100644 --- a/middlewares/addPrefix_test.go +++ b/middlewares/addPrefix_test.go @@ -9,21 +9,56 @@ import ( ) func TestAddPrefix(t *testing.T) { - - path := "/bar" - prefix := "/foo" - - var expectedPath string - handler := &AddPrefix{ - Prefix: prefix, - Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - expectedPath = r.URL.Path - }), + tests := []struct { + desc string + prefix string + path string + expectedPath string + expectedRawPath string + }{ + { + desc: "regular path", + prefix: "/a", + path: "/b", + expectedPath: "/a/b", + }, + { + desc: "raw path is supported", + prefix: "/a", + path: "/b%2Fc", + expectedPath: "/a/b/c", + expectedRawPath: "/a/b%2Fc", + }, } - req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost"+path, nil) + for _, test := range tests { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() - handler.ServeHTTP(nil, req) + var actualPath, actualRawPath, requestURI string + handler := &AddPrefix{ + Prefix: test.prefix, + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + actualPath = r.URL.Path + actualRawPath = r.URL.RawPath + requestURI = r.RequestURI + }), + } - assert.Equal(t, expectedPath, "/foo/bar", "Unexpected path.") + req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost"+test.path, nil) + + handler.ServeHTTP(nil, req) + + assert.Equal(t, test.expectedPath, actualPath, "Unexpected path.") + assert.Equal(t, test.expectedRawPath, actualRawPath, "Unexpected raw path.") + + expectedURI := test.expectedPath + if test.expectedRawPath != "" { + // go HTTP uses the raw path when existent in the RequestURI + expectedURI = test.expectedRawPath + } + assert.Equal(t, expectedURI, requestURI, "Unexpected request URI.") + }) + } } From 7ecd6d20ba3caa8333e2786a6e67b5ee992e33aa Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 15 Dec 2017 11:48:03 +0100 Subject: [PATCH 09/11] Support regex redirect by frontend --- autogen/gentemplates/gen.go | 38 +++++- .../anonymize/anonymize_config_test.go | 4 +- configuration/configuration.go | 17 +-- configuration/configuration_test.go | 5 +- docs/configuration/backends/docker.md | 77 ++++++------ docs/configuration/backends/kubernetes.md | 10 +- docs/configuration/backends/rancher.md | 34 ++--- provider/docker/docker.go | 36 +++++- provider/docker/docker_test.go | 15 ++- provider/docker/service_test.go | 40 +++--- provider/docker/swarm_test.go | 16 +-- provider/kubernetes/kubernetes.go | 23 +++- provider/kubernetes/kubernetes_test.go | 15 ++- provider/rancher/rancher.go | 51 ++++++-- provider/rancher/rancher_test.go | 117 +++++++++++------- server/server.go | 22 ++-- server/server_test.go | 25 +++- templates/docker.tmpl | 20 ++- templates/kubernetes.tmpl | 9 +- templates/rancher.tmpl | 9 +- types/common_label.go | 8 +- types/types.go | 9 +- 22 files changed, 405 insertions(+), 195 deletions(-) diff --git a/autogen/gentemplates/gen.go b/autogen/gentemplates/gen.go index d0bac7c6c..35c00fae6 100644 --- a/autogen/gentemplates/gen.go +++ b/autogen/gentemplates/gen.go @@ -172,7 +172,6 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}} [frontends."frontend-{{getServiceBackend $container $serviceName}}"] backend = "backend-{{getServiceBackend $container $serviceName}}" passHostHeader = {{getServicePassHostHeader $container $serviceName}} - redirect = "{{getServiceRedirect $container $serviceName}}" {{if getWhitelistSourceRange $container}} whitelistSourceRange = [{{range getWhitelistSourceRange $container}} "{{.}}", @@ -185,14 +184,21 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}} basicAuth = [{{range getServiceBasicAuth $container $serviceName}} "{{.}}", {{end}}] - [frontends."frontend-{{getServiceBackend $container $serviceName}}".routes."service-{{$serviceName | replace "/" "" | replace "." "-"}}"] + + {{if hasServiceRedirect $container $serviceName}} + [frontends."frontend-{{getServiceBackend $container $serviceName}}".redirect] + entryPoint = "{{getServiceRedirectEntryPoint $container $serviceName}}" + regex = "{{getServiceRedirectRegex $container $serviceName}}" + replacement = "{{getServiceRedirectReplacement $container $serviceName}}" + {{end}} + + [frontends."frontend-{{getServiceBackend $container $serviceName}}".routes."service-{{$serviceName | replace "/" "" | replace "." "-"}}"] rule = "{{getServiceFrontendRule $container $serviceName}}" {{end}} {{else}} [frontends."frontend-{{$frontend}}"] backend = "backend-{{getBackend $container}}" passHostHeader = {{getPassHostHeader $container}} - redirect = "{{getRedirect $container}}" {{if getWhitelistSourceRange $container}} whitelistSourceRange = [{{range getWhitelistSourceRange $container}} "{{.}}", @@ -205,6 +211,14 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}} basicAuth = [{{range getBasicAuth $container}} "{{.}}", {{end}}] + + {{if hasRedirect $container}} + [frontends."frontend-{{$frontend}}".redirect] + entryPoint = "{{getRedirectEntryPoint $container}}" + regex = "{{getRedirectRegex $container}}" + replacement = "{{getRedirectReplacement $container}}" + {{end}} + [frontends."frontend-{{$frontend}}".headers] {{if hasSSLRedirectHeaders $container}} SSLRedirect = {{getSSLRedirectHeaders $container}} @@ -414,13 +428,20 @@ var _templatesKubernetesTmpl = []byte(`[backends]{{range $backendName, $backend backend = "{{$frontend.Backend}}" priority = {{$frontend.Priority}} passHostHeader = {{$frontend.PassHostHeader}} - redirect = "{{$frontend.Redirect}}" basicAuth = [{{range $frontend.BasicAuth}} "{{.}}", {{end}}] whitelistSourceRange = [{{range $frontend.WhitelistSourceRange}} "{{.}}", {{end}}] + + {{if $frontend.Redirect}} + [frontends."{{$frontendName}}".redirect] + entryPoint = "{{$frontend.RedirectEntryPoint}}" + regex = "{{$frontend.RedirectRegex}}" + replacement = "{{$frontend.RedirectReplacement}}" + {{end}} + [frontends."{{$frontendName}}".headers] SSLRedirect = {{$frontend.Headers.SSLRedirect}} SSLTemporaryRedirect = {{$frontend.Headers.SSLTemporaryRedirect}} @@ -752,13 +773,20 @@ var _templatesRancherTmpl = []byte(`{{$backendServers := .Backends}} backend = "backend-{{getBackend $service}}" passHostHeader = {{getPassHostHeader $service}} priority = {{getPriority $service}} - redirect = "{{getRedirect $service}}" entryPoints = [{{range getEntryPoints $service}} "{{.}}", {{end}}] basicAuth = [{{range getBasicAuth $service}} "{{.}}", {{end}}] + + {{if hasRedirect $service}} + [frontends."frontend-{{$frontendName}}".redirect] + entryPoint = "{{getRedirectEntryPoint $service}}" + regex = "{{getRedirectRegex $service}}" + replacement = "{{getRedirectReplacement $service}}" + {{end}} + [frontends."frontend-{{$frontendName}}".routes."route-frontend-{{$frontendName}}"] rule = "{{getFrontendRule $service}}" {{end}} diff --git a/cmd/traefik/anonymize/anonymize_config_test.go b/cmd/traefik/anonymize/anonymize_config_test.go index cf27291d9..abfd4732a 100644 --- a/cmd/traefik/anonymize/anonymize_config_test.go +++ b/cmd/traefik/anonymize/anonymize_config_test.go @@ -57,7 +57,7 @@ func TestDo_globalConfiguration(t *testing.T) { Optional: false, }, }, - Redirect: &configuration.Redirect{ + Redirect: &types.Redirect{ Replacement: "foo Replacement", Regex: "foo Regex", EntryPoint: "foo EntryPoint", @@ -103,7 +103,7 @@ func TestDo_globalConfiguration(t *testing.T) { Optional: false, }, }, - Redirect: &configuration.Redirect{ + Redirect: &types.Redirect{ Replacement: "fii Replacement", Regex: "fii Regex", EntryPoint: "fii EntryPoint", diff --git a/configuration/configuration.go b/configuration/configuration.go index 199d5ee0d..bc9a1816a 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -317,9 +317,9 @@ func (ep *EntryPoints) Set(value string) error { Optional: optional, } } - var redirect *Redirect + var redirect *types.Redirect if len(result["redirect_entrypoint"]) > 0 || len(result["redirect_regex"]) > 0 || len(result["redirect_replacement"]) > 0 { - redirect = &Redirect{ + redirect = &types.Redirect{ EntryPoint: result["redirect_entrypoint"], Regex: result["redirect_regex"], Replacement: result["redirect_replacement"], @@ -422,22 +422,15 @@ func (ep *EntryPoints) Type() string { type EntryPoint struct { Network string Address string - TLS *tls.TLS `export:"true"` - Redirect *Redirect `export:"true"` - Auth *types.Auth `export:"true"` + TLS *tls.TLS `export:"true"` + Redirect *types.Redirect `export:"true"` + Auth *types.Auth `export:"true"` WhitelistSourceRange []string Compress bool `export:"true"` ProxyProtocol *ProxyProtocol `export:"true"` ForwardedHeaders *ForwardedHeaders `export:"true"` } -// Redirect configures a redirection of an entry point to another, or to an URL -type Redirect struct { - EntryPoint string - Regex string - Replacement string -} - // Retry contains request retry config type Retry struct { Attempts int `description:"Number of attempts" export:"true"` diff --git a/configuration/configuration_test.go b/configuration/configuration_test.go index 9d2f4783c..1acb639c6 100644 --- a/configuration/configuration_test.go +++ b/configuration/configuration_test.go @@ -8,6 +8,7 @@ import ( "github.com/containous/traefik/provider" "github.com/containous/traefik/provider/file" "github.com/containous/traefik/tls" + "github.com/containous/traefik/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -138,7 +139,7 @@ func TestEntryPoints_Set(t *testing.T) { expectedEntryPointName: "foo", expectedEntryPoint: &EntryPoint{ Address: ":8000", - Redirect: &Redirect{ + Redirect: &types.Redirect{ EntryPoint: "RedirectEntryPoint", Regex: "RedirectRegex", Replacement: "RedirectReplacement", @@ -171,7 +172,7 @@ func TestEntryPoints_Set(t *testing.T) { expectedEntryPointName: "foo", expectedEntryPoint: &EntryPoint{ Address: ":8000", - Redirect: &Redirect{ + Redirect: &types.Redirect{ EntryPoint: "RedirectEntryPoint", Regex: "RedirectRegex", Replacement: "RedirectReplacement", diff --git a/docs/configuration/backends/docker.md b/docs/configuration/backends/docker.md index 2dfd9fdd7..bad8ad59d 100644 --- a/docs/configuration/backends/docker.md +++ b/docs/configuration/backends/docker.md @@ -149,29 +149,33 @@ To enable constraints see [backend-specific constraints section](/configuration/ Labels can be used on containers to override default behaviour. -| Label | Description | -|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `traefik.backend=foo` | Give the name `foo` to the generated backend for this container. | -| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect. | -| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect. | -| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm | -| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions | -| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions | -| `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) | -| `traefik.backend.loadbalancer.swarm=true` | Use Swarm's inbuilt load balancer (only relevant under Swarm Mode). | -| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend | -| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. | -| `traefik.protocol=https` | Override the default `http` protocol | -| `traefik.weight=10` | Assign this weight to the container | -| `traefik.enable=false` | Disable this container in Træfik | -| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{containerName}.{domain}` or `Host:{service}.{project_name}.{domain}` if you are using `docker-compose`. | -| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. | -| `traefik.frontend.priority=10` | Override default frontend priority | -| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints` | -| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` | -| `traefik.frontend.whitelistSourceRange:RANGE` | List of IP-Ranges which are allowed to access. An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. | -| `traefik.docker.network` | Set the docker network to use for connections to this container. If a container is linked to several networks, be sure to set the proper network name (you can check with `docker inspect `) otherwise it will randomly pick one (depending on how docker is returning them). For instance when deploying docker `stack` from compose files, the compose defined networks will be prefixed with the `stack` name. | -| `traefik.frontend.redirect=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) | +| Label | Description | +|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `traefik.backend=foo` | Give the name `foo` to the generated backend for this container. | +| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect. | +| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect. | +| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm | +| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions | +| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions | +| `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) | +| `traefik.backend.loadbalancer.swarm=true` | Use Swarm's inbuilt load balancer (only relevant under Swarm Mode). | +| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend | +| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. | +| `traefik.protocol=https` | Override the default `http` protocol | +| `traefik.weight=10` | Assign this weight to the container | +| `traefik.enable=false` | Disable this container in Træfik | +| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{containerName}.{domain}` or `Host:{service}.{project_name}.{domain}` if you are using `docker-compose`. | +| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. | +| `traefik.frontend.priority=10` | Override default frontend priority | +| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints` | +| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` | +| `traefik.frontend.whitelistSourceRange:RANGE` | List of IP-Ranges which are allowed to access. An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. | +| `traefik.docker.network` | Set the docker network to use for connections to this container. If a container is linked to several networks, be sure to set the proper network name (you can check with `docker inspect `) otherwise it will randomly pick one (depending on how docker is returning them). For instance when deploying docker `stack` from compose files, the compose defined networks will be prefixed with the `stack` name. | +| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) | +| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend. Must be set with `traefik.frontend.redirect.replacement`. | +| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend. Must be set with `traefik.frontend.redirect.regex`. | + + #### Security Headers @@ -202,18 +206,21 @@ Labels can be used on containers to override default behaviour. Services labels can be used for overriding default behaviour -| Label | Description | -|---------------------------------------------------|--------------------------------------------------------------------------------------------------| -| `traefik..port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the service labels could be used. | -| `traefik..protocol` | Overrides `traefik.protocol`. | -| `traefik..weight` | Assign this service weight. Overrides `traefik.weight`. | -| `traefik..frontend.backend=BACKEND` | Assign this service frontend to `BACKEND`. Default is to assign to the service backend. | -| `traefik..frontend.entryPoints` | Overrides `traefik.frontend.entrypoints` | -| `traefik..frontend.auth.basic` | Sets a Basic Auth for that frontend | -| `traefik..frontend.passHostHeader` | Overrides `traefik.frontend.passHostHeader`. | -| `traefik..frontend.priority` | Overrides `traefik.frontend.priority`. | -| `traefik..frontend.rule` | Overrides `traefik.frontend.rule`. | -| `traefik..frontend.redirect` | Overrides `traefik.frontend.redirect`. | +| Label | Description | +|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------| +| `traefik..port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the service labels could be used. | +| `traefik..protocol` | Overrides `traefik.protocol`. | +| `traefik..weight` | Assign this service weight. Overrides `traefik.weight`. | +| `traefik..frontend.backend=BACKEND` | Assign this service frontend to `BACKEND`. Default is to assign to the service backend. | +| `traefik..frontend.entryPoints` | Overrides `traefik.frontend.entrypoints` | +| `traefik..frontend.auth.basic` | Sets a Basic Auth for that frontend | +| `traefik..frontend.passHostHeader` | Overrides `traefik.frontend.passHostHeader`. | +| `traefik..frontend.priority` | Overrides `traefik.frontend.priority`. | +| `traefik..frontend.rule` | Overrides `traefik.frontend.rule`. | +| `traefik..frontend.redirect` | Overrides `traefik.frontend.redirect`. | +| `traefik..frontend.redirect.entryPoint=https` | Overrides `traefik.frontend.redirect.entryPoint`. | +| `traefik..frontend.redirect.regex=^http://localhost/(.*)` | Overrides `traefik.frontend.redirect.regex`. | +| `traefik..frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. | !!! note diff --git a/docs/configuration/backends/kubernetes.md b/docs/configuration/backends/kubernetes.md index 069054602..02e0f4617 100644 --- a/docs/configuration/backends/kubernetes.md +++ b/docs/configuration/backends/kubernetes.md @@ -102,13 +102,21 @@ Annotations can be used on containers to override default behaviour for the whol Override the default frontend rule type. Default: `PathPrefix`. - `traefik.frontend.priority: "3"` Override the default frontend rule priority. -- `traefik.frontend.redirect: https`: +- `traefik.frontend.redirect.entryPoint: https`: Enables Redirect to another entryPoint for that frontend (e.g. HTTPS). +- `traefik.frontend.redirect.regex: ^http://localhost/(.*)`: + Redirect to another URL for that frontend. Must be set with `traefik.frontend.redirect.replacement`. +- `traefik.frontend.redirect.replacement: http://mydomain/$1`: + Redirect to another URL for that frontend. Must be set with `traefik.frontend.redirect.regex`. - `traefik.frontend.entryPoints: http,https` Override the default frontend endpoints. - `traefik.frontend.passTLSCert: true` Override the default frontend PassTLSCert value. Default: `false`. +!!! note + Please note that `traefik.frontend.redirect.regex` and `traefik.frontend.redirect.replacement` do not have to be set if `traefik.frontend.redirect.entryPoint` is defined for the redirection (they will not be used in this case). + + Annotations can be used on the Kubernetes service to override default behaviour: - `traefik.backend.loadbalancer.method=drr` diff --git a/docs/configuration/backends/rancher.md b/docs/configuration/backends/rancher.md index 1acd56800..d36ef33f8 100644 --- a/docs/configuration/backends/rancher.md +++ b/docs/configuration/backends/rancher.md @@ -120,19 +120,21 @@ secretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" Labels can be used on task containers to override default behaviour: -| Label | Description | -|-----------------------------------------------------------------------|------------------------------------------------------------------------------------------| -| `traefik.protocol=https` | Override the default `http` protocol | -| `traefik.weight=10` | Assign this weight to the container | -| `traefik.enable=false` | Disable this container in Træfik | -| `traefik.frontend.rule=Host:test.traefik.io` | Override the default frontend rule (Default: `Host:{containerName}.{domain}`). | -| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. | -| `traefik.frontend.priority=10` | Override default frontend priority | -| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. | -| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash`. | -| `traefik.frontend.redirect=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) | -| `traefik.backend.circuitbreaker.expression=NetworkErrorRatio() > 0.5` | Create a [circuit breaker](/basics/#backends) to be used against the backend | -| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm | -| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions | -| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions | -| `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) | \ No newline at end of file +| Label | Description | +|-----------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------| +| `traefik.protocol=https` | Override the default `http` protocol | +| `traefik.weight=10` | Assign this weight to the container | +| `traefik.enable=false` | Disable this container in Træfik | +| `traefik.frontend.rule=Host:test.traefik.io` | Override the default frontend rule (Default: `Host:{containerName}.{domain}`). | +| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. | +| `traefik.frontend.priority=10` | Override default frontend priority | +| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. | +| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash`. | +| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) | +| `traefik.frontend.redirect.regex: ^http://localhost/(.*)` | Redirect to another URL for that frontend.
Must be set with `traefik.frontend.redirect.replacement`. | +| `traefik.frontend.redirect.replacement: http://mydomain/$1` | Redirect to another URL for that frontend.
Must be set with `traefik.frontend.redirect.regex`. | +| `traefik.backend.circuitbreaker.expression=NetworkErrorRatio() > 0.5` | Create a [circuit breaker](/basics/#backends) to be used against the backend | +| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm | +| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions | +| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions | +| `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) | \ No newline at end of file diff --git a/provider/docker/docker.go b/provider/docker/docker.go index 721953425..9300c3a9c 100644 --- a/provider/docker/docker.go +++ b/provider/docker/docker.go @@ -42,7 +42,7 @@ const ( defaultPassHostHeader = "true" defaultFrontendPriority = "0" defaultCircuitBreakerExpression = "NetworkErrorRatio() > 1" - defaultFrontendRedirect = "" + defaultFrontendRedirectEntryPoint = "" defaultBackendLoadBalancerMethod = "wrr" defaultBackendMaxconnExtractorfunc = "request.host" ) @@ -276,7 +276,6 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con "getEntryPoints": getFuncSliceStringLabel(types.LabelFrontendEntryPoints), "getBasicAuth": getFuncSliceStringLabel(types.LabelFrontendAuthBasic), "getFrontendRule": p.getFrontendRule, - "getRedirect": getFuncStringLabel(types.LabelFrontendRedirect, ""), "hasCircuitBreakerLabel": hasLabel(types.LabelBackendCircuitbreakerExpression), "getCircuitBreakerExpression": getFuncStringLabel(types.LabelBackendCircuitbreakerExpression, defaultCircuitBreakerExpression), "hasLoadBalancerLabel": hasLoadBalancerLabel, @@ -289,7 +288,6 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con "getStickinessCookieName": getFuncStringLabel(types.LabelBackendLoadbalancerStickinessCookieName, ""), "getIsBackendLBSwarm": getIsBackendLBSwarm, "getServiceBackend": getServiceBackend, - "getServiceRedirect": getFuncServiceStringLabel(types.SuffixFrontendRedirect, defaultFrontendRedirect), "getWhitelistSourceRange": getFuncSliceStringLabel(types.LabelTraefikFrontendWhitelistSourceRange), "hasRequestHeaders": hasLabel(types.LabelFrontendRequestHeaders), @@ -343,6 +341,15 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con "getServiceFrontendRule": p.getServiceFrontendRule, "getServicePassHostHeader": getFuncServiceStringLabel(types.SuffixFrontendPassHostHeader, defaultPassHostHeader), "getServicePriority": getFuncServiceStringLabel(types.SuffixFrontendPriority, defaultFrontendPriority), + + "hasRedirect": hasRedirect, + "getRedirectEntryPoint": getFuncStringLabel(types.LabelFrontendRedirectEntryPoint, defaultFrontendRedirectEntryPoint), + "getRedirectRegex": getFuncStringLabel(types.LabelFrontendRedirectRegex, ""), + "getRedirectReplacement": getFuncStringLabel(types.LabelFrontendRedirectReplacement, ""), + "hasServiceRedirect": hasServiceRedirect, + "getServiceRedirectEntryPoint": getFuncServiceStringLabel(types.SuffixFrontendRedirectEntryPoint, defaultFrontendRedirectEntryPoint), + "getServiceRedirectReplacement": getFuncServiceStringLabel(types.SuffixFrontendRedirectReplacement, ""), + "getServiceRedirectRegex": getFuncServiceStringLabel(types.SuffixFrontendRedirectRegex, ""), } // filter containers filteredContainers := fun.Filter(func(container dockerData) bool { @@ -865,3 +872,26 @@ func parseTasks(task swarmtypes.Task, serviceDockerData dockerData, networkMap m } return dockerData } + +// TODO will be rewrite when merge on master +func hasServiceRedirect(container dockerData, serviceName string) bool { + serviceLabels, ok := extractServicesLabels(container.Labels)[serviceName] + if !ok || len(serviceLabels) == 0 { + return false + } + + value, ok := serviceLabels[types.SuffixFrontendRedirectEntryPoint] + frep := ok && len(value) > 0 + value, ok = serviceLabels[types.SuffixFrontendRedirectRegex] + frrg := ok && len(value) > 0 + value, ok = serviceLabels[types.SuffixFrontendRedirectReplacement] + frrp := ok && len(value) > 0 + + return frep || frrg && frrp +} + +// TODO will be rewrite when merge on master +func hasRedirect(container dockerData) bool { + return hasLabel(types.LabelFrontendRedirectEntryPoint)(container) || + hasLabel(types.LabelFrontendRedirectReplacement)(container) && hasLabel(types.LabelFrontendRedirectRegex)(container) +} diff --git a/provider/docker/docker_test.go b/provider/docker/docker_test.go index 101301db3..f45ecb14b 100644 --- a/provider/docker/docker_test.go +++ b/provider/docker/docker_test.go @@ -712,7 +712,6 @@ func TestDockerLoadDockerConfig(t *testing.T) { PassHostHeader: true, EntryPoints: []string{}, BasicAuth: []string{}, - Redirect: "", Routes: map[string]types.Route{ "route-frontend-Host-test-docker-localhost-0": { Rule: "Host:test.docker.localhost", @@ -737,10 +736,10 @@ func TestDockerLoadDockerConfig(t *testing.T) { containerJSON( name("test1"), labels(map[string]string{ - types.LabelBackend: "foobar", - types.LabelFrontendEntryPoints: "http,https", - types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - types.LabelFrontendRedirect: "https", + types.LabelBackend: "foobar", + types.LabelFrontendEntryPoints: "http,https", + types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + types.LabelFrontendRedirectEntryPoint: "https", }), ports(nat.PortMap{ "80/tcp": {}, @@ -764,7 +763,9 @@ func TestDockerLoadDockerConfig(t *testing.T) { PassHostHeader: true, EntryPoints: []string{"http", "https"}, BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - Redirect: "https", + Redirect: &types.Redirect{ + EntryPoint: "https", + }, Routes: map[string]types.Route{ "route-frontend-Host-test1-docker-localhost-0": { Rule: "Host:test1.docker.localhost", @@ -776,7 +777,6 @@ func TestDockerLoadDockerConfig(t *testing.T) { PassHostHeader: true, EntryPoints: []string{}, BasicAuth: []string{}, - Redirect: "", Routes: map[string]types.Route{ "route-frontend-Host-test2-docker-localhost-1": { Rule: "Host:test2.docker.localhost", @@ -824,7 +824,6 @@ func TestDockerLoadDockerConfig(t *testing.T) { PassHostHeader: true, EntryPoints: []string{"http", "https"}, BasicAuth: []string{}, - Redirect: "", Routes: map[string]types.Route{ "route-frontend-Host-test1-docker-localhost-0": { Rule: "Host:test1.docker.localhost", diff --git a/provider/docker/service_test.go b/provider/docker/service_test.go index 08625e7f8..45657e594 100644 --- a/provider/docker/service_test.go +++ b/provider/docker/service_test.go @@ -8,6 +8,7 @@ import ( "github.com/containous/traefik/types" docker "github.com/docker/docker/api/types" "github.com/docker/go-connections/nat" + "github.com/stretchr/testify/assert" ) func TestDockerGetServicePort(t *testing.T) { @@ -136,10 +137,10 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) { containerJSON( name("foo"), labels(map[string]string{ - "traefik.service.port": "2503", - "traefik.service.frontend.entryPoints": "http,https", - "traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - "traefik.service.frontend.redirect": "https", + "traefik.service.port": "2503", + "traefik.service.frontend.entryPoints": "http,https", + "traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + "traefik.service.frontend.redirect.entryPoint": "https", }), ports(nat.PortMap{ "80/tcp": {}, @@ -153,7 +154,9 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) { PassHostHeader: true, EntryPoints: []string{"http", "https"}, BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - Redirect: "https", + Redirect: &types.Redirect{ + EntryPoint: "https", + }, Routes: map[string]types.Route{ "service-service": { Rule: "Host:foo.docker.localhost", @@ -178,16 +181,16 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) { containerJSON( name("test1"), labels(map[string]string{ - "traefik.service.port": "2503", - "traefik.service.protocol": "https", - "traefik.service.weight": "80", - "traefik.service.frontend.backend": "foobar", - "traefik.service.frontend.passHostHeader": "false", - "traefik.service.frontend.rule": "Path:/mypath", - "traefik.service.frontend.priority": "5000", - "traefik.service.frontend.entryPoints": "http,https,ws", - "traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - "traefik.service.frontend.redirect": "https", + "traefik.service.port": "2503", + "traefik.service.protocol": "https", + "traefik.service.weight": "80", + "traefik.service.frontend.backend": "foobar", + "traefik.service.frontend.passHostHeader": "false", + "traefik.service.frontend.rule": "Path:/mypath", + "traefik.service.frontend.priority": "5000", + "traefik.service.frontend.entryPoints": "http,https,ws", + "traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + "traefik.service.frontend.redirect.entryPoint": "https", }), ports(nat.PortMap{ "80/tcp": {}, @@ -214,7 +217,9 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) { Priority: 5000, EntryPoints: []string{"http", "https", "ws"}, BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - Redirect: "https", + Redirect: &types.Redirect{ + EntryPoint: "https", + }, Routes: map[string]types.Route{ "service-service": { Rule: "Path:/mypath", @@ -226,7 +231,6 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) { PassHostHeader: true, EntryPoints: []string{}, BasicAuth: []string{}, - Redirect: "", Routes: map[string]types.Route{ "service-anotherservice": { Rule: "Path:/anotherpath", @@ -274,9 +278,11 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) { actualConfig := provider.loadDockerConfig(dockerDataList) // Compare backends + assert.EqualValues(t, test.expectedBackends, actualConfig.Backends) if !reflect.DeepEqual(actualConfig.Backends, test.expectedBackends) { t.Fatalf("expected %#v, got %#v", test.expectedBackends, actualConfig.Backends) } + assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends) if !reflect.DeepEqual(actualConfig.Frontends, test.expectedFrontends) { t.Fatalf("expected %#v, got %#v", test.expectedFrontends, actualConfig.Frontends) } diff --git a/provider/docker/swarm_test.go b/provider/docker/swarm_test.go index 32f95cf74..2ff0aaee9 100644 --- a/provider/docker/swarm_test.go +++ b/provider/docker/swarm_test.go @@ -516,7 +516,6 @@ func TestSwarmLoadDockerConfig(t *testing.T) { PassHostHeader: true, EntryPoints: []string{}, BasicAuth: []string{}, - Redirect: "", Routes: map[string]types.Route{ "route-frontend-Host-test-docker-localhost-0": { Rule: "Host:test.docker.localhost", @@ -547,11 +546,11 @@ func TestSwarmLoadDockerConfig(t *testing.T) { swarmService( serviceName("test1"), serviceLabels(map[string]string{ - types.LabelPort: "80", - types.LabelBackend: "foobar", - types.LabelFrontendEntryPoints: "http,https", - types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - types.LabelFrontendRedirect: "https", + types.LabelPort: "80", + types.LabelBackend: "foobar", + types.LabelFrontendEntryPoints: "http,https", + types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + types.LabelFrontendRedirectEntryPoint: "https", }), withEndpointSpec(modeVIP), withEndpoint(virtualIP("1", "127.0.0.1/24")), @@ -572,7 +571,9 @@ func TestSwarmLoadDockerConfig(t *testing.T) { PassHostHeader: true, EntryPoints: []string{"http", "https"}, BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - Redirect: "https", + Redirect: &types.Redirect{ + EntryPoint: "https", + }, Routes: map[string]types.Route{ "route-frontend-Host-test1-docker-localhost-0": { Rule: "Host:test1.docker.localhost", @@ -584,7 +585,6 @@ func TestSwarmLoadDockerConfig(t *testing.T) { PassHostHeader: true, EntryPoints: []string{}, BasicAuth: []string{}, - Redirect: "", Routes: map[string]types.Route{ "route-frontend-Host-test2-docker-localhost-1": { Rule: "Host:test2.docker.localhost", diff --git a/provider/kubernetes/kubernetes.go b/provider/kubernetes/kubernetes.go index 804510d73..cd577ce74 100644 --- a/provider/kubernetes/kubernetes.go +++ b/provider/kubernetes/kubernetes.go @@ -199,8 +199,6 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error) whitelistSourceRange := getSliceAnnotation(i, annotationKubernetesWhitelistSourceRange) - entryPointRedirect, _ := i.Annotations[types.LabelFrontendRedirect] - if _, exists := templateObjects.Frontends[r.Host+pa.Path]; !exists { basicAuthCreds, err := handleBasicAuthConfig(i, k8sClient) if err != nil { @@ -241,7 +239,7 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error) Priority: priority, BasicAuth: basicAuthCreds, WhitelistSourceRange: whitelistSourceRange, - Redirect: entryPointRedirect, + Redirect: getFrontendRedirect(i), EntryPoints: entryPoints, Headers: headers, } @@ -492,3 +490,22 @@ func shouldProcessIngress(ingressClass string) bool { return false } } + +// TODO will be rewrite when merge on master +func getFrontendRedirect(i *v1beta1.Ingress) *types.Redirect { + frontendRedirectEntryPoint, ok := i.Annotations[types.LabelFrontendRedirectEntryPoint] + frep := ok && len(frontendRedirectEntryPoint) > 0 + frontendRedirectRegex, ok := i.Annotations[types.LabelFrontendRedirectRegex] + frrg := ok && len(frontendRedirectRegex) > 0 + frontendRedirectReplacement, ok := i.Annotations[types.LabelFrontendRedirectReplacement] + frrp := ok && len(frontendRedirectReplacement) > 0 + + if frep || frrg && frrp { + return &types.Redirect{ + EntryPoint: frontendRedirectEntryPoint, + Regex: frontendRedirectRegex, + Replacement: frontendRedirectReplacement, + } + } + return nil +} diff --git a/provider/kubernetes/kubernetes_test.go b/provider/kubernetes/kubernetes_test.go index df98a89b4..2aeebe4c0 100644 --- a/provider/kubernetes/kubernetes_test.go +++ b/provider/kubernetes/kubernetes_test.go @@ -8,6 +8,7 @@ import ( "github.com/containous/traefik/types" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/pkg/apis/extensions/v1beta1" "k8s.io/client-go/pkg/util/intstr" @@ -1266,8 +1267,8 @@ func TestIngressAnnotations(t *testing.T) { ObjectMeta: v1.ObjectMeta{ Namespace: "testing", Annotations: map[string]string{ - "kubernetes.io/ingress.class": "traefik", - types.LabelFrontendRedirect: "https", + "kubernetes.io/ingress.class": "traefik", + types.LabelFrontendRedirectEntryPoint: "https", }, }, Spec: v1beta1.IngressSpec{ @@ -1452,7 +1453,6 @@ func TestIngressAnnotations(t *testing.T) { Rule: "Host:foo", }, }, - Redirect: "", }, "other/stuff": { Backend: "other/stuff", @@ -1465,7 +1465,6 @@ func TestIngressAnnotations(t *testing.T) { Rule: "Host:other", }, }, - Redirect: "", }, "other/": { Backend: "other/", @@ -1505,7 +1504,6 @@ func TestIngressAnnotations(t *testing.T) { }, }, BasicAuth: []string{"myUser:myEncodedPW"}, - Redirect: "", }, "redirect/https": { Backend: "redirect/https", @@ -1518,7 +1516,9 @@ func TestIngressAnnotations(t *testing.T) { Rule: "Host:redirect", }, }, - Redirect: "https", + Redirect: &types.Redirect{ + EntryPoint: "https", + }, }, "test/whitelist-source-range": { @@ -1536,7 +1536,6 @@ func TestIngressAnnotations(t *testing.T) { Rule: "Host:test", }, }, - Redirect: "", }, "rewrite/api": { Backend: "rewrite/api", @@ -1549,7 +1548,6 @@ func TestIngressAnnotations(t *testing.T) { Rule: "Host:rewrite", }, }, - Redirect: "", }, }, } @@ -2256,6 +2254,7 @@ func TestBasicAuthInTemplate(t *testing.T) { } actual = provider.loadConfig(*actual) + require.NotNil(t, actual) got := actual.Frontends["basic/auth"].BasicAuth if !reflect.DeepEqual(got, []string{"myUser:myEncodedPW"}) { t.Fatalf("unexpected credentials: %+v", got) diff --git a/provider/rancher/rancher.go b/provider/rancher/rancher.go index 6c84b9379..4817c1e44 100644 --- a/provider/rancher/rancher.go +++ b/provider/rancher/rancher.go @@ -76,13 +76,6 @@ func (p *Provider) getBasicAuth(service rancherData) []string { return []string{} } -func (p *Provider) getRedirect(service rancherData) string { - if redirect, err := getServiceLabel(service, types.LabelFrontendRedirect); err == nil { - return redirect - } - return "" -} - func (p *Provider) getFrontendName(service rancherData) string { // Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78 return provider.Normalize(p.getFrontendRule(service)) @@ -246,7 +239,10 @@ func (p *Provider) loadRancherConfig(services []rancherData) *types.Configuratio "getSticky": p.getSticky, "hasStickinessLabel": p.hasStickinessLabel, "getStickinessCookieName": p.getStickinessCookieName, - "getRedirect": p.getRedirect, + "hasRedirect": hasRedirect, + "getRedirectEntryPoint": getRedirectEntryPoint, + "getRedirectRegex": getRedirectRegex, + "getRedirectReplacement": getRedirectReplacement, } // filter services @@ -340,3 +336,42 @@ func isServiceEnabled(service rancherData, exposedByDefault bool) bool { } return exposedByDefault } + +// TODO will be rewrite when merge on master +func hasRedirect(service rancherData) bool { + value, err := getServiceLabel(service, types.LabelFrontendRedirectEntryPoint) + frep := err == nil && len(value) > 0 + value, err = getServiceLabel(service, types.LabelFrontendRedirectRegex) + frrg := err == nil && len(value) > 0 + value, err = getServiceLabel(service, types.LabelFrontendRedirectReplacement) + frrp := err == nil && len(value) > 0 + + return frep || frrg && frrp +} + +// TODO will be rewrite when merge on master +func getRedirectEntryPoint(service rancherData) string { + value, err := getServiceLabel(service, types.LabelFrontendRedirectEntryPoint) + if err != nil || len(value) == 0 { + return "" + } + return value +} + +// TODO will be rewrite when merge on master +func getRedirectRegex(service rancherData) string { + value, err := getServiceLabel(service, types.LabelFrontendRedirectRegex) + if err != nil || len(value) == 0 { + return "" + } + return value +} + +// TODO will be rewrite when merge on master +func getRedirectReplacement(service rancherData) string { + value, err := getServiceLabel(service, types.LabelFrontendRedirectReplacement) + if err != nil || len(value) == 0 { + return "" + } + return value +} diff --git a/provider/rancher/rancher_test.go b/provider/rancher/rancher_test.go index 60f2eab12..a0da4fb8c 100644 --- a/provider/rancher/rancher_test.go +++ b/provider/rancher/rancher_test.go @@ -6,6 +6,7 @@ import ( "github.com/containous/traefik/types" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestProviderServiceFilter(t *testing.T) { @@ -529,44 +530,6 @@ func TestProviderGetPassHostHeader(t *testing.T) { } } -func TestProviderGetRedirect(t *testing.T) { - provider := &Provider{Domain: "rancher.localhost"} - - testCases := []struct { - desc string - service rancherData - expected string - }{ - { - desc: "without label", - service: rancherData{ - Name: "test-service", - }, - expected: "", - }, - { - desc: "with label", - service: rancherData{ - Name: "test-service", - Labels: map[string]string{ - types.LabelFrontendRedirect: "https", - }, - }, - expected: "https", - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := provider.getRedirect(test.service) - assert.Equal(t, test.expected, actual) - }) - } -} - func TestProviderGetLabel(t *testing.T) { testCases := []struct { desc string @@ -634,9 +597,9 @@ func TestProviderLoadRancherConfig(t *testing.T) { { Name: "test/service", Labels: map[string]string{ - types.LabelPort: "80", - types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - types.LabelFrontendRedirect: "https", + types.LabelPort: "80", + types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + types.LabelFrontendRedirectEntryPoint: "https", }, Health: "healthy", Containers: []string{"127.0.0.1"}, @@ -649,7 +612,9 @@ func TestProviderLoadRancherConfig(t *testing.T) { EntryPoints: []string{}, BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, Priority: 0, - Redirect: "https", + Redirect: &types.Redirect{ + EntryPoint: "https", + }, Routes: map[string]types.Route{ "route-frontend-Host-test-service-rancher-localhost": { Rule: "Host:test.service.rancher.localhost", @@ -679,6 +644,7 @@ func TestProviderLoadRancherConfig(t *testing.T) { actualConfig := provider.loadRancherConfig(test.services) + require.NotNil(t, actualConfig) assert.EqualValues(t, test.expectedBackends, actualConfig.Backends) assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends) }) @@ -732,3 +698,70 @@ func TestProviderHasStickinessLabel(t *testing.T) { }) } } + +func TestHasRedirect(t *testing.T) { + testCases := []struct { + desc string + service rancherData + expected bool + }{ + { + desc: "without redirect labels", + service: rancherData{ + Name: "test-service", + }, + expected: false, + }, + { + desc: "with Redirect EntryPoint label", + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + types.LabelFrontendRedirectEntryPoint: "https", + }, + }, + expected: true, + }, + { + desc: "with Redirect regex label", + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + types.LabelFrontendRedirectRegex: `(.+)`, + }, + }, + expected: false, + }, + { + desc: "with Redirect replacement label", + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + types.LabelFrontendRedirectReplacement: "$1", + }, + }, + expected: false, + }, + { + desc: "with Redirect regex & replacement labels", + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + types.LabelFrontendRedirectRegex: `(.+)`, + types.LabelFrontendRedirectReplacement: "$1", + }, + }, + expected: true, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := hasRedirect(test.service) + assert.Equal(t, test.expected, actual) + }) + } +} diff --git a/server/server.go b/server/server.go index 33e8d2b27..b9ab28535 100644 --- a/server/server.go +++ b/server/server.go @@ -944,7 +944,7 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura if entryPoint.Redirect != nil { if redirectHandlers[entryPointName] != nil { n.Use(redirectHandlers[entryPointName]) - } else if handler, err := s.buildEntryPointRedirect(entryPointName, entryPoint); err != nil { + } else if handler, err := s.buildRedirectHandler(entryPointName, entryPoint.Redirect); err != nil { log.Errorf("Error loading entrypoint configuration for frontend %s: %v", frontendName, err) log.Errorf("Skipping frontend %s...", frontendName) continue frontend @@ -1131,8 +1131,8 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura log.Infof("Configured IP Whitelists: %s", frontend.WhitelistSourceRange) } - if len(frontend.Redirect) > 0 { - rewrite, err := s.buildRedirectRewrite(entryPointName, frontend.Redirect) + if frontend.Redirect != nil { + rewrite, err := s.buildRedirectHandler(entryPointName, frontend.Redirect) if err != nil { log.Errorf("Error creating Frontend Redirect: %v", err) } @@ -1287,23 +1287,23 @@ func (s *Server) wireFrontendBackend(serverRoute *serverRoute, handler http.Hand serverRoute.route.Handler(handler) } -func (s *Server) buildEntryPointRedirect(srcEntryPointName string, entryPoint *configuration.EntryPoint) (*middlewares.Rewrite, error) { - if len(entryPoint.Redirect.EntryPoint) > 0 { - return s.buildRedirectRewrite(srcEntryPointName, entryPoint.Redirect.EntryPoint) +func (s *Server) buildRedirectHandler(srcEntryPointName string, redirect *types.Redirect) (*middlewares.Rewrite, error) { + // entry point redirect + if len(redirect.EntryPoint) > 0 { + return s.buildEntryPointRedirect(srcEntryPointName, redirect.EntryPoint) } - regex := entryPoint.Redirect.Regex - replacement := entryPoint.Redirect.Replacement - rewrite, err := middlewares.NewRewrite(regex, replacement, true) + // regex redirect + rewrite, err := middlewares.NewRewrite(redirect.Regex, redirect.Replacement, true) if err != nil { return nil, err } - log.Debugf("Creating entryPoint redirect %s -> %s : %s -> %s", srcEntryPointName, entryPoint.Redirect.EntryPoint, regex, replacement) + log.Debugf("Creating entryPoint redirect %s -> %s -> %s", srcEntryPointName, redirect.Regex, redirect.Replacement) return rewrite, nil } -func (s *Server) buildRedirectRewrite(srcEntryPointName string, redirectEntryPoint string) (*middlewares.Rewrite, error) { +func (s *Server) buildEntryPointRedirect(srcEntryPointName string, redirectEntryPoint string) (*middlewares.Rewrite, error) { regex, replacement, err := s.buildRedirect(redirectEntryPoint) if err != nil { return nil, err diff --git a/server/server_test.go b/server/server_test.go index ac8737b90..919b910ca 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -918,15 +918,20 @@ func TestBuildEntryPointRedirect(t *testing.T) { srcEntryPointName string url string entryPoint *configuration.EntryPoint + redirect *types.Redirect expectedURL string }{ { desc: "redirect regex", srcEntryPointName: "http", url: "http://foo.com", + redirect: &types.Redirect{ + Regex: `^(?:http?:\/\/)(foo)(\.com)$`, + Replacement: "https://$1{{\"bar\"}}$2", + }, entryPoint: &configuration.EntryPoint{ Address: ":80", - Redirect: &configuration.Redirect{ + Redirect: &types.Redirect{ Regex: `^(?:http?:\/\/)(foo)(\.com)$`, Replacement: "https://$1{{\"bar\"}}$2", }, @@ -937,9 +942,12 @@ func TestBuildEntryPointRedirect(t *testing.T) { desc: "redirect entry point", srcEntryPointName: "http", url: "http://foo:80", + redirect: &types.Redirect{ + EntryPoint: "https", + }, entryPoint: &configuration.EntryPoint{ Address: ":80", - Redirect: &configuration.Redirect{ + Redirect: &types.Redirect{ EntryPoint: "https", }, }, @@ -949,9 +957,14 @@ func TestBuildEntryPointRedirect(t *testing.T) { desc: "redirect entry point with regex (ignored)", srcEntryPointName: "http", url: "http://foo.com:80", + redirect: &types.Redirect{ + EntryPoint: "https", + Regex: `^(?:http?:\/\/)(foo)(\.com)$`, + Replacement: "https://$1{{\"bar\"}}$2", + }, entryPoint: &configuration.EntryPoint{ Address: ":80", - Redirect: &configuration.Redirect{ + Redirect: &types.Redirect{ EntryPoint: "https", Regex: `^(?:http?:\/\/)(foo)(\.com)$`, Replacement: "https://$1{{\"bar\"}}$2", @@ -966,7 +979,7 @@ func TestBuildEntryPointRedirect(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - rewrite, err := srv.buildEntryPointRedirect(test.srcEntryPointName, test.entryPoint) + rewrite, err := srv.buildRedirectHandler(test.srcEntryPointName, test.redirect) require.NoError(t, err) req := testhelpers.MustNewRequest(http.MethodGet, test.url, nil) @@ -983,7 +996,7 @@ func TestBuildEntryPointRedirect(t *testing.T) { } } -func TestServerBuildRedirectRewrite(t *testing.T) { +func TestServerBuildEntryPointRedirect(t *testing.T) { srv := Server{ globalConfiguration: configuration.GlobalConfiguration{ EntryPoints: configuration.EntryPoints{ @@ -1022,7 +1035,7 @@ func TestServerBuildRedirectRewrite(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - rewrite, err := srv.buildRedirectRewrite(test.srcEntryPointName, test.redirectEntryPoint) + rewrite, err := srv.buildEntryPointRedirect(test.srcEntryPointName, test.redirectEntryPoint) if test.errorExpected { require.Error(t, err) } else { diff --git a/templates/docker.tmpl b/templates/docker.tmpl index d4a7c9a84..6d1db7a02 100644 --- a/templates/docker.tmpl +++ b/templates/docker.tmpl @@ -47,7 +47,6 @@ [frontends."frontend-{{getServiceBackend $container $serviceName}}"] backend = "backend-{{getServiceBackend $container $serviceName}}" passHostHeader = {{getServicePassHostHeader $container $serviceName}} - redirect = "{{getServiceRedirect $container $serviceName}}" {{if getWhitelistSourceRange $container}} whitelistSourceRange = [{{range getWhitelistSourceRange $container}} "{{.}}", @@ -60,14 +59,21 @@ basicAuth = [{{range getServiceBasicAuth $container $serviceName}} "{{.}}", {{end}}] - [frontends."frontend-{{getServiceBackend $container $serviceName}}".routes."service-{{$serviceName | replace "/" "" | replace "." "-"}}"] + + {{if hasServiceRedirect $container $serviceName}} + [frontends."frontend-{{getServiceBackend $container $serviceName}}".redirect] + entryPoint = "{{getServiceRedirectEntryPoint $container $serviceName}}" + regex = "{{getServiceRedirectRegex $container $serviceName}}" + replacement = "{{getServiceRedirectReplacement $container $serviceName}}" + {{end}} + + [frontends."frontend-{{getServiceBackend $container $serviceName}}".routes."service-{{$serviceName | replace "/" "" | replace "." "-"}}"] rule = "{{getServiceFrontendRule $container $serviceName}}" {{end}} {{else}} [frontends."frontend-{{$frontend}}"] backend = "backend-{{getBackend $container}}" passHostHeader = {{getPassHostHeader $container}} - redirect = "{{getRedirect $container}}" {{if getWhitelistSourceRange $container}} whitelistSourceRange = [{{range getWhitelistSourceRange $container}} "{{.}}", @@ -80,6 +86,14 @@ basicAuth = [{{range getBasicAuth $container}} "{{.}}", {{end}}] + + {{if hasRedirect $container}} + [frontends."frontend-{{$frontend}}".redirect] + entryPoint = "{{getRedirectEntryPoint $container}}" + regex = "{{getRedirectRegex $container}}" + replacement = "{{getRedirectReplacement $container}}" + {{end}} + [frontends."frontend-{{$frontend}}".headers] {{if hasSSLRedirectHeaders $container}} SSLRedirect = {{getSSLRedirectHeaders $container}} diff --git a/templates/kubernetes.tmpl b/templates/kubernetes.tmpl index 7a671d323..305fd4f72 100644 --- a/templates/kubernetes.tmpl +++ b/templates/kubernetes.tmpl @@ -25,13 +25,20 @@ backend = "{{$frontend.Backend}}" priority = {{$frontend.Priority}} passHostHeader = {{$frontend.PassHostHeader}} - redirect = "{{$frontend.Redirect}}" basicAuth = [{{range $frontend.BasicAuth}} "{{.}}", {{end}}] whitelistSourceRange = [{{range $frontend.WhitelistSourceRange}} "{{.}}", {{end}}] + + {{if $frontend.Redirect}} + [frontends."{{$frontendName}}".redirect] + entryPoint = "{{$frontend.RedirectEntryPoint}}" + regex = "{{$frontend.RedirectRegex}}" + replacement = "{{$frontend.RedirectReplacement}}" + {{end}} + [frontends."{{$frontendName}}".headers] SSLRedirect = {{$frontend.Headers.SSLRedirect}} SSLTemporaryRedirect = {{$frontend.Headers.SSLTemporaryRedirect}} diff --git a/templates/rancher.tmpl b/templates/rancher.tmpl index 034168e76..3faad513d 100644 --- a/templates/rancher.tmpl +++ b/templates/rancher.tmpl @@ -34,13 +34,20 @@ backend = "backend-{{getBackend $service}}" passHostHeader = {{getPassHostHeader $service}} priority = {{getPriority $service}} - redirect = "{{getRedirect $service}}" entryPoints = [{{range getEntryPoints $service}} "{{.}}", {{end}}] basicAuth = [{{range getBasicAuth $service}} "{{.}}", {{end}}] + + {{if hasRedirect $service}} + [frontends."frontend-{{$frontendName}}".redirect] + entryPoint = "{{getRedirectEntryPoint $service}}" + regex = "{{getRedirectRegex $service}}" + replacement = "{{getRedirectReplacement $service}}" + {{end}} + [frontends."frontend-{{$frontendName}}".routes."route-frontend-{{$frontendName}}"] rule = "{{getFrontendRule $service}}" {{end}} diff --git a/types/common_label.go b/types/common_label.go index af543b573..f0bc3ffd5 100644 --- a/types/common_label.go +++ b/types/common_label.go @@ -13,7 +13,9 @@ const ( SuffixFrontendEntryPoints = "frontend.entryPoints" SuffixFrontendPassHostHeader = "frontend.passHostHeader" SuffixFrontendPriority = "frontend.priority" - SuffixFrontendRedirect = "frontend.redirect" + SuffixFrontendRedirectEntryPoint = "frontend.redirect.entryPoint" + SuffixFrontendRedirectRegex = "frontend.redirect.regex" + SuffixFrontendRedirectReplacement = "frontend.redirect.replacement" SuffixFrontendRule = "frontend.rule" LabelDomain = LabelPrefix + "domain" LabelEnable = LabelPrefix + "enable" @@ -29,7 +31,9 @@ const ( LabelFrontendPriority = LabelPrefix + SuffixFrontendPriority LabelFrontendRule = LabelPrefix + SuffixFrontendRule LabelFrontendRuleType = LabelPrefix + "frontend.rule.type" - LabelFrontendRedirect = LabelPrefix + SuffixFrontendRedirect + LabelFrontendRedirectEntryPoint = LabelPrefix + SuffixFrontendRedirectEntryPoint + LabelFrontendRedirectRegex = LabelPrefix + SuffixFrontendRedirectRegex + LabelFrontendRedirectReplacement = LabelPrefix + SuffixFrontendRedirectReplacement LabelTraefikFrontendValue = LabelPrefix + "frontend.value" LabelTraefikFrontendWhitelistSourceRange = LabelPrefix + "frontend.whitelistSourceRange" LabelFrontendRequestHeaders = LabelPrefix + "frontend.headers.customRequestHeaders" diff --git a/types/types.go b/types/types.go index c92318d3a..0625b2271 100644 --- a/types/types.go +++ b/types/types.go @@ -153,7 +153,14 @@ type Frontend struct { Headers Headers `json:"headers,omitempty"` Errors map[string]ErrorPage `json:"errors,omitempty"` RateLimit *RateLimit `json:"ratelimit,omitempty"` - Redirect string `json:"redirect,omitempty"` + Redirect *Redirect `json:"redirect,omitempty"` +} + +// Redirect configures a redirection of an entry point to another, or to an URL +type Redirect struct { + EntryPoint string `json:"entryPoint,omitempty"` + Regex string `json:"regex,omitempty"` + Replacement string `json:"replacement,omitempty"` } // LoadBalancerMethod holds the method of load balancing to use. From 48b4eb5c0d3694863a35c3121e2129c70b70f48d Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 15 Dec 2017 16:00:14 +0100 Subject: [PATCH 10/11] =?UTF-8?q?Fix=20bad=20Tr=C3=A6fik=20update=20on=20C?= =?UTF-8?q?onsul=20Catalog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- provider/consul/consul_catalog.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provider/consul/consul_catalog.go b/provider/consul/consul_catalog.go index 8387e72fa..8f0510510 100644 --- a/provider/consul/consul_catalog.go +++ b/provider/consul/consul_catalog.go @@ -262,7 +262,7 @@ func (p *CatalogProvider) watchCatalogServices(stopCh <-chan struct{}, watchCh c func getServiceIds(services []*api.CatalogService) []string { var serviceIds []string for _, service := range services { - serviceIds = append(serviceIds, service.ServiceID) + serviceIds = append(serviceIds, service.ID) } return serviceIds } From b17d5b80b832fa8a619cb03a4b4888f24af0fb84 Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 15 Dec 2017 20:52:03 +0100 Subject: [PATCH 11/11] Reload configuration when port change for one service --- integration/consul_catalog_test.go | 40 ++++++++++++++++ provider/consul/consul_catalog.go | 57 +++++++++++++++++------ provider/consul/consul_catalog_test.go | 64 +++++++++++++++++++++++++- 3 files changed, 145 insertions(+), 16 deletions(-) diff --git a/integration/consul_catalog_test.go b/integration/consul_catalog_test.go index 0241d8ab3..40765059c 100644 --- a/integration/consul_catalog_test.go +++ b/integration/consul_catalog_test.go @@ -419,6 +419,46 @@ func (s *ConsulCatalogSuite) TestCircuitBreaker(c *check.C) { c.Assert(err, checker.IsNil) } +func (s *ConsulCatalogSuite) TestRefreshConfigPortChange(c *check.C) { + cmd, display := s.traefikCmd( + withConfigFile("fixtures/consul_catalog/simple.toml"), + "--consulCatalog", + "--consulCatalog.exposedByDefault=false", + "--consulCatalog.watch=true", + "--consulCatalog.endpoint="+s.consulIP+":8500", + "--consulCatalog.domain=consul.localhost") + defer display(c) + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + nginx := s.composeProject.Container(c, "nginx1") + + err = s.registerService("test", nginx.NetworkSettings.IPAddress, 81, []string{"name=nginx1", "traefik.enable=true"}) + c.Assert(err, checker.IsNil, check.Commentf("Error registering service")) + + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) + c.Assert(err, checker.IsNil) + req.Host = "test.consul.localhost" + + err = try.Request(req, 20*time.Second, try.StatusCodeIs(http.StatusBadGateway)) + c.Assert(err, checker.IsNil) + + err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 5*time.Second, try.BodyContains("nginx1")) + c.Assert(err, checker.IsNil) + + err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"name=nginx1", "traefik.enable=true"}) + c.Assert(err, checker.IsNil, check.Commentf("Error registering service")) + + defer s.deregisterService("test", nginx.NetworkSettings.IPAddress) + + err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 60*time.Second, try.BodyContains("nginx1")) + c.Assert(err, checker.IsNil) + + err = try.Request(req, 20*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody()) + c.Assert(err, checker.IsNil) +} + func (s *ConsulCatalogSuite) TestRetryWithConsulServer(c *check.C) { //Scale consul to 0 to be able to start traefik before and test retry s.composeProject.Scale(c, "consul", 0) diff --git a/provider/consul/consul_catalog.go b/provider/consul/consul_catalog.go index 8f0510510..b87d63e90 100644 --- a/provider/consul/consul_catalog.go +++ b/provider/consul/consul_catalog.go @@ -78,8 +78,11 @@ func (a nodeSorter) Less(i int, j int) bool { } func hasChanged(current map[string]Service, previous map[string]Service) bool { + if len(current) != len(previous) { + return true + } addedServiceKeys, removedServiceKeys := getChangedServiceKeys(current, previous) - return len(removedServiceKeys) > 0 || len(addedServiceKeys) > 0 || hasNodeOrTagsChanged(current, previous) + return len(removedServiceKeys) > 0 || len(addedServiceKeys) > 0 || hasServiceChanged(current, previous) } func getChangedServiceKeys(current map[string]Service, previous map[string]Service) ([]string, []string) { @@ -92,20 +95,24 @@ func getChangedServiceKeys(current map[string]Service, previous map[string]Servi return fun.Keys(addedKeys).([]string), fun.Keys(removedKeys).([]string) } -func hasNodeOrTagsChanged(current map[string]Service, previous map[string]Service) bool { - var added []string - var removed []string +func hasServiceChanged(current map[string]Service, previous map[string]Service) bool { for key, value := range current { if prevValue, ok := previous[key]; ok { addedNodesKeys, removedNodesKeys := getChangedStringKeys(value.Nodes, prevValue.Nodes) - added = append(added, addedNodesKeys...) - removed = append(removed, removedNodesKeys...) + if len(addedNodesKeys) > 0 || len(removedNodesKeys) > 0 { + return true + } addedTagsKeys, removedTagsKeys := getChangedStringKeys(value.Tags, prevValue.Tags) - added = append(added, addedTagsKeys...) - removed = append(removed, removedTagsKeys...) + if len(addedTagsKeys) > 0 || len(removedTagsKeys) > 0 { + return true + } + addedPortsKeys, removedPortsKeys := getChangedIntKeys(value.Ports, prevValue.Ports) + if len(addedPortsKeys) > 0 || len(removedPortsKeys) > 0 { + return true + } } } - return len(added) > 0 || len(removed) > 0 + return false } func getChangedStringKeys(currState []string, prevState []string) ([]string, []string) { @@ -118,6 +125,16 @@ func getChangedStringKeys(currState []string, prevState []string) ([]string, []s return fun.Keys(addedKeys).([]string), fun.Keys(removedKeys).([]string) } +func getChangedIntKeys(currState []int, prevState []int) ([]int, []int) { + currKeySet := fun.Set(currState).(map[int]bool) + prevKeySet := fun.Set(prevState).(map[int]bool) + + addedKeys := fun.Difference(currKeySet, prevKeySet).(map[int]bool) + removedKeys := fun.Difference(prevKeySet, currKeySet).(map[int]bool) + + return fun.Keys(addedKeys).([]int), fun.Keys(removedKeys).([]int) +} + func (p *CatalogProvider) watchHealthState(stopCh <-chan struct{}, watchCh chan<- map[string][]string, errorCh chan<- error) { health := p.client.Health() catalog := p.client.Catalog() @@ -194,6 +211,7 @@ type Service struct { Name string Tags []string Nodes []string + Ports []int } func (p *CatalogProvider) watchCatalogServices(stopCh <-chan struct{}, watchCh chan<- map[string][]string, errorCh chan<- error) { @@ -235,14 +253,17 @@ func (p *CatalogProvider) watchCatalogServices(stopCh <-chan struct{}, watchCh c return } nodesID := getServiceIds(nodes) + ports := getServicePorts(nodes) if service, ok := current[key]; ok { service.Tags = value service.Nodes = nodesID + service.Ports = ports } else { service := Service{ Name: key, Tags: value, Nodes: nodesID, + Ports: ports, } current[key] = service } @@ -267,6 +288,14 @@ func getServiceIds(services []*api.CatalogService) []string { return serviceIds } +func getServicePorts(services []*api.CatalogService) []int { + var servicePorts []int + for _, service := range services { + servicePorts = append(servicePorts, service.ServicePort) + } + return servicePorts +} + func (p *CatalogProvider) healthyNodes(service string) (catalogUpdate, error) { health := p.client.Health() opts := &api.QueryOptions{} @@ -279,7 +308,7 @@ func (p *CatalogProvider) healthyNodes(service string) (catalogUpdate, error) { return p.nodeFilter(service, node) }, data).([]*api.ServiceEntry) - //Merge tags of nodes matching constraints, in a single slice. + // Merge tags of nodes matching constraints, in a single slice. tags := fun.Foldl(func(node *api.ServiceEntry, set []string) []string { return fun.Keys(fun.Union( fun.Set(set), @@ -480,8 +509,8 @@ func (p *CatalogProvider) buildConfig(catalog []catalogUpdate) *types.Configurat "hasMaxconnAttributes": p.hasMaxconnAttributes, } - allNodes := []*api.ServiceEntry{} - services := []*serviceUpdate{} + var allNodes []*api.ServiceEntry + var services []*serviceUpdate for _, info := range catalog { if len(info.Nodes) > 0 { services = append(services, info.Service) @@ -519,7 +548,7 @@ func (p *CatalogProvider) hasMaxconnAttributes(attributes []string) bool { func (p *CatalogProvider) getNodes(index map[string][]string) ([]catalogUpdate, error) { visited := make(map[string]bool) - nodes := []catalogUpdate{} + var nodes []catalogUpdate for service := range index { name := strings.ToLower(service) if !strings.Contains(name, " ") && !visited[name] { @@ -555,7 +584,7 @@ func (p *CatalogProvider) watch(configurationChan chan<- types.ConfigMessage, st return nil case index, ok := <-watchCh: if !ok { - return errors.New("Consul service list nil") + return errors.New("consul service list nil") } log.Debug("List of services changed") nodes, err := p.getNodes(index) diff --git a/provider/consul/consul_catalog_test.go b/provider/consul/consul_catalog_test.go index 3e6899f80..ce86cf74e 100644 --- a/provider/consul/consul_catalog_test.go +++ b/provider/consul/consul_catalog_test.go @@ -1094,7 +1094,7 @@ func TestConsulCatalogHasNodeOrTagschanged(t *testing.T) { expected: false, }, { - desc: "Change detected con tags", + desc: "Change detected on tags", current: map[string]Service{ "foo-service": { Name: "foo", @@ -1111,6 +1111,66 @@ func TestConsulCatalogHasNodeOrTagschanged(t *testing.T) { }, expected: true, }, + { + desc: "Change detected on ports", + current: map[string]Service{ + "foo-service": { + Name: "foo", + Nodes: []string{"node1"}, + Tags: []string{"foo=bar"}, + Ports: []int{80}, + }, + }, + previous: map[string]Service{ + "foo-service": { + Name: "foo", + Nodes: []string{"node1"}, + Tags: []string{"foo"}, + Ports: []int{81}, + }, + }, + expected: true, + }, + { + desc: "Change detected on ports", + current: map[string]Service{ + "foo-service": { + Name: "foo", + Nodes: []string{"node1"}, + Tags: []string{"foo=bar"}, + Ports: []int{80}, + }, + }, + previous: map[string]Service{ + "foo-service": { + Name: "foo", + Nodes: []string{"node1"}, + Tags: []string{"foo"}, + Ports: []int{81, 82}, + }, + }, + expected: true, + }, + { + desc: "No Change detected", + current: map[string]Service{ + "foo-service": { + Name: "foo", + Nodes: []string{"node1"}, + Tags: []string{"foo"}, + Ports: []int{80}, + }, + }, + previous: map[string]Service{ + "foo-service": { + Name: "foo", + Nodes: []string{"node1"}, + Tags: []string{"foo"}, + Ports: []int{80}, + }, + }, + expected: false, + }, } for _, test := range testCases { @@ -1118,7 +1178,7 @@ func TestConsulCatalogHasNodeOrTagschanged(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - actual := hasNodeOrTagsChanged(test.current, test.previous) + actual := hasServiceChanged(test.current, test.previous) assert.Equal(t, test.expected, actual) }) }