diff --git a/internal/nginx/config/servers.go b/internal/nginx/config/servers.go index 3075370141..d7f37f6139 100644 --- a/internal/nginx/config/servers.go +++ b/internal/nginx/config/servers.go @@ -14,6 +14,8 @@ import ( var serversTemplate = gotemplate.Must(gotemplate.New("servers").Parse(serversTemplateText)) +const rootPath = "/" + func executeServers(conf state.Configuration) []byte { servers := createServers(conf.HTTPServers, conf.SSLServers) @@ -64,14 +66,20 @@ func createLocations(pathRules []state.PathRule, listenerPort int) []http.Locati lenPathRules := len(pathRules) if lenPathRules == 0 { - return []http.Location{{Path: "/", Return: &http.Return{Code: http.StatusNotFound}}} + return []http.Location{createDefaultRootLocation()} } locs := make([]http.Location, 0, lenPathRules) // FIXME(pleshakov): expand with rule.Routes + rootPathExists := false + for _, rule := range pathRules { matches := make([]httpMatch, 0, len(rule.MatchRules)) + if rule.Path == rootPath { + rootPathExists = true + } + for matchRuleIdx, r := range rule.MatchRules { m := r.GetMatch() @@ -130,6 +138,10 @@ func createLocations(pathRules []state.PathRule, listenerPort int) []http.Locati } } + if !rootPathExists { + locs = append(locs, createDefaultRootLocation()) + } + return locs } @@ -279,3 +291,10 @@ func createMatchLocation(path string) http.Location { func createPathForMatch(path string, routeIdx int) string { return fmt.Sprintf("%s_route%d", path, routeIdx) } + +func createDefaultRootLocation() http.Location { + return http.Location{ + Path: "/", + Return: &http.Return{Code: http.StatusNotFound}, + } +} diff --git a/internal/nginx/config/servers_test.go b/internal/nginx/config/servers_test.go index f95530743c..621d4d2d02 100644 --- a/internal/nginx/config/servers_test.go +++ b/internal/nginx/config/servers_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/gateway-api/apis/v1beta1" @@ -527,6 +528,173 @@ func TestCreateServers(t *testing.T) { } } +func TestCreateLocationsRootPath(t *testing.T) { + g := NewGomegaWithT(t) + + createRoute := func(rootPath bool) *v1beta1.HTTPRoute { + route := &v1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "route1", + }, + Spec: v1beta1.HTTPRouteSpec{ + Hostnames: []v1beta1.Hostname{ + "cafe.example.com", + }, + Rules: []v1beta1.HTTPRouteRule{ + { + Matches: []v1beta1.HTTPRouteMatch{ + { + Path: &v1beta1.HTTPPathMatch{ + Value: helpers.GetStringPointer("/path-1"), + }, + }, + { + Path: &v1beta1.HTTPPathMatch{ + Value: helpers.GetStringPointer("/path-2"), + }, + }, + }, + }, + }, + }, + } + + if rootPath { + route.Spec.Rules[0].Matches = append(route.Spec.Rules[0].Matches, v1beta1.HTTPRouteMatch{ + Path: &v1beta1.HTTPPathMatch{ + Value: helpers.GetStringPointer("/"), + }, + }) + } + + return route + } + + hrWithRootPathRule := createRoute(true) + + hrWithoutRootPathRule := createRoute(false) + + hrNsName := types.NamespacedName{Namespace: "test", Name: "route1"} + + fooGroup := state.BackendGroup{ + Source: hrNsName, + RuleIdx: 0, + Backends: []state.BackendRef{ + { + Name: "test_foo_80", + Valid: true, + Weight: 1, + }, + }, + } + + getPathRules := func(source *v1beta1.HTTPRoute, rootPath bool) []state.PathRule { + rules := []state.PathRule{ + { + Path: "/path-1", + MatchRules: []state.MatchRule{ + { + Source: source, + BackendGroup: fooGroup, + MatchIdx: 0, + RuleIdx: 0, + }, + }, + }, + { + Path: "/path-2", + MatchRules: []state.MatchRule{ + { + Source: source, + BackendGroup: fooGroup, + MatchIdx: 1, + RuleIdx: 0, + }, + }, + }, + } + + if rootPath { + rules = append(rules, state.PathRule{ + Path: "/", + MatchRules: []state.MatchRule{ + { + Source: source, + BackendGroup: fooGroup, + MatchIdx: 2, + RuleIdx: 0, + }, + }, + }) + } + + return rules + } + + tests := []struct { + name string + pathRules []state.PathRule + expLocations []http.Location + }{ + { + name: "path rules with no root path should generate a default 404 root location", + pathRules: getPathRules(hrWithoutRootPathRule, false), + expLocations: []http.Location{ + { + Path: "/path-1", + ProxyPass: "http://test_foo_80", + }, + { + Path: "/path-2", + ProxyPass: "http://test_foo_80", + }, + { + Path: "/", + Return: &http.Return{ + Code: http.StatusNotFound, + }, + }, + }, + }, + { + name: "path rules with a root path should not generate a default 404 root path", + pathRules: getPathRules(hrWithRootPathRule, true), + expLocations: []http.Location{ + { + Path: "/path-1", + ProxyPass: "http://test_foo_80", + }, + { + Path: "/path-2", + ProxyPass: "http://test_foo_80", + }, + { + Path: "/", + ProxyPass: "http://test_foo_80", + }, + }, + }, + { + name: "nil path rules should generate a default 404 root path", + pathRules: nil, + expLocations: []http.Location{ + { + Path: "/", + Return: &http.Return{ + Code: http.StatusNotFound, + }, + }, + }, + }, + } + + for _, test := range tests { + locs := createLocations(test.pathRules, 80) + g.Expect(locs).To(Equal(test.expLocations), fmt.Sprintf("test case: %s", test.name)) + } +} + func TestCreateReturnValForRedirectFilter(t *testing.T) { const listenerPort = 123