Skip to content

Commit

Permalink
fix(route-provisioner): support Prefix vs Exact routing (#109)
Browse files Browse the repository at this point in the history
Signed-off-by: Ben Meier <[email protected]>
  • Loading branch information
astromechza authored Apr 15, 2024
1 parent c005dd3 commit 69c5226
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 7 deletions.
2 changes: 2 additions & 0 deletions examples/09-dns-and-route/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ This adds an additional nginx service to the compose file which contains an HTTP

By default, this listens on http://localhost:8080.

By default, this uses a `Prefix` route matching type so `/` can match `/any/request/path` but you can add a `score-compose.score.dev/route-provisioner-path-type: Exact` annotation to a Route to restrict this behavior.

## Limitations

Technically, the `dns` resource should produce random or appropriate hostnames for each dns resource. However, this implementation always generates localhost since we don't by default create real DNS names or modify the local /etc/hosts file.
Expand Down
9 changes: 6 additions & 3 deletions internal/command/default.provisioners.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,8 @@
{{ $target := (printf "%s:%d" $targetHost $targetPort) }}
{{ $hBefore := dig .Init.sk "hosts" (dict) .Shared }}
{{ $rBefore := dig .Params.host (dict) $hBefore }}
{{ $inner := dict "path" .Params.path "target" $target "port" $targetPort }}
{{ $pathType := dig "score-compose.score.dev/route-provisioner-path-type" "Prefix" (dig "annotations" (dict) (.Metadata | default (dict))) }}
{{ $inner := dict "path" .Params.path "target" $target "port" $targetPort "path_type" $pathType }}
{{ $rAfter := (merge $rBefore (dict .Uid $inner)) }}
{{ $hAfter := (merge $hBefore (dict .Params.host $rAfter)) }}
hosts: {{ $hAfter | toRawJson }}
Expand Down Expand Up @@ -363,14 +364,16 @@
}
{{ range $k, $v := $r }}
# the basic path variant, "/" or "/one/two"
location = {{ index $v "path" }} {
set $backend {{ index $v "target" }};
rewrite ^{{ index $v "path" }}(.*)$ /$1 break;
proxy_pass http://$backend;
}
{{ if not (eq (index $v "path") "/") }}
location {{ index $v "path" }}/ {
# The prefix match variants are included by default but can be excluded via 'score-compose.score.dev/route-provisioner-path-type' annotation
{{ if eq (index $v "path_type") "Prefix" }}
location {{ index $v "path" | trimSuffix "/" }}/ {
set $backend {{ index $v "target" }};
rewrite ^{{ index $v "path" }}/(.*)$ /$1 break;
proxy_pass http://$backend;
Expand Down
22 changes: 18 additions & 4 deletions internal/command/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,9 @@ resources:
port: foo
r3:
type: route
metadata:
annotations:
score-compose.score.dev/route-provisioner-path-type: Exact
params:
host: localhost2
path: /third
Expand All @@ -822,23 +825,34 @@ resources:
assert.Len(t, sd.State.Workloads, 1)
assert.Len(t, sd.State.Resources, 3)
x := sd.State.SharedState["default-provisioners-routing-instance"].(map[string]interface{})
assert.Contains(t, x["instanceServiceName"].(string), "routing-")
instanceServiceName := x["instanceServiceName"].(string)
assert.Contains(t, instanceServiceName, "routing-")
delete(x, "instanceServiceName")
assert.Equal(t, map[string]interface{}{
"default-provisioners-routing-instance": map[string]interface{}{
"hosts": map[string]interface{}{
"localhost1": map[string]interface{}{
"route.default#example.r1": map[string]interface{}{"path": "/first", "port": 8080, "target": "example-example:8080"},
"route.default#example.r2": map[string]interface{}{"path": "/second", "port": 8080, "target": "example-example:8080"},
"route.default#example.r1": map[string]interface{}{"path": "/first", "port": 8080, "target": "example-example:8080", "path_type": "Prefix"},
"route.default#example.r2": map[string]interface{}{"path": "/second", "port": 8080, "target": "example-example:8080", "path_type": "Prefix"},
},
"localhost2": map[string]interface{}{
"route.default#example.r3": map[string]interface{}{"path": "/third", "port": 8080, "target": "example-example:8080"},
"route.default#example.r3": map[string]interface{}{"path": "/third", "port": 8080, "target": "example-example:8080", "path_type": "Exact"},
},
},
"instancePort": 8080,
},
}, sd.State.SharedState)

// validate that the wildcard routes don't exist for /third
raw, err := os.ReadFile(filepath.Join(td, ".score-compose", "mounts", instanceServiceName, "nginx.conf"))
assert.NoError(t, err)
assert.Contains(t, string(raw), `location = /first`)
assert.Contains(t, string(raw), `location /first/`)
assert.Contains(t, string(raw), `location = /second`)
assert.Contains(t, string(raw), `location /second/`)
assert.Contains(t, string(raw), `location = /third`)
assert.NotContains(t, string(raw), `location /third/`)

t.Run("validate compose spec", func(t *testing.T) {
if os.Getenv("NO_DOCKER") != "" {
t.Skip("NO_DOCKER is set")
Expand Down

0 comments on commit 69c5226

Please sign in to comment.