diff --git a/controllers/nginx/controller.go b/controllers/nginx/controller.go index d1fb498f8..84653bab9 100644 --- a/controllers/nginx/controller.go +++ b/controllers/nginx/controller.go @@ -48,7 +48,7 @@ import ( const ( defUpstreamName = "upstream-default-backend" defServerName = "_" - namedPortAnnotation = "kubernetes.io/ingress-named-ports" + namedPortAnnotation = "ingress.kubernetes.io/named-ports" podStoreSyncedPollPeriod = 1 * time.Second rootLocation = "/" ) diff --git a/controllers/nginx/examples/rewrite/README.md b/controllers/nginx/examples/rewrite/README.md new file mode 100644 index 000000000..507b4315a --- /dev/null +++ b/controllers/nginx/examples/rewrite/README.md @@ -0,0 +1,67 @@ + +Create an Ingress rule with a rewrite annotation: +``` +$ echo " +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + annotations: + ingress.kubernetes.io/rewrite-target: / + name: rewrite + namespace: default +spec: + rules: + - host: rewrite.bar.com + http: + paths: + - backend: + serviceName: echoheaders + servicePort: 80 + path: /something +" | kubectl create -f - +``` + +Check the rewrite is working + +``` +$ curl -v http://172.17.4.99/something -H 'Host: rewrite.bar.com' +* Trying 172.17.4.99... +* Connected to 172.17.4.99 (172.17.4.99) port 80 (#0) +> GET /something HTTP/1.1 +> Host: rewrite.bar.com +> User-Agent: curl/7.43.0 +> Accept: */* +> +< HTTP/1.1 200 OK +< Server: nginx/1.11.0 +< Date: Tue, 31 May 2016 16:07:31 GMT +< Content-Type: text/plain +< Transfer-Encoding: chunked +< Connection: keep-alive +< +CLIENT VALUES: +client_address=10.2.56.9 +command=GET +real path=/ +query=nil +request_version=1.1 +request_uri=http://rewrite.bar.com:8080/ + +SERVER VALUES: +server_version=nginx: 1.9.11 - lua: 10001 + +HEADERS RECEIVED: +accept=*/* +connection=close +host=rewrite.bar.com +user-agent=curl/7.43.0 +x-forwarded-for=10.2.56.1 +x-forwarded-host=rewrite.bar.com +x-forwarded-port=80 +x-forwarded-proto=http +x-real-ip=10.2.56.1 +BODY: +* Connection #0 to host 172.17.4.99 left intact +-no body in request- +``` + diff --git a/controllers/nginx/nginx.tmpl b/controllers/nginx/nginx.tmpl index 812f7f438..68277d351 100644 --- a/controllers/nginx/nginx.tmpl +++ b/controllers/nginx/nginx.tmpl @@ -177,8 +177,10 @@ http { {{ if $cfg.enableVtsStatus }}vhost_traffic_status_filter_by_set_key $geoip_country_code country::$server_name;{{ end }} - {{ range $location := $server.Locations }} - location {{ $location.Path }} { + {{- range $location := $server.Locations }} + {{- $path := buildLocation $location }} + location {{ $path }} { + location {{ $path }} { proxy_set_header Host $host; # Pass Real IP @@ -202,7 +204,12 @@ http { proxy_http_version 1.1; - proxy_pass http://{{ $location.Upstream.Name }}; + {{/* rewrite only works if the content is not compressed */}} + {{ if $location.Redirect.AddBaseURL -}} + proxy_set_header Accept-Encoding ""; + {{- end }} + + {{- buildProxyPass $location }} } {{ end }} diff --git a/controllers/nginx/nginx/rewrite/main.go b/controllers/nginx/nginx/rewrite/main.go index f2726ad91..ed17f31a3 100644 --- a/controllers/nginx/nginx/rewrite/main.go +++ b/controllers/nginx/nginx/rewrite/main.go @@ -17,68 +17,57 @@ limitations under the License. package rewrite import ( + "errors" "strconv" "k8s.io/kubernetes/pkg/apis/extensions" ) const ( - rewrite = "ingress-nginx.kubernetes.io/rewrite-to" - fixUrls = "ingress-nginx.kubernetes.io/fix-urls" + rewriteTo = "ingress.kubernetes.io/rewrite-target" + addBaseURL = "ingress.kubernetes.io/add-base-url" ) -// ErrMissingAnnotations is returned when the ingress rule -// does not contains annotations related with redirect or strip prefix -type ErrMissingAnnotations struct { - msg string -} - -func (e ErrMissingAnnotations) Error() string { - return e.msg -} - // Redirect returns authentication configuration for an Ingress rule type Redirect struct { - // To URI where the traffic must be redirected - To string - // Rewrite indicates if is required to change the - // links in the response from the upstream servers - Rewrite bool + // Target URI where the traffic must be redirected + Target string + // AddBaseURL indicates if is required to add a base tag in the head + // of the responses from the upstream servers + AddBaseURL bool } type ingAnnotations map[string]string -func (a ingAnnotations) rewrite() string { - val, ok := a[rewrite] - if ok { - return val - } - - return "" -} - -func (a ingAnnotations) fixUrls() bool { - val, ok := a[fixUrls] +func (a ingAnnotations) addBaseURL() bool { + val, ok := a[addBaseURL] if ok { if b, err := strconv.ParseBool(val); err == nil { return b } } - return false } +func (a ingAnnotations) rewriteTo() string { + val, ok := a[rewriteTo] + if ok { + return val + } + return "" +} + // ParseAnnotations parses the annotations contained in the ingress // rule used to rewrite the defined paths func ParseAnnotations(ing *extensions.Ingress) (*Redirect, error) { if ing.GetAnnotations() == nil { - return &Redirect{}, ErrMissingAnnotations{"no annotations present"} + return &Redirect{}, errors.New("no annotations present") } - rt := ingAnnotations(ing.GetAnnotations()).rewrite() - rw := ingAnnotations(ing.GetAnnotations()).fixUrls() + rt := ingAnnotations(ing.GetAnnotations()).rewriteTo() + abu := ingAnnotations(ing.GetAnnotations()).addBaseURL() return &Redirect{ - To: rt, - Rewrite: rw, + Target: rt, + AddBaseURL: abu, }, nil } diff --git a/controllers/nginx/nginx/rewrite/main_test.go b/controllers/nginx/nginx/rewrite/main_test.go index 6fc500c0b..cd8cf843e 100644 --- a/controllers/nginx/nginx/rewrite/main_test.go +++ b/controllers/nginx/nginx/rewrite/main_test.go @@ -66,29 +66,29 @@ func buildIngress() *extensions.Ingress { func TestAnnotations(t *testing.T) { ing := buildIngress() - r := ingAnnotations(ing.GetAnnotations()).rewrite() + r := ingAnnotations(ing.GetAnnotations()).rewriteTo() if r != "" { t.Error("Expected no redirect") } - f := ingAnnotations(ing.GetAnnotations()).fixUrls() + f := ingAnnotations(ing.GetAnnotations()).addBaseURL() if f != false { - t.Error("Expected false as fix-urls but %v was returend", f) + t.Error("Expected false in add-base-url but %v was returend", f) } data := map[string]string{} - data[rewrite] = defRoute - data[fixUrls] = "true" + data[rewriteTo] = defRoute + data[addBaseURL] = "true" ing.SetAnnotations(data) - r = ingAnnotations(ing.GetAnnotations()).rewrite() + r = ingAnnotations(ing.GetAnnotations()).rewriteTo() if r != defRoute { - t.Error("Expected %v as rewrite but %v was returend", defRoute, r) + t.Error("Expected %v in rewrite but %v was returend", defRoute, r) } - f = ingAnnotations(ing.GetAnnotations()).fixUrls() + f = ingAnnotations(ing.GetAnnotations()).addBaseURL() if f != true { - t.Error("Expected true as fix-urls but %v was returend", f) + t.Error("Expected true in add-base-url but %v was returend", f) } } @@ -104,7 +104,7 @@ func TestRedirect(t *testing.T) { ing := buildIngress() data := map[string]string{} - data[rewrite] = defRoute + data[rewriteTo] = defRoute ing.SetAnnotations(data) redirect, err := ParseAnnotations(ing) @@ -112,7 +112,7 @@ func TestRedirect(t *testing.T) { t.Errorf("Uxpected error with ingress: %v", err) } - if redirect.To != defRoute { - t.Errorf("Expected %v as redirect but returned %s", defRoute, redirect.To) + if redirect.Target != defRoute { + t.Errorf("Expected %v as redirect but returned %s", defRoute, redirect.Target) } } diff --git a/controllers/nginx/nginx/template.go b/controllers/nginx/nginx/template.go index b1b0b1cfc..a844cb944 100644 --- a/controllers/nginx/nginx/template.go +++ b/controllers/nginx/nginx/template.go @@ -109,6 +109,8 @@ func toCamelCase(src string) string { return string(bytes.Join(chunks, nil)) } +// buildLocation produces the location string, if the ingress has redirects +// (specified through the ingress.kubernetes.io/rewrite-to annotation) func buildLocation(input interface{}) string { location, ok := input.(*Location) if !ok { @@ -116,16 +118,17 @@ func buildLocation(input interface{}) string { } path := location.Path - if len(location.Redirect.To) > 0 && location.Redirect.To != path { - // if path != slash && !strings.HasSuffix(path, slash) { - // path = fmt.Sprintf("%s/", path) - // } + if len(location.Redirect.Target) > 0 && location.Redirect.Target != path { return fmt.Sprintf("~* %s", path) } return path } +// buildProxyPass produces the proxy pass string, if the ingress has redirects +// (specified through the ingress.kubernetes.io/rewrite-to annotation) +// If the annotation ingress.kubernetes.io/add-base-url:"true" is specified it will +// add a base tag in the head of the response from the service func buildProxyPass(input interface{}) string { location, ok := input.(*Location) if !ok { @@ -134,34 +137,46 @@ func buildProxyPass(input interface{}) string { path := location.Path - if path == location.Redirect.To { - return fmt.Sprintf("proxy_pass http://%s;", location.Upstream.Name) + // defProxyPass returns the default proxy_pass, just the name of the upstream + defProxyPass := fmt.Sprintf("proxy_pass http://%s;", location.Upstream.Name) + // if the path in the ingress rule is equals to the target: no special rewrite + if path == location.Redirect.Target { + return defProxyPass } if path != slash && !strings.HasSuffix(path, slash) { path = fmt.Sprintf("%s/", path) } - if len(location.Redirect.To) > 0 { - rc := "" - if location.Redirect.Rewrite { - rc = fmt.Sprintf(`sub_filter '' ''; -sub_filter_once off;`, location.Path) + if len(location.Redirect.Target) > 0 { + abu := "" + if location.Redirect.AddBaseURL { + bPath := location.Redirect.Target + if !strings.HasSuffix(bPath, slash) { + bPath = fmt.Sprintf("%s/", bPath) + } + + abu = fmt.Sprintf(`subs_filter '' '' r; + subs_filter '' '' r; + `, bPath, bPath) } - if location.Redirect.To == slash { + if location.Redirect.Target == slash { // special case redirect to / // ie /something to / - return fmt.Sprintf(`rewrite %s(.*) /$1 break; -proxy_pass http://%s; -%v`, path, location.Upstream.Name, rc) + return fmt.Sprintf(` + rewrite %s / break; + rewrite %s(.*) /$1 break; + proxy_pass http://%s; + %v`, location.Path, path, location.Upstream.Name, abu) } - return fmt.Sprintf(`rewrite %s(.*) %s/$1 break; -proxy_pass http://%s; -%v`, path, location.Redirect.To, location.Upstream.Name, rc) + return fmt.Sprintf(` + rewrite %s(.*) %s/$1 break; + proxy_pass http://%s; + %v`, path, location.Redirect.Target, location.Upstream.Name, abu) } // default proxy_pass - return fmt.Sprintf("proxy_pass http://%s;", location.Upstream.Name) + return defProxyPass } diff --git a/controllers/nginx/nginx/template_test.go b/controllers/nginx/nginx/template_test.go index 945f12543..4d82c40ae 100644 --- a/controllers/nginx/nginx/template_test.go +++ b/controllers/nginx/nginx/template_test.go @@ -17,6 +17,7 @@ limitations under the License. package nginx import ( + "strings" "testing" "k8s.io/contrib/ingress/controllers/nginx/nginx/rewrite" @@ -24,36 +25,46 @@ import ( var ( tmplFuncTestcases = map[string]struct { - Path string - To string - Location string - ProxyPass string - Rewrite bool + Path string + Target string + Location string + ProxyPass string + AddBaseURL bool }{ "invalid redirect / to /": {"/", "/", "/", "proxy_pass http://upstream-name;", false}, "redirect / to /jenkins": {"/", "/jenkins", "~* /", - `rewrite /(.*) /jenkins/$1 break; -proxy_pass http://upstream-name; -`, false}, - "redirect /something to /": {"/something", "/", "~* /something/", `rewrite /something/(.*) /$1 break; -proxy_pass http://upstream-name; -`, false}, - "redirect /something-complex to /not-root": {"/something-complex", "/not-root", "~* /something-complex/", `rewrite /something-complex/(.*) /not-root/$1 break; -proxy_pass http://upstream-name; -`, false}, - "redirect / to /jenkins and rewrite": {"/", "/jenkins", "~* /", - `rewrite /(.*) /jenkins/$1 break; -proxy_pass http://upstream-name; -sub_filter "//$host/" "//$host/jenkins"; -sub_filter_once off;`, true}, - "redirect /something to / and rewrite": {"/something", "/", "~* /something/", `rewrite /something/(.*) /$1 break; -proxy_pass http://upstream-name; -sub_filter "//$host/something" "//$host/"; -sub_filter_once off;`, true}, - "redirect /something-complex to /not-root and rewrite": {"/something-complex", "/not-root", "~* /something-complex/", `rewrite /something-complex/(.*) /not-root/$1 break; -proxy_pass http://upstream-name; -sub_filter "//$host/something-complex" "//$host/not-root"; -sub_filter_once off;`, true}, + ` + rewrite /(.*) /jenkins/$1 break; + proxy_pass http://upstream-name; + `, false}, + "redirect /something to /": {"/something", "/", "~* /something", ` + rewrite /something / break; + rewrite /something/(.*) /$1 break; + proxy_pass http://upstream-name; + `, false}, + "redirect /something-complex to /not-root": {"/something-complex", "/not-root", "~* /something-complex", ` + rewrite /something-complex/(.*) /not-root/$1 break; + proxy_pass http://upstream-name; + `, false}, + "redirect / to /jenkins and rewrite": {"/", "/jenkins", "~* /", ` + rewrite /(.*) /jenkins/$1 break; + proxy_pass http://upstream-name; + subs_filter '' '' r; + subs_filter '' '' r; + `, true}, + "redirect /something to / and rewrite": {"/something", "/", "~* /something", ` + rewrite /something / break; + rewrite /something/(.*) /$1 break; + proxy_pass http://upstream-name; + subs_filter '' '' r; + subs_filter '' '' r; + `, true}, + "redirect /something-complex to /not-root and rewrite": {"/something-complex", "/not-root", "~* /something-complex", ` + rewrite /something-complex/(.*) /not-root/$1 break; + proxy_pass http://upstream-name; + subs_filter '' '' r; + subs_filter '' '' r; + `, true}, } ) @@ -61,12 +72,12 @@ func TestBuildLocation(t *testing.T) { for k, tc := range tmplFuncTestcases { loc := &Location{ Path: tc.Path, - Redirect: rewrite.Redirect{tc.To, tc.Rewrite}, + Redirect: rewrite.Redirect{tc.Target, tc.AddBaseURL}, } newLoc := buildLocation(loc) if tc.Location != newLoc { - t.Errorf("%s: expected %v but returned %v", k, tc.Location, newLoc) + t.Errorf("%s: expected '%v' but returned %v", k, tc.Location, newLoc) } } } @@ -75,13 +86,13 @@ func TestBuildProxyPass(t *testing.T) { for k, tc := range tmplFuncTestcases { loc := &Location{ Path: tc.Path, - Redirect: rewrite.Redirect{tc.To, tc.Rewrite}, + Redirect: rewrite.Redirect{tc.Target, tc.AddBaseURL}, Upstream: Upstream{Name: "upstream-name"}, } pp := buildProxyPass(loc) - if tc.ProxyPass != pp { - t.Errorf("%s: expected \n%v \nbut returned \n%v", k, tc.ProxyPass, pp) + if !strings.EqualFold(tc.ProxyPass, pp) { + t.Errorf("%s: expected \n'%v'\nbut returned \n'%v'", k, tc.ProxyPass, pp) } } }