Skip to content

Commit 6fa9c25

Browse files
committed
implementation-specific options
A new options struct has been introduced. Such a struct can be customized by providers to implement provider-specific API features. Signed-off-by: Mattia Lavacca <[email protected]>
1 parent 14f252f commit 6fa9c25

File tree

10 files changed

+115
-57
lines changed

10 files changed

+115
-57
lines changed

pkg/i2gw/provider.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ type ResourceConverter interface {
7373
ToGatewayAPI(resources InputResources) (GatewayResources, field.ErrorList)
7474
}
7575

76+
// HTTPMatchTypeOption is an option to customize the ingress implementationSpecific
77+
// match type conversion.
78+
type HTTPMatchTypeOption func(*gatewayv1beta1.HTTPPathMatch)
79+
80+
// ImplementationSpecificOptions contains all the pointers to implementation-specific
81+
// customization functions.
82+
type ImplementationSpecificOptions struct {
83+
ToHTTPRouteMatch HTTPMatchTypeOption
84+
}
85+
7686
// InputResources contains all Ingress objects, and Provider specific
7787
// custom resources.
7888
type InputResources struct {

pkg/i2gw/providers/common/converter.go

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,9 @@ import (
3030
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
3131
)
3232

33-
// ToHTTPRouteMatchOption is an option to give the providers the possibility to customize
34-
// HTTPRouteMatch support.
35-
type ToHTTPRouteMatchOption func(routePath networkingv1.HTTPIngressPath, path *field.Path) (*gatewayv1beta1.HTTPRouteMatch, *field.Error)
36-
3733
// ToGateway converts the received ingresses to i2gw.GatewayResources,
3834
// without taking into consideration any provider specific logic.
39-
func ToGateway(ingresses []networkingv1.Ingress, toHTTPRouteMatchCustom ToHTTPRouteMatchOption) (i2gw.GatewayResources, field.ErrorList) {
35+
func ToGateway(ingresses []networkingv1.Ingress, options i2gw.ImplementationSpecificOptions) (i2gw.GatewayResources, field.ErrorList) {
4036
aggregator := ingressAggregator{ruleGroups: map[ruleGroupKey]*ingressRuleGroup{}}
4137

4238
var errs field.ErrorList
@@ -47,7 +43,7 @@ func ToGateway(ingresses []networkingv1.Ingress, toHTTPRouteMatchCustom ToHTTPRo
4743
return i2gw.GatewayResources{}, errs
4844
}
4945

50-
routes, gateways, errs := aggregator.toHTTPRoutesAndGateways(toHTTPRouteMatchCustom)
46+
routes, gateways, errs := aggregator.toHTTPRoutesAndGateways(options)
5147
if len(errs) > 0 {
5248
return i2gw.GatewayResources{}, errs
5349
}
@@ -161,7 +157,7 @@ func (a *ingressAggregator) addIngressRule(namespace, name, ingressClass string,
161157
rg.rules = append(rg.rules, ingressRule{rule: rule})
162158
}
163159

164-
func (a *ingressAggregator) toHTTPRoutesAndGateways(toHTTPRouteMatchCustom ToHTTPRouteMatchOption) ([]gatewayv1beta1.HTTPRoute, []gatewayv1beta1.Gateway, field.ErrorList) {
160+
func (a *ingressAggregator) toHTTPRoutesAndGateways(options i2gw.ImplementationSpecificOptions) ([]gatewayv1beta1.HTTPRoute, []gatewayv1beta1.Gateway, field.ErrorList) {
165161
var httpRoutes []gatewayv1beta1.HTTPRoute
166162
var errors field.ErrorList
167163
listenersByNamespacedGateway := map[string][]gatewayv1beta1.Listener{}
@@ -182,7 +178,7 @@ func (a *ingressAggregator) toHTTPRoutesAndGateways(toHTTPRouteMatchCustom ToHTT
182178
}
183179
gwKey := fmt.Sprintf("%s/%s", rg.namespace, rg.ingressClass)
184180
listenersByNamespacedGateway[gwKey] = append(listenersByNamespacedGateway[gwKey], listener)
185-
httpRoute, errs := rg.toHTTPRoute(toHTTPRouteMatchCustom)
181+
httpRoute, errs := rg.toHTTPRoute(options)
186182
httpRoutes = append(httpRoutes, httpRoute)
187183
errors = append(errors, errs...)
188184
}
@@ -273,7 +269,7 @@ func (a *ingressAggregator) toHTTPRoutesAndGateways(toHTTPRouteMatchCustom ToHTT
273269
return httpRoutes, gateways, errors
274270
}
275271

276-
func (rg *ingressRuleGroup) toHTTPRoute(toHTTPRouteMatchCustom ToHTTPRouteMatchOption) (gatewayv1beta1.HTTPRoute, field.ErrorList) {
272+
func (rg *ingressRuleGroup) toHTTPRoute(options i2gw.ImplementationSpecificOptions) (gatewayv1beta1.HTTPRoute, field.ErrorList) {
277273
pathsByMatchGroup := map[pathMatchKey][]ingressPath{}
278274
var errors field.ErrorList
279275

@@ -311,11 +307,7 @@ func (rg *ingressRuleGroup) toHTTPRoute(toHTTPRouteMatchCustom ToHTTPRouteMatchO
311307
fieldPath := field.NewPath("spec", "rules").Index(path.ruleIdx).Child(path.ruleType).Child("paths").Index(path.pathIdx)
312308
var match *gatewayv1beta1.HTTPRouteMatch
313309
var err *field.Error
314-
if toHTTPRouteMatchCustom != nil {
315-
match, err = toHTTPRouteMatchCustom(path.path, fieldPath)
316-
} else {
317-
match, err = toHTTPRouteMatch(path.path, fieldPath)
318-
}
310+
match, err = toHTTPRouteMatch(path.path, fieldPath, options.ToHTTPRouteMatch)
319311
if err != nil {
320312
errors = append(errors, err)
321313
continue
@@ -358,20 +350,22 @@ func getPathMatchKey(ip ingressPath) pathMatchKey {
358350
return pathMatchKey(fmt.Sprintf("%s/%s", pathType, ip.path.Path))
359351
}
360352

361-
func toHTTPRouteMatch(routePath networkingv1.HTTPIngressPath, path *field.Path) (*gatewayv1beta1.HTTPRouteMatch, *field.Error) {
353+
func toHTTPRouteMatch(routePath networkingv1.HTTPIngressPath, path *field.Path, implementationSpecificPath i2gw.HTTPMatchTypeOption) (*gatewayv1beta1.HTTPRouteMatch, *field.Error) {
362354
pmPrefix := gatewayv1beta1.PathMatchPathPrefix
363355
pmExact := gatewayv1beta1.PathMatchExact
364356

365357
match := &gatewayv1beta1.HTTPRouteMatch{Path: &gatewayv1beta1.HTTPPathMatch{Value: &routePath.Path}}
366-
//exhaustive:ignore -explicit-exhaustive-switch
367-
// networkingv1.PathTypeImplementationSpecific is not supported here, hence it goes into default case.
368358
switch *routePath.PathType {
369359
case networkingv1.PathTypePrefix:
370360
match.Path.Type = &pmPrefix
371361
case networkingv1.PathTypeExact:
372362
match.Path.Type = &pmExact
373-
default:
374-
return nil, field.Invalid(path.Child("pathType"), routePath.PathType, fmt.Sprintf("unsupported path match type: %s", *routePath.PathType))
363+
case networkingv1.PathTypeImplementationSpecific:
364+
if implementationSpecificPath != nil {
365+
implementationSpecificPath(match.Path)
366+
} else {
367+
return nil, field.Invalid(path.Child("pathType"), routePath.PathType, fmt.Sprintf("unsupported path match type: %s", *routePath.PathType))
368+
}
375369
}
376370

377371
return match, nil

pkg/i2gw/providers/common/converter_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ func Test_ingresses2GatewaysAndHttpRoutes(t *testing.T) {
316316
for _, tc := range testCases {
317317
t.Run(tc.name, func(t *testing.T) {
318318

319-
gatewayResources, errs := ToGateway(tc.ingresses, nil)
319+
gatewayResources, errs := ToGateway(tc.ingresses, i2gw.ImplementationSpecificOptions{})
320320

321321
if len(gatewayResources.HTTPRoutes) != len(tc.expectedGatewayResources.HTTPRoutes) {
322322
t.Errorf("Expected %d HTTPRoutes, got %d: %+v",

pkg/i2gw/providers/ingressnginx/converter.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func (c *converter) ToGatewayAPI(resources i2gw.InputResources) (i2gw.GatewayRes
4545

4646
// Convert plain ingress resources to gateway resources, ignoring all
4747
// provider-specific features.
48-
gatewayResources, errs := common.ToGateway(resources.Ingresses, nil)
48+
gatewayResources, errs := common.ToGateway(resources.Ingresses, i2gw.ImplementationSpecificOptions{})
4949
if len(errs) > 0 {
5050
return i2gw.GatewayResources{}, errs
5151
}

pkg/i2gw/providers/ingressnginx/converter_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@ import (
2929
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3030
"k8s.io/apimachinery/pkg/types"
3131
"k8s.io/apimachinery/pkg/util/validation/field"
32+
"k8s.io/utils/pointer"
3233
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
3334
)
3435

3536
func Test_ToGateway(t *testing.T) {
3637
iPrefix := networkingv1.PathTypePrefix
3738
//iExact := networkingv1.PathTypeExact
3839
gPathPrefix := gatewayv1beta1.PathMatchPathPrefix
40+
isPathType := networkingv1.PathTypeImplementationSpecific
3941
//gExact := gatewayv1beta1.PathMatchExact
4042

4143
testCases := []struct {
@@ -164,6 +166,48 @@ func Test_ToGateway(t *testing.T) {
164166
},
165167
expectedErrors: field.ErrorList{},
166168
},
169+
{
170+
name: "ImplementationSpecific HTTPRouteMatching",
171+
ingresses: []networkingv1.Ingress{
172+
{
173+
ObjectMeta: metav1.ObjectMeta{
174+
Name: "implementation-specific-regex",
175+
Namespace: "default",
176+
},
177+
Spec: networkingv1.IngressSpec{
178+
IngressClassName: ptrTo("ingress-kong"),
179+
Rules: []networkingv1.IngressRule{{
180+
Host: "test.mydomain.com",
181+
IngressRuleValue: networkingv1.IngressRuleValue{
182+
HTTP: &networkingv1.HTTPIngressRuleValue{
183+
Paths: []networkingv1.HTTPIngressPath{{
184+
Path: "/~/echo/**/test",
185+
PathType: &isPathType,
186+
Backend: networkingv1.IngressBackend{
187+
Service: &networkingv1.IngressServiceBackend{
188+
Name: "test",
189+
Port: networkingv1.ServiceBackendPort{
190+
Number: 80,
191+
},
192+
},
193+
},
194+
}},
195+
},
196+
},
197+
}},
198+
},
199+
},
200+
},
201+
expectedGatewayResources: i2gw.GatewayResources{},
202+
expectedErrors: field.ErrorList{
203+
{
204+
Type: field.ErrorTypeInvalid,
205+
Field: "spec.rules[0].http.paths[0].pathType",
206+
BadValue: pointer.String("ImplementationSpecific"),
207+
Detail: "unsupported path match type: ImplementationSpecific",
208+
},
209+
},
210+
},
167211
}
168212

169213
for _, tc := range testCases {

pkg/i2gw/providers/kong/converter.go

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,9 @@ limitations under the License.
1717
package kong
1818

1919
import (
20-
"fmt"
21-
"strings"
22-
2320
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw"
2421
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common"
25-
networkingv1 "k8s.io/api/networking/v1"
2622
"k8s.io/apimachinery/pkg/util/validation/field"
27-
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
2823
)
2924

3025
// converter implements the ToGatewayAPI function of i2gw.ResourceConverter interface.
@@ -52,7 +47,9 @@ func (c *converter) ToGatewayAPI(resources i2gw.InputResources) (i2gw.GatewayRes
5247

5348
// Convert plain ingress resources to gateway resources, ignoring all
5449
// provider-specific features.
55-
gatewayResources, errs := common.ToGateway(resources.Ingresses, toHTTPRouteMatchOption)
50+
gatewayResources, errs := common.ToGateway(resources.Ingresses, i2gw.ImplementationSpecificOptions{
51+
ToHTTPRouteMatch: toHTTPRouteMatch,
52+
})
5653
if len(errs) > 0 {
5754
return i2gw.GatewayResources{}, errs
5855
}
@@ -66,29 +63,3 @@ func (c *converter) ToGatewayAPI(resources i2gw.InputResources) (i2gw.GatewayRes
6663

6764
return gatewayResources, errs
6865
}
69-
70-
func toHTTPRouteMatchOption(routePath networkingv1.HTTPIngressPath, path *field.Path) (*gatewayv1beta1.HTTPRouteMatch, *field.Error) {
71-
pmPrefix := gatewayv1beta1.PathMatchPathPrefix
72-
pmExact := gatewayv1beta1.PathMatchExact
73-
pmRegex := gatewayv1beta1.PathMatchRegularExpression
74-
75-
match := &gatewayv1beta1.HTTPRouteMatch{Path: &gatewayv1beta1.HTTPPathMatch{Value: &routePath.Path}}
76-
switch *routePath.PathType {
77-
case networkingv1.PathTypePrefix:
78-
match.Path.Type = &pmPrefix
79-
case networkingv1.PathTypeExact:
80-
match.Path.Type = &pmExact
81-
case networkingv1.PathTypeImplementationSpecific:
82-
if strings.HasPrefix(routePath.Path, "/~") {
83-
match.Path.Type = &pmRegex
84-
match.Path.Value = common.PtrTo(strings.TrimPrefix(*match.Path.Value, "/~"))
85-
} else {
86-
match.Path.Type = &pmPrefix
87-
}
88-
89-
default:
90-
return nil, field.Invalid(path.Child("pathType"), routePath.PathType, fmt.Sprintf("unsupported path match type: %s", *routePath.PathType))
91-
}
92-
93-
return match, nil
94-
}

pkg/i2gw/providers/kong/converter_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,8 +372,8 @@ func Test_ToGateway(t *testing.T) {
372372
},
373373
},
374374
HTTPRoutes: map[types.NamespacedName]gatewayv1beta1.HTTPRoute{
375-
{Namespace: "default", Name: "test-mydomain-com"}: {
376-
ObjectMeta: metav1.ObjectMeta{Name: "test-mydomain-com", Namespace: "default"},
375+
{Namespace: "default", Name: "implementation-specific-regex-test-mydomain-com"}: {
376+
ObjectMeta: metav1.ObjectMeta{Name: "implementation-specific-regex-test-mydomain-com", Namespace: "default"},
377377
Spec: gatewayv1beta1.HTTPRouteSpec{
378378
CommonRouteSpec: gatewayv1beta1.CommonRouteSpec{
379379
ParentRefs: []gatewayv1beta1.ParentReference{{

pkg/i2gw/providers/kong/header_matching_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,9 @@ func TestHeaderMatchingFeature(t *testing.T) {
246246

247247
for _, tc := range testCases {
248248
t.Run(tc.name, func(t *testing.T) {
249-
gatewayResources, errs := common.ToGateway(tc.inputResources.Ingresses, toHTTPRouteMatchOption)
249+
gatewayResources, errs := common.ToGateway(tc.inputResources.Ingresses, i2gw.ImplementationSpecificOptions{
250+
ToHTTPRouteMatch: toHTTPRouteMatch,
251+
})
250252
if len(errs) != 0 {
251253
t.Errorf("Expected no errors, got %d: %+v", len(errs), errs)
252254
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
Copyright 2023 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package kong
18+
19+
import (
20+
"strings"
21+
22+
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common"
23+
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
24+
)
25+
26+
func toHTTPRouteMatch(path *gatewayv1beta1.HTTPPathMatch) {
27+
pmPrefix := gatewayv1beta1.PathMatchPathPrefix
28+
pmRegex := gatewayv1beta1.PathMatchRegularExpression
29+
if strings.HasPrefix(*path.Value, "/~") {
30+
path.Type = &pmRegex
31+
path.Value = common.PtrTo(strings.TrimPrefix(*path.Value, "/~"))
32+
} else {
33+
path.Type = &pmPrefix
34+
}
35+
}

pkg/i2gw/providers/kong/method_matching_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,9 @@ func TestMethodMatchingFeature(t *testing.T) {
189189

190190
for _, tc := range testCases {
191191
t.Run(tc.name, func(t *testing.T) {
192-
gatewayResources, errs := common.ToGateway(tc.inputResources.Ingresses, toHTTPRouteMatchOption)
192+
gatewayResources, errs := common.ToGateway(tc.inputResources.Ingresses, i2gw.ImplementationSpecificOptions{
193+
ToHTTPRouteMatch: toHTTPRouteMatch,
194+
})
193195
if len(errs) != 0 {
194196
t.Errorf("Expected no errors, got %d: %+v", len(errs), errs)
195197
}

0 commit comments

Comments
 (0)