Skip to content

Commit

Permalink
Add rewrites annotation
Browse files Browse the repository at this point in the history
Closes nginx#63
  • Loading branch information
Julian Strobl committed Nov 8, 2016
1 parent 9226632 commit 4c0f7f8
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 12 deletions.
41 changes: 41 additions & 0 deletions examples/rewrites/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Rewrites Support

To load balance an application that requires rewrites with NGINX Ingress controllers, you need to add the **nginx.org/rewrites** annotation to your Ingress resource definition. The annotation specifies which services need rewrites. The annotation syntax is as follows:
```
nginx.org/rewrites: "serviceName=service1 rewrite=/rewrite1/[;serviceName=service2 rewrite=/rewrite2/;...]"
```

In the following example we load balance two applications, which require rewrites:
```yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: cafe-ingress
annotations:
nginx.org/rewrites: "serviceName=tea-svc rewrite=/;serviceName=coffee-svc rewrite=/beans/"
spec:
rules:
- host: cafe.example.com
http:
paths:
- path: /tea/
backend:
serviceName: tea-svc
servicePort: 80
- path: /coffee/
backend:
serviceName: coffee-svc
servicePort: 80
```
Requests to the tea service are rewritten as follows:
* /tea -> gets redirected to /tea/ first
* /tea/ -> /
* /tea/abc -> /abc
Requests to the coffee service are rewritten as follows:
* /coffee -> gets redirected to /coffee/ first
* /coffee/ -> /beans/
* /coffee/abc -> /beans/abc
46 changes: 42 additions & 4 deletions nginx-controller/nginx/configurator.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri
upstreams := make(map[string]Upstream)

wsServices := getWebsocketServices(ingEx)
rewrites := getRewrites(ingEx)
sslServices := getSSLServices(ingEx)

if ingEx.Ingress.Spec.Backend != nil {
Expand Down Expand Up @@ -120,7 +121,7 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri
upstreams[upsName] = upstream
}

loc := createLocation(pathOrDefault(path.Path), upstreams[upsName], &ingCfg, wsServices[path.Backend.ServiceName], sslServices[path.Backend.ServiceName])
loc := createLocation(pathOrDefault(path.Path), upstreams[upsName], &ingCfg, wsServices[path.Backend.ServiceName], rewrites[path.Backend.ServiceName], sslServices[path.Backend.ServiceName])
locations = append(locations, loc)

if loc.Path == "/" {
Expand All @@ -130,7 +131,7 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri

if rootLocation == false && ingEx.Ingress.Spec.Backend != nil {
upsName := getNameForUpstream(ingEx.Ingress, emptyHost, ingEx.Ingress.Spec.Backend.ServiceName)
loc := createLocation(pathOrDefault("/"), upstreams[upsName], &ingCfg, wsServices[ingEx.Ingress.Spec.Backend.ServiceName], sslServices[ingEx.Ingress.Spec.Backend.ServiceName])
loc := createLocation(pathOrDefault("/"), upstreams[upsName], &ingCfg, wsServices[ingEx.Ingress.Spec.Backend.ServiceName], rewrites[ingEx.Ingress.Spec.Backend.ServiceName], sslServices[ingEx.Ingress.Spec.Backend.ServiceName])
locations = append(locations, loc)
}

Expand All @@ -151,7 +152,7 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri

upsName := getNameForUpstream(ingEx.Ingress, emptyHost, ingEx.Ingress.Spec.Backend.ServiceName)

loc := createLocation(pathOrDefault("/"), upstreams[upsName], &ingCfg, wsServices[ingEx.Ingress.Spec.Backend.ServiceName], sslServices[ingEx.Ingress.Spec.Backend.ServiceName])
loc := createLocation(pathOrDefault("/"), upstreams[upsName], &ingCfg, wsServices[ingEx.Ingress.Spec.Backend.ServiceName], rewrites[ingEx.Ingress.Spec.Backend.ServiceName], sslServices[ingEx.Ingress.Spec.Backend.ServiceName])
locations = append(locations, loc)

server.Locations = locations
Expand Down Expand Up @@ -188,6 +189,42 @@ func getWebsocketServices(ingEx *IngressEx) map[string]bool {
return wsServices
}

func getRewrites(ingEx *IngressEx) map[string]string {
rewrites := make(map[string]string)

if services, exists := ingEx.Ingress.Annotations["nginx.org/rewrites"]; exists {
for _, svc := range strings.Split(services, ";") {
if serviceName, rewrite, err := parseRewrites(svc); err != nil {
glog.Errorf("In %v nginx.org/rewrites contains invalid declaration: %v, ignoring", ingEx.Ingress.Name, err)
} else {
rewrites[serviceName] = rewrite
}
}
}

return rewrites
}

func parseRewrites(service string) (serviceName string, rewrite string, err error) {
parts := strings.SplitN(service, " ", 2)

if len(parts) != 2 {
return "", "", fmt.Errorf("Invalid rewrite format: %s\n", service)
}

svcNameParts := strings.Split(parts[0], "=")
if len(svcNameParts) != 2 {
return "", "", fmt.Errorf("Invalid rewrite format: %s\n", svcNameParts)
}

rwPathParts := strings.Split(parts[1], "=")
if len(rwPathParts) != 2 {
return "", "", fmt.Errorf("Invalid rewrite format: %s\n", rwPathParts)
}

return svcNameParts[1], rwPathParts[1], nil
}

func getSSLServices(ingEx *IngressEx) map[string]bool {
sslServices := make(map[string]bool)

Expand All @@ -200,14 +237,15 @@ func getSSLServices(ingEx *IngressEx) map[string]bool {
return sslServices
}

func createLocation(path string, upstream Upstream, cfg *Config, websocket bool, ssl bool) Location {
func createLocation(path string, upstream Upstream, cfg *Config, websocket bool, rewrite string, ssl bool) Location {
loc := Location{
Path: path,
Upstream: upstream,
ProxyConnectTimeout: cfg.ProxyConnectTimeout,
ProxyReadTimeout: cfg.ProxyReadTimeout,
ClientMaxBodySize: cfg.ClientMaxBodySize,
Websocket: websocket,
Rewrite: rewrite,
SSL: ssl,
}

Expand Down
22 changes: 22 additions & 0 deletions nginx-controller/nginx/configurator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,25 @@ func TestPathOrDefaultReturnActual(t *testing.T) {
t.Errorf("pathOrDefault(%q) should return %q", path, path)
}
}

func TestParseRewrites(t *testing.T) {
serviceName := "coffee-svc"
serviceNamePart := "serviceName=" + serviceName
rewritePath := "/beans/"
rewritePathPart := "rewrite=" + rewritePath
rewriteService := serviceNamePart + " " + rewritePathPart

serviceNameActual, rewritePathActual, err := parseRewrites(rewriteService)
if serviceName != serviceNameActual || rewritePath != rewritePathActual || err != nil {
t.Errorf("parseRewrites(%s) should return %q, %q, nil; got %q, %q, %v", rewriteService, serviceName, rewritePath, serviceNameActual, rewritePathActual, err)
}
}

func TestParseRewritesInvalidFormat(t *testing.T) {
rewriteService := "serviceNamecoffee-svc rewrite=/"

_, _, err := parseRewrites(rewriteService)
if err == nil {
t.Errorf("parseRewrites(%s) should return error, got nil", rewriteService)
}
}
4 changes: 2 additions & 2 deletions nginx-controller/nginx/ingress.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ server {
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Proto $scheme;
{{if $location.SSL}}
proxy_pass https://{{$location.Upstream.Name}};
proxy_pass https://{{$location.Upstream.Name}}{{$location.Rewrite}};
{{else}}
proxy_pass http://{{$location.Upstream.Name}};
proxy_pass http://{{$location.Upstream.Name}}{{$location.Rewrite}};
{{end}}
}{{end}}
}{{end}}
1 change: 1 addition & 0 deletions nginx-controller/nginx/nginx.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type Location struct {
ProxyReadTimeout string
ClientMaxBodySize string
Websocket bool
Rewrite string
SSL bool
}

Expand Down
46 changes: 42 additions & 4 deletions nginx-plus-controller/nginx/configurator.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri

wsServices := getWebsocketServices(ingEx)
spServices := getSessionPersistenceServices(ingEx)
rewrites := getRewrites(ingEx)
sslServices := getSSLServices(ingEx)

if ingEx.Ingress.Spec.Backend != nil {
Expand Down Expand Up @@ -129,7 +130,7 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri
upstreams[upsName] = upstream
}

loc := createLocation(pathOrDefault(path.Path), upstreams[upsName], &ingCfg, wsServices[path.Backend.ServiceName], sslServices[path.Backend.ServiceName])
loc := createLocation(pathOrDefault(path.Path), upstreams[upsName], &ingCfg, wsServices[path.Backend.ServiceName], rewrites[path.Backend.ServiceName], sslServices[path.Backend.ServiceName])
locations = append(locations, loc)

if loc.Path == "/" {
Expand All @@ -139,7 +140,7 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri

if rootLocation == false && ingEx.Ingress.Spec.Backend != nil {
upsName := getNameForUpstream(ingEx.Ingress, emptyHost, ingEx.Ingress.Spec.Backend.ServiceName)
loc := createLocation(pathOrDefault("/"), upstreams[upsName], &ingCfg, wsServices[ingEx.Ingress.Spec.Backend.ServiceName], sslServices[ingEx.Ingress.Spec.Backend.ServiceName])
loc := createLocation(pathOrDefault("/"), upstreams[upsName], &ingCfg, wsServices[ingEx.Ingress.Spec.Backend.ServiceName], rewrites[ingEx.Ingress.Spec.Backend.ServiceName], sslServices[ingEx.Ingress.Spec.Backend.ServiceName])
locations = append(locations, loc)
}

Expand All @@ -164,7 +165,7 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri

upsName := getNameForUpstream(ingEx.Ingress, emptyHost, ingEx.Ingress.Spec.Backend.ServiceName)

loc := createLocation(pathOrDefault("/"), upstreams[upsName], &ingCfg, wsServices[ingEx.Ingress.Spec.Backend.ServiceName], sslServices[ingEx.Ingress.Spec.Backend.ServiceName])
loc := createLocation(pathOrDefault("/"), upstreams[upsName], &ingCfg, wsServices[ingEx.Ingress.Spec.Backend.ServiceName], rewrites[ingEx.Ingress.Spec.Backend.ServiceName], sslServices[ingEx.Ingress.Spec.Backend.ServiceName])
locations = append(locations, loc)

server.Locations = locations
Expand Down Expand Up @@ -201,6 +202,42 @@ func getWebsocketServices(ingEx *IngressEx) map[string]bool {
return wsServices
}

func getRewrites(ingEx *IngressEx) map[string]string {
rewrites := make(map[string]string)

if services, exists := ingEx.Ingress.Annotations["nginx.org/rewrites"]; exists {
for _, svc := range strings.Split(services, ";") {
if serviceName, rewrite, err := parseRewrites(svc); err != nil {
glog.Errorf("In %v nginx.org/rewrites contains invalid declaration: %v, ignoring", ingEx.Ingress.Name, err)
} else {
rewrites[serviceName] = rewrite
}
}
}

return rewrites
}

func parseRewrites(service string) (serviceName string, rewrite string, err error) {
parts := strings.SplitN(service, " ", 2)

if len(parts) != 2 {
return "", "", fmt.Errorf("Invalid rewrite format: %s\n", service)
}

svcNameParts := strings.Split(parts[0], "=")
if len(svcNameParts) != 2 {
return "", "", fmt.Errorf("Invalid rewrite format: %s\n", svcNameParts)
}

rwPathParts := strings.Split(parts[1], "=")
if len(rwPathParts) != 2 {
return "", "", fmt.Errorf("Invalid rewrite format: %s\n", rwPathParts)
}

return svcNameParts[1], rwPathParts[1], nil
}

func getSSLServices(ingEx *IngressEx) map[string]bool {
sslServices := make(map[string]bool)

Expand Down Expand Up @@ -244,14 +281,15 @@ func parseStickyService(service string) (serviceName string, stickyCookie string
return svcNameParts[1], parts[1], nil
}

func createLocation(path string, upstream Upstream, cfg *Config, websocket bool, ssl bool) Location {
func createLocation(path string, upstream Upstream, cfg *Config, websocket bool, rewrite string, ssl bool) Location {
loc := Location{
Path: path,
Upstream: upstream,
ProxyConnectTimeout: cfg.ProxyConnectTimeout,
ProxyReadTimeout: cfg.ProxyReadTimeout,
ClientMaxBodySize: cfg.ClientMaxBodySize,
Websocket: websocket,
Rewrite: rewrite,
SSL: ssl,
}

Expand Down
22 changes: 22 additions & 0 deletions nginx-plus-controller/nginx/configurator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,28 @@ func TestPathOrDefaultReturnActual(t *testing.T) {
}
}

func TestParseRewrites(t *testing.T) {
serviceName := "coffee-svc"
serviceNamePart := "serviceName=" + serviceName
rewritePath := "/beans/"
rewritePathPart := "rewrite=" + rewritePath
rewriteService := serviceNamePart + " " + rewritePathPart

serviceNameActual, rewritePathActual, err := parseRewrites(rewriteService)
if serviceName != serviceNameActual || rewritePath != rewritePathActual || err != nil {
t.Errorf("parseRewrites(%s) should return %q, %q, nil; got %q, %q, %v", rewriteService, serviceName, rewritePath, serviceNameActual, rewritePathActual, err)
}
}

func TestParseRewritesInvalidFormat(t *testing.T) {
rewriteService := "serviceNamecoffee-svc rewrite=/"

_, _, err := parseRewrites(rewriteService)
if err == nil {
t.Errorf("parseRewrites(%s) should return error, got nil", rewriteService)
}
}

func TestParseStickyService(t *testing.T) {
serviceName := "coffee-svc"
serviceNamePart := "serviceName=" + serviceName
Expand Down
4 changes: 2 additions & 2 deletions nginx-plus-controller/nginx/ingress.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ server {
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Proto $scheme;
{{if $location.SSL}}
proxy_pass https://{{$location.Upstream.Name}};
proxy_pass https://{{$location.Upstream.Name}}{{$location.Rewrite}};
{{else}}
proxy_pass http://{{$location.Upstream.Name}};
proxy_pass http://{{$location.Upstream.Name}}{{$location.Rewrite}};
{{end}}
}{{end}}
}{{end}}
1 change: 1 addition & 0 deletions nginx-plus-controller/nginx/nginx.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ type Location struct {
ProxyReadTimeout string
ClientMaxBodySize string
Websocket bool
Rewrite string
SSL bool
}

Expand Down

0 comments on commit 4c0f7f8

Please sign in to comment.