Add support for rewrite

This commit is contained in:
Manuel de Brito Fontes 2016-05-25 18:04:34 -03:00
parent 6968e23f8d
commit 95e85b57e3
6 changed files with 371 additions and 0 deletions

View file

@ -42,6 +42,7 @@ import (
"k8s.io/contrib/ingress/controllers/nginx/healthcheck" "k8s.io/contrib/ingress/controllers/nginx/healthcheck"
"k8s.io/contrib/ingress/controllers/nginx/nginx" "k8s.io/contrib/ingress/controllers/nginx/nginx"
"k8s.io/contrib/ingress/controllers/nginx/nginx/rewrite"
) )
const ( const (
@ -609,6 +610,12 @@ func (lbc *loadBalancerController) getUpstreamServers(ngxCfg nginx.NginxConfigur
for _, loc := range server.Locations { for _, loc := range server.Locations {
if loc.Path == rootLocation && nginxPath == rootLocation && loc.IsDefBackend { if loc.Path == rootLocation && nginxPath == rootLocation && loc.IsDefBackend {
loc.Upstream = *ups loc.Upstream = *ups
locRew, err := rewrite.ParseAnnotations(ing)
if err != nil {
glog.V(3).Infof("error parsing rewrite annotations for Ingress rule %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
}
loc.Redirect = *locRew
addLoc = false addLoc = false
continue continue
} }
@ -622,9 +629,15 @@ func (lbc *loadBalancerController) getUpstreamServers(ngxCfg nginx.NginxConfigur
} }
if addLoc { if addLoc {
locRew, err := rewrite.ParseAnnotations(ing)
if err != nil {
glog.V(3).Infof("error parsing rewrite annotations for Ingress rule %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
}
server.Locations = append(server.Locations, &nginx.Location{ server.Locations = append(server.Locations, &nginx.Location{
Path: nginxPath, Path: nginxPath,
Upstream: *ups, Upstream: *ups,
Redirect: *locRew,
}) })
} }
} }

View file

@ -16,6 +16,10 @@ limitations under the License.
package nginx package nginx
import (
"k8s.io/contrib/ingress/controllers/nginx/nginx/rewrite"
)
// IngressConfig describes an NGINX configuration // IngressConfig describes an NGINX configuration
type IngressConfig struct { type IngressConfig struct {
Upstreams []*Upstream Upstreams []*Upstream
@ -88,6 +92,7 @@ type Location struct {
Path string Path string
IsDefBackend bool IsDefBackend bool
Upstream Upstream Upstream Upstream
Redirect rewrite.Redirect
} }
// LocationByPath sorts location by path // LocationByPath sorts location by path

View file

@ -0,0 +1,84 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rewrite
import (
"strconv"
"k8s.io/kubernetes/pkg/apis/extensions"
)
const (
rewrite = "ingress-nginx.kubernetes.io/rewrite-to"
fixUrls = "ingress-nginx.kubernetes.io/fix-urls"
)
// 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
}
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]
if ok {
if b, err := strconv.ParseBool(val); err == nil {
return b
}
}
return false
}
// 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"}
}
rt := ingAnnotations(ing.GetAnnotations()).rewrite()
rw := ingAnnotations(ing.GetAnnotations()).fixUrls()
return &Redirect{
To: rt,
Rewrite: rw,
}, nil
}

View file

@ -0,0 +1,118 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rewrite
import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/util/intstr"
)
const (
defRoute = "/demo"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
func TestAnnotations(t *testing.T) {
ing := buildIngress()
r := ingAnnotations(ing.GetAnnotations()).rewrite()
if r != "" {
t.Error("Expected no redirect")
}
f := ingAnnotations(ing.GetAnnotations()).fixUrls()
if f != false {
t.Error("Expected false as fix-urls but %v was returend", f)
}
data := map[string]string{}
data[rewrite] = defRoute
data[fixUrls] = "true"
ing.SetAnnotations(data)
r = ingAnnotations(ing.GetAnnotations()).rewrite()
if r != defRoute {
t.Error("Expected %v as rewrite but %v was returend", defRoute, r)
}
f = ingAnnotations(ing.GetAnnotations()).fixUrls()
if f != true {
t.Error("Expected true as fix-urls but %v was returend", f)
}
}
func TestWithoutAnnotations(t *testing.T) {
ing := buildIngress()
_, err := ParseAnnotations(ing)
if err == nil {
t.Error("Expected error with ingress without annotations")
}
}
func TestRedirect(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[rewrite] = defRoute
ing.SetAnnotations(data)
redirect, err := ParseAnnotations(ing)
if err != nil {
t.Errorf("Uxpected error with ingress: %v", err)
}
if redirect.To != defRoute {
t.Errorf("Expected %v as redirect but returned %s", defRoute, redirect.To)
}
}

View file

@ -21,12 +21,17 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"regexp" "regexp"
"strings"
"text/template" "text/template"
"github.com/fatih/structs" "github.com/fatih/structs"
"github.com/golang/glog" "github.com/golang/glog"
) )
const (
slash = "/"
)
var ( var (
camelRegexp = regexp.MustCompile("[0-9A-Za-z]+") camelRegexp = regexp.MustCompile("[0-9A-Za-z]+")
tmplPath = "/etc/nginx/template/nginx.tmpl" tmplPath = "/etc/nginx/template/nginx.tmpl"
@ -40,6 +45,8 @@ var (
return true return true
}, },
"buildLocation": buildLocation,
"buildProxyPass": buildProxyPass,
} }
) )
@ -101,3 +108,60 @@ func toCamelCase(src string) string {
} }
return string(bytes.Join(chunks, nil)) return string(bytes.Join(chunks, nil))
} }
func buildLocation(input interface{}) string {
location, ok := input.(*Location)
if !ok {
return slash
}
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)
// }
return fmt.Sprintf("~* %s", path)
}
return path
}
func buildProxyPass(input interface{}) string {
location, ok := input.(*Location)
if !ok {
return ""
}
path := location.Path
if path == location.Redirect.To {
return fmt.Sprintf("proxy_pass http://%s;", location.Upstream.Name)
}
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 '<head(.*)>' '<head$1><base href="$scheme://$server_name%v">';
sub_filter_once off;`, location.Path)
}
if location.Redirect.To == 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(.*) %s/$1 break;
proxy_pass http://%s;
%v`, path, location.Redirect.To, location.Upstream.Name, rc)
}
// default proxy_pass
return fmt.Sprintf("proxy_pass http://%s;", location.Upstream.Name)
}

View file

@ -0,0 +1,87 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package nginx
import (
"testing"
"k8s.io/contrib/ingress/controllers/nginx/nginx/rewrite"
)
var (
tmplFuncTestcases = map[string]struct {
Path string
To string
Location string
ProxyPass string
Rewrite 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},
}
)
func TestBuildLocation(t *testing.T) {
for k, tc := range tmplFuncTestcases {
loc := &Location{
Path: tc.Path,
Redirect: rewrite.Redirect{tc.To, tc.Rewrite},
}
newLoc := buildLocation(loc)
if tc.Location != newLoc {
t.Errorf("%s: expected %v but returned %v", k, tc.Location, newLoc)
}
}
}
func TestBuildProxyPass(t *testing.T) {
for k, tc := range tmplFuncTestcases {
loc := &Location{
Path: tc.Path,
Redirect: rewrite.Redirect{tc.To, tc.Rewrite},
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)
}
}
}