Skip to content

Commit 57918f6

Browse files
authored
Merge pull request #64 from endocode/jmastr/add_rewrites_annotation
Add rewrites annotation
2 parents 9226632 + 4c0f7f8 commit 57918f6

File tree

9 files changed

+175
-12
lines changed

9 files changed

+175
-12
lines changed

examples/rewrites/README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Rewrites Support
2+
3+
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:
4+
```
5+
nginx.org/rewrites: "serviceName=service1 rewrite=/rewrite1/[;serviceName=service2 rewrite=/rewrite2/;...]"
6+
```
7+
8+
In the following example we load balance two applications, which require rewrites:
9+
```yaml
10+
apiVersion: extensions/v1beta1
11+
kind: Ingress
12+
metadata:
13+
name: cafe-ingress
14+
annotations:
15+
nginx.org/rewrites: "serviceName=tea-svc rewrite=/;serviceName=coffee-svc rewrite=/beans/"
16+
spec:
17+
rules:
18+
- host: cafe.example.com
19+
http:
20+
paths:
21+
- path: /tea/
22+
backend:
23+
serviceName: tea-svc
24+
servicePort: 80
25+
- path: /coffee/
26+
backend:
27+
serviceName: coffee-svc
28+
servicePort: 80
29+
```
30+
31+
Requests to the tea service are rewritten as follows:
32+
33+
* /tea -> gets redirected to /tea/ first
34+
* /tea/ -> /
35+
* /tea/abc -> /abc
36+
37+
Requests to the coffee service are rewritten as follows:
38+
39+
* /coffee -> gets redirected to /coffee/ first
40+
* /coffee/ -> /beans/
41+
* /coffee/abc -> /beans/abc

nginx-controller/nginx/configurator.go

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri
8080
upstreams := make(map[string]Upstream)
8181

8282
wsServices := getWebsocketServices(ingEx)
83+
rewrites := getRewrites(ingEx)
8384
sslServices := getSSLServices(ingEx)
8485

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

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

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

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

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

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

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

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

192+
func getRewrites(ingEx *IngressEx) map[string]string {
193+
rewrites := make(map[string]string)
194+
195+
if services, exists := ingEx.Ingress.Annotations["nginx.org/rewrites"]; exists {
196+
for _, svc := range strings.Split(services, ";") {
197+
if serviceName, rewrite, err := parseRewrites(svc); err != nil {
198+
glog.Errorf("In %v nginx.org/rewrites contains invalid declaration: %v, ignoring", ingEx.Ingress.Name, err)
199+
} else {
200+
rewrites[serviceName] = rewrite
201+
}
202+
}
203+
}
204+
205+
return rewrites
206+
}
207+
208+
func parseRewrites(service string) (serviceName string, rewrite string, err error) {
209+
parts := strings.SplitN(service, " ", 2)
210+
211+
if len(parts) != 2 {
212+
return "", "", fmt.Errorf("Invalid rewrite format: %s\n", service)
213+
}
214+
215+
svcNameParts := strings.Split(parts[0], "=")
216+
if len(svcNameParts) != 2 {
217+
return "", "", fmt.Errorf("Invalid rewrite format: %s\n", svcNameParts)
218+
}
219+
220+
rwPathParts := strings.Split(parts[1], "=")
221+
if len(rwPathParts) != 2 {
222+
return "", "", fmt.Errorf("Invalid rewrite format: %s\n", rwPathParts)
223+
}
224+
225+
return svcNameParts[1], rwPathParts[1], nil
226+
}
227+
191228
func getSSLServices(ingEx *IngressEx) map[string]bool {
192229
sslServices := make(map[string]bool)
193230

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

203-
func createLocation(path string, upstream Upstream, cfg *Config, websocket bool, ssl bool) Location {
240+
func createLocation(path string, upstream Upstream, cfg *Config, websocket bool, rewrite string, ssl bool) Location {
204241
loc := Location{
205242
Path: path,
206243
Upstream: upstream,
207244
ProxyConnectTimeout: cfg.ProxyConnectTimeout,
208245
ProxyReadTimeout: cfg.ProxyReadTimeout,
209246
ClientMaxBodySize: cfg.ClientMaxBodySize,
210247
Websocket: websocket,
248+
Rewrite: rewrite,
211249
SSL: ssl,
212250
}
213251

nginx-controller/nginx/configurator_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,25 @@ func TestPathOrDefaultReturnActual(t *testing.T) {
1818
t.Errorf("pathOrDefault(%q) should return %q", path, path)
1919
}
2020
}
21+
22+
func TestParseRewrites(t *testing.T) {
23+
serviceName := "coffee-svc"
24+
serviceNamePart := "serviceName=" + serviceName
25+
rewritePath := "/beans/"
26+
rewritePathPart := "rewrite=" + rewritePath
27+
rewriteService := serviceNamePart + " " + rewritePathPart
28+
29+
serviceNameActual, rewritePathActual, err := parseRewrites(rewriteService)
30+
if serviceName != serviceNameActual || rewritePath != rewritePathActual || err != nil {
31+
t.Errorf("parseRewrites(%s) should return %q, %q, nil; got %q, %q, %v", rewriteService, serviceName, rewritePath, serviceNameActual, rewritePathActual, err)
32+
}
33+
}
34+
35+
func TestParseRewritesInvalidFormat(t *testing.T) {
36+
rewriteService := "serviceNamecoffee-svc rewrite=/"
37+
38+
_, _, err := parseRewrites(rewriteService)
39+
if err == nil {
40+
t.Errorf("parseRewrites(%s) should return error, got nil", rewriteService)
41+
}
42+
}

nginx-controller/nginx/ingress.tmpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ server {
4040
proxy_set_header X-Forwarded-Port $server_port;
4141
proxy_set_header X-Forwarded-Proto $scheme;
4242
{{if $location.SSL}}
43-
proxy_pass https://{{$location.Upstream.Name}};
43+
proxy_pass https://{{$location.Upstream.Name}}{{$location.Rewrite}};
4444
{{else}}
45-
proxy_pass http://{{$location.Upstream.Name}};
45+
proxy_pass http://{{$location.Upstream.Name}}{{$location.Rewrite}};
4646
{{end}}
4747
}{{end}}
4848
}{{end}}

nginx-controller/nginx/nginx.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ type Location struct {
5353
ProxyReadTimeout string
5454
ClientMaxBodySize string
5555
Websocket bool
56+
Rewrite string
5657
SSL bool
5758
}
5859

nginx-plus-controller/nginx/configurator.go

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri
8787

8888
wsServices := getWebsocketServices(ingEx)
8989
spServices := getSessionPersistenceServices(ingEx)
90+
rewrites := getRewrites(ingEx)
9091
sslServices := getSSLServices(ingEx)
9192

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

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

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

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

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

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

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

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

205+
func getRewrites(ingEx *IngressEx) map[string]string {
206+
rewrites := make(map[string]string)
207+
208+
if services, exists := ingEx.Ingress.Annotations["nginx.org/rewrites"]; exists {
209+
for _, svc := range strings.Split(services, ";") {
210+
if serviceName, rewrite, err := parseRewrites(svc); err != nil {
211+
glog.Errorf("In %v nginx.org/rewrites contains invalid declaration: %v, ignoring", ingEx.Ingress.Name, err)
212+
} else {
213+
rewrites[serviceName] = rewrite
214+
}
215+
}
216+
}
217+
218+
return rewrites
219+
}
220+
221+
func parseRewrites(service string) (serviceName string, rewrite string, err error) {
222+
parts := strings.SplitN(service, " ", 2)
223+
224+
if len(parts) != 2 {
225+
return "", "", fmt.Errorf("Invalid rewrite format: %s\n", service)
226+
}
227+
228+
svcNameParts := strings.Split(parts[0], "=")
229+
if len(svcNameParts) != 2 {
230+
return "", "", fmt.Errorf("Invalid rewrite format: %s\n", svcNameParts)
231+
}
232+
233+
rwPathParts := strings.Split(parts[1], "=")
234+
if len(rwPathParts) != 2 {
235+
return "", "", fmt.Errorf("Invalid rewrite format: %s\n", rwPathParts)
236+
}
237+
238+
return svcNameParts[1], rwPathParts[1], nil
239+
}
240+
204241
func getSSLServices(ingEx *IngressEx) map[string]bool {
205242
sslServices := make(map[string]bool)
206243

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

247-
func createLocation(path string, upstream Upstream, cfg *Config, websocket bool, ssl bool) Location {
284+
func createLocation(path string, upstream Upstream, cfg *Config, websocket bool, rewrite string, ssl bool) Location {
248285
loc := Location{
249286
Path: path,
250287
Upstream: upstream,
251288
ProxyConnectTimeout: cfg.ProxyConnectTimeout,
252289
ProxyReadTimeout: cfg.ProxyReadTimeout,
253290
ClientMaxBodySize: cfg.ClientMaxBodySize,
254291
Websocket: websocket,
292+
Rewrite: rewrite,
255293
SSL: ssl,
256294
}
257295

nginx-plus-controller/nginx/configurator_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,28 @@ func TestPathOrDefaultReturnActual(t *testing.T) {
1919
}
2020
}
2121

22+
func TestParseRewrites(t *testing.T) {
23+
serviceName := "coffee-svc"
24+
serviceNamePart := "serviceName=" + serviceName
25+
rewritePath := "/beans/"
26+
rewritePathPart := "rewrite=" + rewritePath
27+
rewriteService := serviceNamePart + " " + rewritePathPart
28+
29+
serviceNameActual, rewritePathActual, err := parseRewrites(rewriteService)
30+
if serviceName != serviceNameActual || rewritePath != rewritePathActual || err != nil {
31+
t.Errorf("parseRewrites(%s) should return %q, %q, nil; got %q, %q, %v", rewriteService, serviceName, rewritePath, serviceNameActual, rewritePathActual, err)
32+
}
33+
}
34+
35+
func TestParseRewritesInvalidFormat(t *testing.T) {
36+
rewriteService := "serviceNamecoffee-svc rewrite=/"
37+
38+
_, _, err := parseRewrites(rewriteService)
39+
if err == nil {
40+
t.Errorf("parseRewrites(%s) should return error, got nil", rewriteService)
41+
}
42+
}
43+
2244
func TestParseStickyService(t *testing.T) {
2345
serviceName := "coffee-svc"
2446
serviceNamePart := "serviceName=" + serviceName

nginx-plus-controller/nginx/ingress.tmpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ server {
4545
proxy_set_header X-Forwarded-Port $server_port;
4646
proxy_set_header X-Forwarded-Proto $scheme;
4747
{{if $location.SSL}}
48-
proxy_pass https://{{$location.Upstream.Name}};
48+
proxy_pass https://{{$location.Upstream.Name}}{{$location.Rewrite}};
4949
{{else}}
50-
proxy_pass http://{{$location.Upstream.Name}};
50+
proxy_pass http://{{$location.Upstream.Name}}{{$location.Rewrite}};
5151
{{end}}
5252
}{{end}}
5353
}{{end}}

nginx-plus-controller/nginx/nginx.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ type Location struct {
7676
ProxyReadTimeout string
7777
ClientMaxBodySize string
7878
Websocket bool
79+
Rewrite string
7980
SSL bool
8081
}
8182

0 commit comments

Comments
 (0)