Add option to append a base tag in the head

This commit is contained in:
Manuel de Brito Fontes 2016-05-27 11:58:13 -03:00
parent 95e85b57e3
commit f8ea58882b
7 changed files with 190 additions and 101 deletions

View file

@ -48,7 +48,7 @@ import (
const ( const (
defUpstreamName = "upstream-default-backend" defUpstreamName = "upstream-default-backend"
defServerName = "_" defServerName = "_"
namedPortAnnotation = "kubernetes.io/ingress-named-ports" namedPortAnnotation = "ingress.kubernetes.io/named-ports"
podStoreSyncedPollPeriod = 1 * time.Second podStoreSyncedPollPeriod = 1 * time.Second
rootLocation = "/" rootLocation = "/"
) )

View file

@ -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-
```

View file

@ -177,8 +177,10 @@ http {
{{ if $cfg.enableVtsStatus }}vhost_traffic_status_filter_by_set_key $geoip_country_code country::$server_name;{{ end }} {{ if $cfg.enableVtsStatus }}vhost_traffic_status_filter_by_set_key $geoip_country_code country::$server_name;{{ end }}
{{ range $location := $server.Locations }} {{- range $location := $server.Locations }}
location {{ $location.Path }} { {{- $path := buildLocation $location }}
location {{ $path }} {
location {{ $path }} {
proxy_set_header Host $host; proxy_set_header Host $host;
# Pass Real IP # Pass Real IP
@ -202,7 +204,12 @@ http {
proxy_http_version 1.1; 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 }} {{ end }}

View file

@ -17,68 +17,57 @@ limitations under the License.
package rewrite package rewrite
import ( import (
"errors"
"strconv" "strconv"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
) )
const ( const (
rewrite = "ingress-nginx.kubernetes.io/rewrite-to" rewriteTo = "ingress.kubernetes.io/rewrite-target"
fixUrls = "ingress-nginx.kubernetes.io/fix-urls" 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 // Redirect returns authentication configuration for an Ingress rule
type Redirect struct { type Redirect struct {
// To URI where the traffic must be redirected // Target URI where the traffic must be redirected
To string Target string
// Rewrite indicates if is required to change the // AddBaseURL indicates if is required to add a base tag in the head
// links in the response from the upstream servers // of the responses from the upstream servers
Rewrite bool AddBaseURL bool
} }
type ingAnnotations map[string]string type ingAnnotations map[string]string
func (a ingAnnotations) rewrite() string { func (a ingAnnotations) addBaseURL() bool {
val, ok := a[rewrite] val, ok := a[addBaseURL]
if ok {
return val
}
return ""
}
func (a ingAnnotations) fixUrls() bool {
val, ok := a[fixUrls]
if ok { if ok {
if b, err := strconv.ParseBool(val); err == nil { if b, err := strconv.ParseBool(val); err == nil {
return b return b
} }
} }
return false return false
} }
func (a ingAnnotations) rewriteTo() string {
val, ok := a[rewriteTo]
if ok {
return val
}
return ""
}
// ParseAnnotations parses the annotations contained in the ingress // ParseAnnotations parses the annotations contained in the ingress
// rule used to rewrite the defined paths // rule used to rewrite the defined paths
func ParseAnnotations(ing *extensions.Ingress) (*Redirect, error) { func ParseAnnotations(ing *extensions.Ingress) (*Redirect, error) {
if ing.GetAnnotations() == nil { if ing.GetAnnotations() == nil {
return &Redirect{}, ErrMissingAnnotations{"no annotations present"} return &Redirect{}, errors.New("no annotations present")
} }
rt := ingAnnotations(ing.GetAnnotations()).rewrite() rt := ingAnnotations(ing.GetAnnotations()).rewriteTo()
rw := ingAnnotations(ing.GetAnnotations()).fixUrls() abu := ingAnnotations(ing.GetAnnotations()).addBaseURL()
return &Redirect{ return &Redirect{
To: rt, Target: rt,
Rewrite: rw, AddBaseURL: abu,
}, nil }, nil
} }

View file

@ -66,29 +66,29 @@ func buildIngress() *extensions.Ingress {
func TestAnnotations(t *testing.T) { func TestAnnotations(t *testing.T) {
ing := buildIngress() ing := buildIngress()
r := ingAnnotations(ing.GetAnnotations()).rewrite() r := ingAnnotations(ing.GetAnnotations()).rewriteTo()
if r != "" { if r != "" {
t.Error("Expected no redirect") t.Error("Expected no redirect")
} }
f := ingAnnotations(ing.GetAnnotations()).fixUrls() f := ingAnnotations(ing.GetAnnotations()).addBaseURL()
if f != false { 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 := map[string]string{}
data[rewrite] = defRoute data[rewriteTo] = defRoute
data[fixUrls] = "true" data[addBaseURL] = "true"
ing.SetAnnotations(data) ing.SetAnnotations(data)
r = ingAnnotations(ing.GetAnnotations()).rewrite() r = ingAnnotations(ing.GetAnnotations()).rewriteTo()
if r != defRoute { 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 { 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() ing := buildIngress()
data := map[string]string{} data := map[string]string{}
data[rewrite] = defRoute data[rewriteTo] = defRoute
ing.SetAnnotations(data) ing.SetAnnotations(data)
redirect, err := ParseAnnotations(ing) redirect, err := ParseAnnotations(ing)
@ -112,7 +112,7 @@ func TestRedirect(t *testing.T) {
t.Errorf("Uxpected error with ingress: %v", err) t.Errorf("Uxpected error with ingress: %v", err)
} }
if redirect.To != defRoute { if redirect.Target != defRoute {
t.Errorf("Expected %v as redirect but returned %s", defRoute, redirect.To) t.Errorf("Expected %v as redirect but returned %s", defRoute, redirect.Target)
} }
} }

View file

@ -109,6 +109,8 @@ func toCamelCase(src string) string {
return string(bytes.Join(chunks, nil)) 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 { func buildLocation(input interface{}) string {
location, ok := input.(*Location) location, ok := input.(*Location)
if !ok { if !ok {
@ -116,16 +118,17 @@ func buildLocation(input interface{}) string {
} }
path := location.Path path := location.Path
if len(location.Redirect.To) > 0 && location.Redirect.To != path { if len(location.Redirect.Target) > 0 && location.Redirect.Target != path {
// if path != slash && !strings.HasSuffix(path, slash) {
// path = fmt.Sprintf("%s/", path)
// }
return fmt.Sprintf("~* %s", path) return fmt.Sprintf("~* %s", path)
} }
return 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 { func buildProxyPass(input interface{}) string {
location, ok := input.(*Location) location, ok := input.(*Location)
if !ok { if !ok {
@ -134,34 +137,46 @@ func buildProxyPass(input interface{}) string {
path := location.Path path := location.Path
if path == location.Redirect.To { // defProxyPass returns the default proxy_pass, just the name of the upstream
return fmt.Sprintf("proxy_pass http://%s;", location.Upstream.Name) 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) { if path != slash && !strings.HasSuffix(path, slash) {
path = fmt.Sprintf("%s/", path) path = fmt.Sprintf("%s/", path)
} }
if len(location.Redirect.To) > 0 { if len(location.Redirect.Target) > 0 {
rc := "" abu := ""
if location.Redirect.Rewrite { if location.Redirect.AddBaseURL {
rc = fmt.Sprintf(`sub_filter '<head(.*)>' '<head$1><base href="$scheme://$server_name%v">'; bPath := location.Redirect.Target
sub_filter_once off;`, location.Path) if !strings.HasSuffix(bPath, slash) {
bPath = fmt.Sprintf("%s/", bPath)
}
abu = fmt.Sprintf(`subs_filter '<head(.*)>' '<head$1><base href="$scheme://$server_name%v">' r;
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$server_name%v">' r;
`, bPath, bPath)
} }
if location.Redirect.To == slash { if location.Redirect.Target == slash {
// special case redirect to / // special case redirect to /
// ie /something to / // ie /something to /
return fmt.Sprintf(`rewrite %s(.*) /$1 break; return fmt.Sprintf(`
proxy_pass http://%s; rewrite %s / break;
%v`, path, location.Upstream.Name, rc) rewrite %s(.*) /$1 break;
proxy_pass http://%s;
%v`, location.Path, path, location.Upstream.Name, abu)
} }
return fmt.Sprintf(`rewrite %s(.*) %s/$1 break; return fmt.Sprintf(`
proxy_pass http://%s; rewrite %s(.*) %s/$1 break;
%v`, path, location.Redirect.To, location.Upstream.Name, rc) proxy_pass http://%s;
%v`, path, location.Redirect.Target, location.Upstream.Name, abu)
} }
// default proxy_pass // default proxy_pass
return fmt.Sprintf("proxy_pass http://%s;", location.Upstream.Name) return defProxyPass
} }

View file

@ -17,6 +17,7 @@ limitations under the License.
package nginx package nginx
import ( import (
"strings"
"testing" "testing"
"k8s.io/contrib/ingress/controllers/nginx/nginx/rewrite" "k8s.io/contrib/ingress/controllers/nginx/nginx/rewrite"
@ -24,36 +25,46 @@ import (
var ( var (
tmplFuncTestcases = map[string]struct { tmplFuncTestcases = map[string]struct {
Path string Path string
To string Target string
Location string Location string
ProxyPass string ProxyPass string
Rewrite bool AddBaseURL bool
}{ }{
"invalid redirect / to /": {"/", "/", "/", "proxy_pass http://upstream-name;", false}, "invalid redirect / to /": {"/", "/", "/", "proxy_pass http://upstream-name;", false},
"redirect / to /jenkins": {"/", "/jenkins", "~* /", "redirect / to /jenkins": {"/", "/jenkins", "~* /",
`rewrite /(.*) /jenkins/$1 break; `
proxy_pass http://upstream-name; rewrite /(.*) /jenkins/$1 break;
`, false}, proxy_pass http://upstream-name;
"redirect /something to /": {"/something", "/", "~* /something/", `rewrite /something/(.*) /$1 break; `, false},
proxy_pass http://upstream-name; "redirect /something to /": {"/something", "/", "~* /something", `
`, false}, rewrite /something / break;
"redirect /something-complex to /not-root": {"/something-complex", "/not-root", "~* /something-complex/", `rewrite /something-complex/(.*) /not-root/$1 break; rewrite /something/(.*) /$1 break;
proxy_pass http://upstream-name; proxy_pass http://upstream-name;
`, false}, `, false},
"redirect / to /jenkins and rewrite": {"/", "/jenkins", "~* /", "redirect /something-complex to /not-root": {"/something-complex", "/not-root", "~* /something-complex", `
`rewrite /(.*) /jenkins/$1 break; rewrite /something-complex/(.*) /not-root/$1 break;
proxy_pass http://upstream-name; proxy_pass http://upstream-name;
sub_filter "//$host/" "//$host/jenkins"; `, false},
sub_filter_once off;`, true}, "redirect / to /jenkins and rewrite": {"/", "/jenkins", "~* /", `
"redirect /something to / and rewrite": {"/something", "/", "~* /something/", `rewrite /something/(.*) /$1 break; rewrite /(.*) /jenkins/$1 break;
proxy_pass http://upstream-name; proxy_pass http://upstream-name;
sub_filter "//$host/something" "//$host/"; subs_filter '<head(.*)>' '<head$1><base href="$scheme://$server_name/jenkins/">' r;
sub_filter_once off;`, true}, subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$server_name/jenkins/">' r;
"redirect /something-complex to /not-root and rewrite": {"/something-complex", "/not-root", "~* /something-complex/", `rewrite /something-complex/(.*) /not-root/$1 break; `, true},
proxy_pass http://upstream-name; "redirect /something to / and rewrite": {"/something", "/", "~* /something", `
sub_filter "//$host/something-complex" "//$host/not-root"; rewrite /something / break;
sub_filter_once off;`, true}, rewrite /something/(.*) /$1 break;
proxy_pass http://upstream-name;
subs_filter '<head(.*)>' '<head$1><base href="$scheme://$server_name/">' r;
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$server_name/">' 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 '<head(.*)>' '<head$1><base href="$scheme://$server_name/not-root/">' r;
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$server_name/not-root/">' r;
`, true},
} }
) )
@ -61,12 +72,12 @@ func TestBuildLocation(t *testing.T) {
for k, tc := range tmplFuncTestcases { for k, tc := range tmplFuncTestcases {
loc := &Location{ loc := &Location{
Path: tc.Path, Path: tc.Path,
Redirect: rewrite.Redirect{tc.To, tc.Rewrite}, Redirect: rewrite.Redirect{tc.Target, tc.AddBaseURL},
} }
newLoc := buildLocation(loc) newLoc := buildLocation(loc)
if tc.Location != newLoc { 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 { for k, tc := range tmplFuncTestcases {
loc := &Location{ loc := &Location{
Path: tc.Path, Path: tc.Path,
Redirect: rewrite.Redirect{tc.To, tc.Rewrite}, Redirect: rewrite.Redirect{tc.Target, tc.AddBaseURL},
Upstream: Upstream{Name: "upstream-name"}, Upstream: Upstream{Name: "upstream-name"},
} }
pp := buildProxyPass(loc) pp := buildProxyPass(loc)
if tc.ProxyPass != pp { if !strings.EqualFold(tc.ProxyPass, pp) {
t.Errorf("%s: expected \n%v \nbut returned \n%v", k, tc.ProxyPass, pp) t.Errorf("%s: expected \n'%v'\nbut returned \n'%v'", k, tc.ProxyPass, pp)
} }
} }
} }