Skip to content

Commit

Permalink
translate HTTPRoute to expression based routes
Browse files Browse the repository at this point in the history
  • Loading branch information
randmonkey committed May 8, 2023
1 parent aad1dbb commit 40c1c8b
Show file tree
Hide file tree
Showing 12 changed files with 486 additions and 227 deletions.
2 changes: 2 additions & 0 deletions internal/dataplane/parser/translate_errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package parser

import "errors"

// REVIEW: use error variables in translator package instead?

var (
errRouteValidationNoRules = errors.New("no rules provided")
errRouteValidationQueryParamMatchesUnsupported = errors.New("query param matches are not yet supported")
Expand Down
152 changes: 29 additions & 123 deletions internal/dataplane/parser/translate_httproute.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package parser

import (
"fmt"
pathlib "path"
"strings"

"github.com/kong/go-kong/kong"
Expand Down Expand Up @@ -85,7 +84,7 @@ func (p *Parser) ingressRulesFromHTTPRouteWithCombinedServiceRoutes(httproute *g

// generate the routes for the service and attach them to the service
for _, kongRouteTranslation := range kongServiceTranslation.KongRoutes {
routes, err := generateKongRouteFromTranslation(httproute, kongRouteTranslation, p.flagEnabledRegexPathPrefix)
routes, err := generateKongRouteFromTranslation(httproute, kongRouteTranslation, p.flagEnabledRegexPathPrefix, p.featureEnabledExpressionRoutes)
if err != nil {
return err
}
Expand Down Expand Up @@ -134,6 +133,17 @@ func (p *Parser) ingressRulesFromHTTPRouteLegacyFallback(httproute *gatewayv1bet
// Translate HTTPRoute - Utils
// -----------------------------------------------------------------------------

// getHTTPRouteHostnamesAsSliceOfStrings translates the hostnames defined in an
// HTTPRoute specification into a []*string slice, which is the type required by translating to matchers
// in expression based routes.
func getHTTPRouteHostnamesAsSliceOfStrings(httproute *gatewayv1beta1.HTTPRoute) []string {
hostnames := make([]string, 0, len(httproute.Spec.Hostnames))
for _, hostname := range httproute.Spec.Hostnames {
hostnames = append(hostnames, string(hostname))
}
return hostnames
}

// getHTTPRouteHostnamesAsSliceOfStringPointers translates the hostnames defined
// in an HTTPRoute specification into a []*string slice, which is the type required
// by kong.Route{}.
Expand Down Expand Up @@ -221,11 +231,26 @@ func generateKongRouteFromTranslation(
httproute *gatewayv1beta1.HTTPRoute,
translation translators.KongRouteTranslation,
addRegexPrefix bool,
expressionRoutes bool,
) ([]kongstate.Route, error) {
// gather the k8s object information and hostnames from the httproute
objectInfo := util.FromK8sObject(httproute)
tags := util.GenerateTagsForObject(httproute)

// translate to expression based routes when expressionRoutes is enabled.
if expressionRoutes {
// get the hostnames from the HTTPRoute
hostnames := getHTTPRouteHostnamesAsSliceOfStrings(httproute)
return translators.GenerateKongExpressionRoutesFromHTTPRouteMatches(
translation.Name,
translation.Matches,
translation.Filters,
objectInfo,
hostnames,
tags,
)
}

// get the hostnames from the HTTPRoute
hostnames := getHTTPRouteHostnamesAsSliceOfStringPointers(httproute)

Expand Down Expand Up @@ -306,7 +331,7 @@ func generateKongRoutesFromHTTPRouteMatches(

// if the redirect filter has not been set, we still need to set the route plugins
if !hasRedirectFilter {
plugins := generatePluginsFromHTTPRouteFilters(filters, "", tags)
plugins := translators.GeneratePluginsFromHTTPRouteFilters(filters, "", tags)
r.Plugins = append(r.Plugins, plugins...)
routes = []kongstate.Route{r}
}
Expand Down Expand Up @@ -356,7 +381,7 @@ func getRoutesFromMatches(matches []gatewayv1beta1.HTTPRouteMatch,
}

// generate kong plugins from rule.filters
plugins := generatePluginsFromHTTPRouteFilters(filters, path, tags)
plugins := translators.GeneratePluginsFromHTTPRouteFilters(filters, path, tags)
matchRoute.Plugins = append(matchRoute.Plugins, plugins...)

routes = append(routes, *route)
Expand Down Expand Up @@ -435,125 +460,6 @@ func generateKongstateHTTPRoute(routeName string, ingressObjectInfo util.K8sObje
return r
}

// generatePluginsFromHTTPRouteFilters converts HTTPRouteFilter into Kong plugins.
// path is the parameter to be used by the redirect plugin, to perform redirection.
func generatePluginsFromHTTPRouteFilters(filters []gatewayv1beta1.HTTPRouteFilter, path string, tags []*string) []kong.Plugin {
kongPlugins := make([]kong.Plugin, 0)
if len(filters) == 0 {
return kongPlugins
}

for _, filter := range filters {
switch filter.Type {
case gatewayv1beta1.HTTPRouteFilterRequestHeaderModifier:
kongPlugins = append(kongPlugins, generateRequestHeaderModifierKongPlugin(filter.RequestHeaderModifier))

case gatewayv1beta1.HTTPRouteFilterRequestRedirect:
kongPlugins = append(kongPlugins, generateRequestRedirectKongPlugin(filter.RequestRedirect, path)...)

case gatewayv1beta1.HTTPRouteFilterExtensionRef,
gatewayv1beta1.HTTPRouteFilterRequestMirror,
gatewayv1beta1.HTTPRouteFilterResponseHeaderModifier,
gatewayv1beta1.HTTPRouteFilterURLRewrite:
// not supported
}
}
for _, p := range kongPlugins {
// This plugin is derived from an HTTPRoute filter, not a KongPlugin, so we apply tags indicating that
// HTTPRoute as the parent Kubernetes resource for these generated plugins.
p.Tags = tags
}

return kongPlugins
}

func generateRequestRedirectKongPlugin(modifier *gatewayv1beta1.HTTPRequestRedirectFilter, path string) []kong.Plugin {
plugins := make([]kong.Plugin, 2)
plugins[0] = kong.Plugin{
Name: kong.String("request-termination"),
Config: kong.Configuration{
"status_code": modifier.StatusCode,
},
}

var locationHeader string
scheme := "http"
port := 80

if modifier.Scheme != nil {
scheme = *modifier.Scheme
}
if modifier.Port != nil {
port = int(*modifier.Port)
}
if modifier.Path != nil && modifier.Path.Type == gatewayv1beta1.FullPathHTTPPathModifier && modifier.Path.ReplaceFullPath != nil {
// only ReplaceFullPath currently supported
path = *modifier.Path.ReplaceFullPath
}
if modifier.Hostname != nil {
locationHeader = fmt.Sprintf("Location: %s://%s", scheme, pathlib.Join(fmt.Sprintf("%s:%d", *modifier.Hostname, port), path))
} else {
locationHeader = fmt.Sprintf("Location: %s", path)
}

plugins[1] = kong.Plugin{
Name: kong.String("response-transformer"),
Config: kong.Configuration{
"add": map[string][]string{
"headers": {locationHeader},
},
},
}

return plugins
}

// generateRequestHeaderModifierKongPlugin converts a gatewayv1beta1.HTTPRequestHeaderFilter into a
// kong.Plugin of type request-transformer.
func generateRequestHeaderModifierKongPlugin(modifier *gatewayv1beta1.HTTPHeaderFilter) kong.Plugin {
plugin := kong.Plugin{
Name: kong.String("request-transformer"),
Config: make(kong.Configuration),
}

// modifier.Set is converted to a pair composed of "replace" and "add"
if modifier.Set != nil {
setModifiers := make([]string, 0, len(modifier.Set))
for _, s := range modifier.Set {
setModifiers = append(setModifiers, kongHeaderFormatter(s))
}
plugin.Config["replace"] = map[string][]string{
"headers": setModifiers,
}
plugin.Config["add"] = map[string][]string{
"headers": setModifiers,
}
}

// modifier.Add is converted to "append"
if modifier.Add != nil {
appendModifiers := make([]string, 0, len(modifier.Add))
for _, a := range modifier.Add {
appendModifiers = append(appendModifiers, kongHeaderFormatter(a))
}
plugin.Config["append"] = map[string][]string{
"headers": appendModifiers,
}
}

if modifier.Remove != nil {
plugin.Config["remove"] = map[string][]string{
"headers": modifier.Remove,
}
}

return plugin
}

func kongHeaderFormatter(header gatewayv1beta1.HTTPHeader) string {
return fmt.Sprintf("%s:%s", header.Name, header.Value)
}

func httpBackendRefsToBackendRefs(httpBackendRef []gatewayv1beta1.HTTPBackendRef) []gatewayv1beta1.BackendRef {
backendRefs := make([]gatewayv1beta1.BackendRef, 0, len(httpBackendRef))

Expand Down
92 changes: 0 additions & 92 deletions internal/dataplane/parser/translate_httproute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1352,98 +1352,6 @@ func TestGetHTTPRouteHostnamesAsSliceOfStringPointers(t *testing.T) {
}
}

func TestGeneratePluginsFromHTTPRouteFilters(t *testing.T) {
testCases := []struct {
name string
filters []gatewayv1beta1.HTTPRouteFilter
path string
expectedPlugins []kong.Plugin
}{
{
name: "no filters",
filters: []gatewayv1beta1.HTTPRouteFilter{},
expectedPlugins: []kong.Plugin{},
},
{
name: "request header modifier",
filters: []gatewayv1beta1.HTTPRouteFilter{
{
Type: gatewayv1beta1.HTTPRouteFilterRequestHeaderModifier,
RequestHeaderModifier: &gatewayv1beta1.HTTPHeaderFilter{
Set: []gatewayv1beta1.HTTPHeader{
{
Name: "header-to-set",
Value: "bar",
},
},
Remove: []string{"header-to-remove"},
},
},
},
expectedPlugins: []kong.Plugin{
{
Name: kong.String("request-transformer"),
Config: kong.Configuration{
"add": map[string][]string{
"headers": {
"header-to-set:bar",
},
},
"remove": map[string][]string{
"headers": {
"header-to-remove",
},
},
"replace": map[string][]string{
"headers": {
"header-to-set:bar",
},
},
},
},
},
},
{
name: "request redirect modifier",
filters: []gatewayv1beta1.HTTPRouteFilter{
{
Type: gatewayv1beta1.HTTPRouteFilterRequestRedirect,
RequestRedirect: &gatewayv1beta1.HTTPRequestRedirectFilter{
Hostname: (*gatewayv1beta1.PreciseHostname)(lo.ToPtr("example.org")),
StatusCode: lo.ToPtr(302),
},
},
},
path: "/test",
expectedPlugins: []kong.Plugin{
{
Name: kong.String("request-termination"),
Config: kong.Configuration{
"status_code": lo.ToPtr(302),
},
},
{
Name: kong.String("response-transformer"),
Config: kong.Configuration{
"add": map[string][]string{
"headers": {
"Location: http://example.org:80/test",
},
},
},
},
},
},
}

for _, tc := range testCases {
plugins := generatePluginsFromHTTPRouteFilters(tc.filters, tc.path, nil)
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expectedPlugins, plugins)
})
}
}

func TestIngressRulesFromHTTPRoutes_RegexPrefix(t *testing.T) {
fakestore, err := store.NewFakeStore(store.FakeObjects{})
require.NoError(t, err)
Expand Down
Loading

0 comments on commit 40c1c8b

Please sign in to comment.