Parse location-modifier annotation
In nginx a modifier can be passed which decides how locations will be parsed, i.e. regex or no regex. This change allows a location modifier to be passed as an annotatione e.g. ingress.kubernetes.io/location-modifier: "~" If no location-modifier is passed but 'rewrite-target' is provided, then a default of "*~" is assumed.
This commit is contained in:
parent
37bd14dcd2
commit
8a56f42a0f
4 changed files with 98 additions and 32 deletions
|
@ -200,9 +200,15 @@ func buildLocation(input interface{}) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
path := location.Path
|
path := location.Path
|
||||||
if len(location.Rewrite.Target) > 0 && location.Rewrite.Target != path {
|
|
||||||
|
if len(location.Rewrite.Target) > 0 {
|
||||||
|
if location.Rewrite.Target == path {
|
||||||
|
// This is an invalid rewrite case
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
if path == slash {
|
if path == slash {
|
||||||
return fmt.Sprintf("~* %s", path)
|
return fmt.Sprintf("%s %s", location.Rewrite.LocationModifier, path)
|
||||||
}
|
}
|
||||||
// baseuri regex will parse basename from the given location
|
// baseuri regex will parse basename from the given location
|
||||||
baseuri := `(?<baseuri>.*)`
|
baseuri := `(?<baseuri>.*)`
|
||||||
|
@ -210,7 +216,11 @@ func buildLocation(input interface{}) string {
|
||||||
// Not treat the slash after "location path" as a part of baseuri
|
// Not treat the slash after "location path" as a part of baseuri
|
||||||
baseuri = fmt.Sprintf(`\/?%s`, baseuri)
|
baseuri = fmt.Sprintf(`\/?%s`, baseuri)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf(`~* ^%s%s`, path, baseuri)
|
return fmt.Sprintf("%s ^%s%s", location.Rewrite.LocationModifier, path, baseuri)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(location.Rewrite.LocationModifier) > 0 {
|
||||||
|
return fmt.Sprintf("%s %s", location.Rewrite.LocationModifier, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return path
|
return path
|
||||||
|
|
|
@ -32,61 +32,68 @@ import (
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
|
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
emptyTarget = ""
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// TODO: add tests for secure endpoints
|
// TODO: add tests for secure endpoints
|
||||||
tmplFuncTestcases = map[string]struct {
|
tmplFuncTestcases = map[string]struct {
|
||||||
Path string
|
Path string
|
||||||
Target string
|
Target string
|
||||||
Location string
|
LocationModifier string
|
||||||
ProxyPass string
|
Location string
|
||||||
AddBaseURL bool
|
ProxyPass string
|
||||||
BaseURLScheme string
|
AddBaseURL bool
|
||||||
|
BaseURLScheme string
|
||||||
}{
|
}{
|
||||||
"invalid redirect / to /": {"/", "/", "/", "proxy_pass http://upstream-name;", false, ""},
|
"case insensitive location modifier": {"/something", emptyTarget, "~*", "~* /something", "proxy_pass http://upstream-name;", false, ""},
|
||||||
"redirect / to /jenkins": {"/", "/jenkins", "~* /",
|
"case sensitive location modifier": {"/something", emptyTarget, "~", "~ /something", "proxy_pass http://upstream-name;", false, ""},
|
||||||
|
"invalid redirect / to /": {"/", "/", "~*", "/", "proxy_pass http://upstream-name;", false, ""},
|
||||||
|
"redirect / to /jenkins": {"/", "/jenkins", "~*", "~* /",
|
||||||
`
|
`
|
||||||
rewrite /(.*) /jenkins/$1 break;
|
rewrite /(.*) /jenkins/$1 break;
|
||||||
proxy_pass http://upstream-name;
|
proxy_pass http://upstream-name;
|
||||||
`, false, ""},
|
`, false, ""},
|
||||||
"redirect /something to /": {"/something", "/", `~* ^/something\/?(?<baseuri>.*)`, `
|
"redirect /something to /": {"/something", "/", "~*", `~* ^/something\/?(?<baseuri>.*)`, `
|
||||||
rewrite /something/(.*) /$1 break;
|
rewrite /something/(.*) /$1 break;
|
||||||
rewrite /something / break;
|
rewrite /something / break;
|
||||||
proxy_pass http://upstream-name;
|
proxy_pass http://upstream-name;
|
||||||
`, false, ""},
|
`, false, ""},
|
||||||
"redirect /end-with-slash/ to /not-root": {"/end-with-slash/", "/not-root", "~* ^/end-with-slash/(?<baseuri>.*)", `
|
"redirect /end-with-slash/ to /not-root": {"/end-with-slash/", "/not-root", "~*", "~* ^/end-with-slash/(?<baseuri>.*)", `
|
||||||
rewrite /end-with-slash/(.*) /not-root/$1 break;
|
rewrite /end-with-slash/(.*) /not-root/$1 break;
|
||||||
proxy_pass http://upstream-name;
|
proxy_pass http://upstream-name;
|
||||||
`, false, ""},
|
`, false, ""},
|
||||||
"redirect /something-complex to /not-root": {"/something-complex", "/not-root", `~* ^/something-complex\/?(?<baseuri>.*)`, `
|
"redirect /something-complex to /not-root": {"/something-complex", "/not-root", "~*", `~* ^/something-complex\/?(?<baseuri>.*)`, `
|
||||||
rewrite /something-complex/(.*) /not-root/$1 break;
|
rewrite /something-complex/(.*) /not-root/$1 break;
|
||||||
proxy_pass http://upstream-name;
|
proxy_pass http://upstream-name;
|
||||||
`, false, ""},
|
`, false, ""},
|
||||||
"redirect / to /jenkins and rewrite": {"/", "/jenkins", "~* /", `
|
"redirect / to /jenkins and rewrite": {"/", "/jenkins", "~*", "~* /", `
|
||||||
rewrite /(.*) /jenkins/$1 break;
|
rewrite /(.*) /jenkins/$1 break;
|
||||||
proxy_pass http://upstream-name;
|
proxy_pass http://upstream-name;
|
||||||
subs_filter '<head(.*)>' '<head$1><base href="$scheme://$http_host/$baseuri">' r;
|
subs_filter '<head(.*)>' '<head$1><base href="$scheme://$http_host/$baseuri">' r;
|
||||||
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$http_host/$baseuri">' r;
|
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$http_host/$baseuri">' r;
|
||||||
`, true, ""},
|
`, true, ""},
|
||||||
"redirect /something to / and rewrite": {"/something", "/", `~* ^/something\/?(?<baseuri>.*)`, `
|
"redirect /something to / and rewrite": {"/something", "/", "~*", `~* ^/something\/?(?<baseuri>.*)`, `
|
||||||
rewrite /something/(.*) /$1 break;
|
rewrite /something/(.*) /$1 break;
|
||||||
rewrite /something / break;
|
rewrite /something / break;
|
||||||
proxy_pass http://upstream-name;
|
proxy_pass http://upstream-name;
|
||||||
subs_filter '<head(.*)>' '<head$1><base href="$scheme://$http_host/something/$baseuri">' r;
|
subs_filter '<head(.*)>' '<head$1><base href="$scheme://$http_host/something/$baseuri">' r;
|
||||||
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$http_host/something/$baseuri">' r;
|
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$http_host/something/$baseuri">' r;
|
||||||
`, true, ""},
|
`, true, ""},
|
||||||
"redirect /end-with-slash/ to /not-root and rewrite": {"/end-with-slash/", "/not-root", `~* ^/end-with-slash/(?<baseuri>.*)`, `
|
"redirect /end-with-slash/ to /not-root and rewrite": {"/end-with-slash/", "/not-root", "~*", `~* ^/end-with-slash/(?<baseuri>.*)`, `
|
||||||
rewrite /end-with-slash/(.*) /not-root/$1 break;
|
rewrite /end-with-slash/(.*) /not-root/$1 break;
|
||||||
proxy_pass http://upstream-name;
|
proxy_pass http://upstream-name;
|
||||||
subs_filter '<head(.*)>' '<head$1><base href="$scheme://$http_host/end-with-slash/$baseuri">' r;
|
subs_filter '<head(.*)>' '<head$1><base href="$scheme://$http_host/end-with-slash/$baseuri">' r;
|
||||||
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$http_host/end-with-slash/$baseuri">' r;
|
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$http_host/end-with-slash/$baseuri">' r;
|
||||||
`, true, ""},
|
`, true, ""},
|
||||||
"redirect /something-complex to /not-root and rewrite": {"/something-complex", "/not-root", `~* ^/something-complex\/?(?<baseuri>.*)`, `
|
"redirect /something-complex to /not-root and rewrite": {"/something-complex", "/not-root", "~*", `~* ^/something-complex\/?(?<baseuri>.*)`, `
|
||||||
rewrite /something-complex/(.*) /not-root/$1 break;
|
rewrite /something-complex/(.*) /not-root/$1 break;
|
||||||
proxy_pass http://upstream-name;
|
proxy_pass http://upstream-name;
|
||||||
subs_filter '<head(.*)>' '<head$1><base href="$scheme://$http_host/something-complex/$baseuri">' r;
|
subs_filter '<head(.*)>' '<head$1><base href="$scheme://$http_host/something-complex/$baseuri">' r;
|
||||||
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$http_host/something-complex/$baseuri">' r;
|
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$http_host/something-complex/$baseuri">' r;
|
||||||
`, true, ""},
|
`, true, ""},
|
||||||
"redirect /something to / and rewrite with specific scheme": {"/something", "/", `~* ^/something\/?(?<baseuri>.*)`, `
|
"redirect /something to / and rewrite with specific scheme": {"/something", "/", "~*", `~* ^/something\/?(?<baseuri>.*)`, `
|
||||||
rewrite /something/(.*) /$1 break;
|
rewrite /something/(.*) /$1 break;
|
||||||
rewrite /something / break;
|
rewrite /something / break;
|
||||||
proxy_pass http://upstream-name;
|
proxy_pass http://upstream-name;
|
||||||
|
@ -118,13 +125,17 @@ func TestFormatIP(t *testing.T) {
|
||||||
func TestBuildLocation(t *testing.T) {
|
func TestBuildLocation(t *testing.T) {
|
||||||
for k, tc := range tmplFuncTestcases {
|
for k, tc := range tmplFuncTestcases {
|
||||||
loc := &ingress.Location{
|
loc := &ingress.Location{
|
||||||
Path: tc.Path,
|
Path: tc.Path,
|
||||||
Rewrite: rewrite.Redirect{Target: tc.Target, AddBaseURL: tc.AddBaseURL},
|
Rewrite: rewrite.Redirect{
|
||||||
|
Target: tc.Target,
|
||||||
|
AddBaseURL: tc.AddBaseURL,
|
||||||
|
LocationModifier: tc.LocationModifier,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,8 +143,12 @@ func TestBuildLocation(t *testing.T) {
|
||||||
func TestBuildProxyPass(t *testing.T) {
|
func TestBuildProxyPass(t *testing.T) {
|
||||||
for k, tc := range tmplFuncTestcases {
|
for k, tc := range tmplFuncTestcases {
|
||||||
loc := &ingress.Location{
|
loc := &ingress.Location{
|
||||||
Path: tc.Path,
|
Path: tc.Path,
|
||||||
Rewrite: rewrite.Redirect{Target: tc.Target, AddBaseURL: tc.AddBaseURL, BaseURLScheme: tc.BaseURLScheme},
|
Rewrite: rewrite.Redirect{
|
||||||
|
Target: tc.Target,
|
||||||
|
AddBaseURL: tc.AddBaseURL,
|
||||||
|
BaseURLScheme: tc.BaseURLScheme,
|
||||||
|
},
|
||||||
Backend: "upstream-name",
|
Backend: "upstream-name",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,23 +19,28 @@ package rewrite
|
||||||
import (
|
import (
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
"k8s.io/ingress/core/pkg/ingress/resolver"
|
"k8s.io/ingress/core/pkg/ingress/resolver"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
rewriteTo = "ingress.kubernetes.io/rewrite-target"
|
rewriteTo = "ingress.kubernetes.io/rewrite-target"
|
||||||
addBaseURL = "ingress.kubernetes.io/add-base-url"
|
locationModifier = "ingress.kubernetes.io/location-modifier"
|
||||||
baseURLScheme = "ingress.kubernetes.io/base-url-scheme"
|
defaultRewriteLocationModifier = "~*"
|
||||||
sslRedirect = "ingress.kubernetes.io/ssl-redirect"
|
addBaseURL = "ingress.kubernetes.io/add-base-url"
|
||||||
forceSSLRedirect = "ingress.kubernetes.io/force-ssl-redirect"
|
baseURLScheme = "ingress.kubernetes.io/base-url-scheme"
|
||||||
appRoot = "ingress.kubernetes.io/app-root"
|
sslRedirect = "ingress.kubernetes.io/ssl-redirect"
|
||||||
|
forceSSLRedirect = "ingress.kubernetes.io/force-ssl-redirect"
|
||||||
|
appRoot = "ingress.kubernetes.io/app-root"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Redirect describes the per location redirect config
|
// Redirect describes the per location redirect config
|
||||||
type Redirect struct {
|
type Redirect struct {
|
||||||
// Target URI where the traffic must be redirected
|
// Target URI where the traffic must be redirected
|
||||||
Target string `json:"target"`
|
Target string `json:"target"`
|
||||||
|
// Location modifier
|
||||||
|
LocationModifier string `json:"locationModifier"`
|
||||||
// AddBaseURL indicates if is required to add a base tag in the head
|
// AddBaseURL indicates if is required to add a base tag in the head
|
||||||
// of the responses from the upstream servers
|
// of the responses from the upstream servers
|
||||||
AddBaseURL bool `json:"addBaseUrl"`
|
AddBaseURL bool `json:"addBaseUrl"`
|
||||||
|
@ -92,6 +97,11 @@ func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation {
|
||||||
// rule used to rewrite the defined paths
|
// rule used to rewrite the defined paths
|
||||||
func (a rewrite) Parse(ing *extensions.Ingress) (interface{}, error) {
|
func (a rewrite) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
rt, _ := parser.GetStringAnnotation(rewriteTo, ing)
|
rt, _ := parser.GetStringAnnotation(rewriteTo, ing)
|
||||||
|
locMod, _ := parser.GetStringAnnotation(locationModifier, ing)
|
||||||
|
|
||||||
|
if rt != "" && locMod == "" {
|
||||||
|
locMod = defaultRewriteLocationModifier
|
||||||
|
}
|
||||||
sslRe, err := parser.GetBoolAnnotation(sslRedirect, ing)
|
sslRe, err := parser.GetBoolAnnotation(sslRedirect, ing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sslRe = a.backendResolver.GetDefaultBackend().SSLRedirect
|
sslRe = a.backendResolver.GetDefaultBackend().SSLRedirect
|
||||||
|
@ -103,12 +113,18 @@ func (a rewrite) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
abu, _ := parser.GetBoolAnnotation(addBaseURL, ing)
|
abu, _ := parser.GetBoolAnnotation(addBaseURL, ing)
|
||||||
bus, _ := parser.GetStringAnnotation(baseURLScheme, ing)
|
bus, _ := parser.GetStringAnnotation(baseURLScheme, ing)
|
||||||
ar, _ := parser.GetStringAnnotation(appRoot, ing)
|
ar, _ := parser.GetStringAnnotation(appRoot, ing)
|
||||||
return &Redirect{
|
|
||||||
|
redirect := &Redirect{
|
||||||
Target: rt,
|
Target: rt,
|
||||||
|
LocationModifier: locMod,
|
||||||
AddBaseURL: abu,
|
AddBaseURL: abu,
|
||||||
BaseURLScheme: bus,
|
BaseURLScheme: bus,
|
||||||
SSLRedirect: sslRe,
|
SSLRedirect: sslRe,
|
||||||
ForceSSLRedirect: fSslRe,
|
ForceSSLRedirect: fSslRe,
|
||||||
AppRoot: ar,
|
AppRoot: ar,
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
glog.V(5).Infof("created redirect %s", redirect)
|
||||||
|
|
||||||
|
return redirect, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,6 +100,31 @@ func TestRedirect(t *testing.T) {
|
||||||
if redirect.Target != defRoute {
|
if redirect.Target != defRoute {
|
||||||
t.Errorf("Expected %v as redirect but returned %s", defRoute, redirect.Target)
|
t.Errorf("Expected %v as redirect but returned %s", defRoute, redirect.Target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if redirect.LocationModifier != defaultRewriteLocationModifier {
|
||||||
|
t.Errorf("Expected %v as implicit location modifier but returned %s", defaultRewriteLocationModifier, redirect.LocationModifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegex(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
|
||||||
|
data := map[string]string{}
|
||||||
|
modifier := "~"
|
||||||
|
data[locationModifier] = modifier
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
|
i, err := NewParser(mockBackend{}).Parse(ing)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error with ingress: %v", err)
|
||||||
|
}
|
||||||
|
redirect, ok := i.(*Redirect)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a Redirect type")
|
||||||
|
}
|
||||||
|
if redirect.LocationModifier != modifier {
|
||||||
|
t.Errorf("Expected %v as location modifier but returned %s", modifier, redirect.LocationModifier)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSSLRedirect(t *testing.T) {
|
func TestSSLRedirect(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue