Skip to content

Commit be5ec5b

Browse files
authored
Merge pull request #4255 from shuqz/shuqz-httproute
[feat gw-api]support httproute filter type - RequestRedirect
2 parents fbc6b19 + e1fffde commit be5ec5b

File tree

3 files changed

+215
-0
lines changed

3 files changed

+215
-0
lines changed

pkg/gateway/model/model_build_listener.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,21 @@ func (l listenerBuilderImpl) buildListenerRules(stack core.Stack, ls *elbv2model
201201
})
202202
}
203203
actions := buildL7ListenerActions(targetGroupTuples)
204+
205+
// configure actions based on filters
206+
switch route.GetRouteKind() {
207+
case routeutils.HTTPRouteKind:
208+
httpRule := rule.GetRawRouteRule().(*gwv1.HTTPRouteRule)
209+
if len(httpRule.Filters) > 0 {
210+
finalActions, err := routeutils.BuildHttpRuleActionsBasedOnFilter(httpRule.Filters)
211+
if err != nil {
212+
return err
213+
}
214+
actions = finalActions
215+
}
216+
// TODO: add case for GRPC
217+
}
218+
204219
albRules = append(albRules, elbv2model.Rule{
205220
Conditions: conditionsList,
206221
Actions: actions,

pkg/gateway/routeutils/route_rule_condition.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package routeutils
22

33
import (
4+
"fmt"
45
"github.com/pkg/errors"
56
elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2"
67
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
@@ -144,3 +145,87 @@ func buildGrpcRouteRuleConditions(matches RouteRule) ([][]elbv2model.RuleConditi
144145
var conditions [][]elbv2model.RuleCondition
145146
return conditions, nil
146147
}
148+
149+
func BuildHttpRuleActionsBasedOnFilter(filters []gwv1.HTTPRouteFilter) ([]elbv2model.Action, error) {
150+
for _, filter := range filters {
151+
switch filter.Type {
152+
case gwv1.HTTPRouteFilterRequestHeaderModifier:
153+
// TODO: decide behavior for request header modifier
154+
case gwv1.HTTPRouteFilterRequestRedirect:
155+
return buildHttpRedirectAction(filter.RequestRedirect)
156+
case gwv1.HTTPRouteFilterResponseHeaderModifier:
157+
// TODO: decide behavior for response header modifier
158+
}
159+
}
160+
return nil, nil
161+
}
162+
163+
// buildHttpRedirectAction configure filter attributes to RedirectActionConfig
164+
// gateway api has no attribute to specify query
165+
func buildHttpRedirectAction(filter *gwv1.HTTPRequestRedirectFilter) ([]elbv2model.Action, error) {
166+
isComponentSpecified := false
167+
var statusCode string
168+
if filter.StatusCode != nil {
169+
statusCodeStr := fmt.Sprintf("HTTP_%d", *filter.StatusCode)
170+
statusCode = statusCodeStr
171+
}
172+
173+
var port *string
174+
if filter.Port != nil {
175+
portStr := fmt.Sprintf("%d", *filter.Port)
176+
port = &portStr
177+
isComponentSpecified = true
178+
}
179+
180+
var protocol *string
181+
if filter.Scheme != nil {
182+
upperScheme := strings.ToUpper(*filter.Scheme)
183+
if upperScheme != "HTTP" && upperScheme != "HTTPS" {
184+
return nil, errors.Errorf("unsupported redirect scheme: %v", upperScheme)
185+
}
186+
protocol = &upperScheme
187+
isComponentSpecified = true
188+
}
189+
190+
var path *string
191+
if filter.Path != nil {
192+
if filter.Path.ReplaceFullPath != nil {
193+
pathValue := *filter.Path.ReplaceFullPath
194+
if strings.ContainsAny(pathValue, "*?") {
195+
return nil, errors.Errorf("ReplaceFullPath shouldn't contain wildcards: %v", pathValue)
196+
}
197+
path = filter.Path.ReplaceFullPath
198+
isComponentSpecified = true
199+
} else if filter.Path.ReplacePrefixMatch != nil {
200+
pathValue := *filter.Path.ReplacePrefixMatch
201+
if strings.ContainsAny(pathValue, "*?") {
202+
return nil, errors.Errorf("ReplacePrefixMatch shouldn't contain wildcards: %v", pathValue)
203+
}
204+
processedPath := fmt.Sprintf("%s/*", pathValue)
205+
path = &processedPath
206+
isComponentSpecified = true
207+
}
208+
}
209+
210+
var hostname *string
211+
if filter.Hostname != nil {
212+
hostname = (*string)(filter.Hostname)
213+
isComponentSpecified = true
214+
}
215+
216+
if !isComponentSpecified {
217+
return nil, errors.Errorf("To avoid a redirect loop, you must modify at least one of the following components: protocol, port, hostname or path.")
218+
}
219+
220+
action := elbv2model.Action{
221+
Type: elbv2model.ActionTypeRedirect,
222+
RedirectConfig: &elbv2model.RedirectActionConfig{
223+
Host: hostname,
224+
Path: path,
225+
Port: port,
226+
Protocol: protocol,
227+
StatusCode: statusCode,
228+
},
229+
}
230+
return []elbv2model.Action{action}, nil
231+
}

pkg/gateway/routeutils/route_rule_condition_test.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,3 +276,118 @@ func Test_buildHttpMethodCondition(t *testing.T) {
276276
})
277277
}
278278
}
279+
280+
func Test_buildHttpRedirectAction(t *testing.T) {
281+
282+
scheme := "https"
283+
expectedScheme := "HTTPS"
284+
invalidScheme := "invalid"
285+
hostname := "example.com"
286+
port := int32(80)
287+
portString := "80"
288+
statusCode := 301
289+
replaceFullPath := "/new-path"
290+
replacePrefixPath := "/new-prefix-path"
291+
replacePrefixPathAfterProcessing := "/new-prefix-path/*"
292+
invalidPath := "/invalid-path*"
293+
294+
tests := []struct {
295+
name string
296+
filter *gwv1.HTTPRequestRedirectFilter
297+
want []elbv2model.Action
298+
wantErr bool
299+
}{
300+
{
301+
name: "redirect with all fields provided",
302+
filter: &gwv1.HTTPRequestRedirectFilter{
303+
Scheme: &scheme,
304+
Hostname: (*gwv1.PreciseHostname)(&hostname),
305+
Port: (*gwv1.PortNumber)(&port),
306+
StatusCode: &statusCode,
307+
Path: &gwv1.HTTPPathModifier{
308+
Type: gwv1.FullPathHTTPPathModifier,
309+
ReplaceFullPath: &replaceFullPath,
310+
},
311+
},
312+
want: []elbv2model.Action{
313+
{
314+
Type: elbv2model.ActionTypeRedirect,
315+
RedirectConfig: &elbv2model.RedirectActionConfig{
316+
Host: &hostname,
317+
Path: &replaceFullPath,
318+
Port: &portString,
319+
Protocol: &expectedScheme,
320+
StatusCode: "HTTP_301",
321+
},
322+
},
323+
},
324+
wantErr: false,
325+
},
326+
{
327+
name: "redirect with prefix match",
328+
filter: &gwv1.HTTPRequestRedirectFilter{
329+
Path: &gwv1.HTTPPathModifier{
330+
Type: gwv1.PrefixMatchHTTPPathModifier,
331+
ReplacePrefixMatch: &replacePrefixPath,
332+
},
333+
},
334+
want: []elbv2model.Action{
335+
{
336+
Type: elbv2model.ActionTypeRedirect,
337+
RedirectConfig: &elbv2model.RedirectActionConfig{
338+
Path: &replacePrefixPathAfterProcessing,
339+
},
340+
},
341+
},
342+
wantErr: false,
343+
},
344+
{
345+
name: "redirect with no component provided",
346+
filter: &gwv1.HTTPRequestRedirectFilter{},
347+
want: nil,
348+
wantErr: true,
349+
},
350+
{
351+
name: "invalid scheme provided",
352+
filter: &gwv1.HTTPRequestRedirectFilter{
353+
Scheme: &invalidScheme,
354+
},
355+
want: nil,
356+
wantErr: true,
357+
},
358+
{
359+
name: "path with wildcards in ReplaceFullPath",
360+
filter: &gwv1.HTTPRequestRedirectFilter{
361+
Path: &gwv1.HTTPPathModifier{
362+
Type: gwv1.FullPathHTTPPathModifier,
363+
ReplaceFullPath: &invalidPath,
364+
},
365+
},
366+
want: nil,
367+
wantErr: true,
368+
},
369+
{
370+
name: "path with wildcards in ReplacePrefixMatch",
371+
filter: &gwv1.HTTPRequestRedirectFilter{
372+
Path: &gwv1.HTTPPathModifier{
373+
Type: gwv1.PrefixMatchHTTPPathModifier,
374+
ReplacePrefixMatch: &invalidPath,
375+
},
376+
},
377+
want: nil,
378+
wantErr: true,
379+
},
380+
}
381+
382+
for _, tt := range tests {
383+
t.Run(tt.name, func(t *testing.T) {
384+
got, err := buildHttpRedirectAction(tt.filter)
385+
if tt.wantErr {
386+
assert.Error(t, err)
387+
return
388+
}
389+
assert.NoError(t, err)
390+
assert.Equal(t, tt.want, got)
391+
})
392+
}
393+
}

0 commit comments

Comments
 (0)