Skip to content

Commit 17709b7

Browse files
committed
Ensures Gateway-API object fields obey kubebuilder max item validation
1 parent 0922f03 commit 17709b7

File tree

5 files changed

+183
-7
lines changed

5 files changed

+183
-7
lines changed

go.mod

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ toolchain go1.24.6
66

77
require (
88
github.com/GoogleCloudPlatform/gke-gateway-api v1.3.0
9-
github.com/getkin/kin-openapi v0.124.0
9+
github.com/getkin/kin-openapi v0.133.0
1010
github.com/google/go-cmp v0.7.0
1111
github.com/kong/kubernetes-ingress-controller/v2 v2.12.3
1212
github.com/olekukonko/tablewriter v0.0.5
1313
github.com/samber/lo v1.39.0
1414
github.com/spf13/cobra v1.9.1
1515
github.com/stretchr/testify v1.11.0
16+
golang.org/x/tools v0.36.0
1617
istio.io/api v1.20.0
1718
k8s.io/api v0.34.1
1819
k8s.io/apimachinery v0.34.1
@@ -27,17 +28,20 @@ require (
2728
require (
2829
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
2930
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
30-
github.com/invopop/yaml v0.2.0 // indirect
3131
github.com/mattn/go-runewidth v0.0.15 // indirect
3232
github.com/moby/term v0.5.0 // indirect
3333
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
34+
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
35+
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
3436
github.com/perimeterx/marshmallow v1.1.5 // indirect
3537
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
3638
github.com/rivo/uniseg v0.2.0 // indirect
39+
github.com/woodsbury/decimal128 v1.3.0 // indirect
3740
github.com/x448/float16 v0.8.4 // indirect
3841
go.yaml.in/yaml/v2 v2.4.2 // indirect
3942
go.yaml.in/yaml/v3 v3.0.4 // indirect
4043
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
44+
golang.org/x/mod v0.27.0 // indirect
4145
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
4246
sigs.k8s.io/randfill v1.0.0 // indirect
4347
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
@@ -66,6 +70,7 @@ require (
6670
github.com/spf13/pflag v1.0.7 // indirect
6771
golang.org/x/net v0.43.0 // indirect
6872
golang.org/x/oauth2 v0.30.0 // indirect
73+
golang.org/x/sync v0.16.0 // indirect
6974
golang.org/x/sys v0.35.0 // indirect
7075
golang.org/x/term v0.34.0 // indirect
7176
golang.org/x/text v0.28.0 // indirect

go.sum

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjT
1919
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
2020
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
2121
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
22-
github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M=
23-
github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM=
22+
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
23+
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
2424
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
2525
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
2626
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
@@ -50,8 +50,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
5050
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
5151
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
5252
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
53-
github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY=
54-
github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
5553
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
5654
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
5755
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -83,6 +81,10 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9
8381
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
8482
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
8583
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
84+
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
85+
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
86+
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
87+
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
8688
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
8789
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
8890
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
@@ -123,6 +125,8 @@ github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQ
123125
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
124126
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
125127
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
128+
github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0=
129+
github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds=
126130
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
127131
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
128132
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -142,6 +146,8 @@ golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0
142146
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
143147
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
144148
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
149+
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
150+
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
145151
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
146152
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
147153
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -153,6 +159,8 @@ golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKl
153159
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
154160
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
155161
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
162+
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
163+
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
156164
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
157165
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
158166
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -188,7 +196,6 @@ gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnf
188196
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
189197
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
190198
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
191-
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
192199
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
193200
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
194201
istio.io/api v1.20.0 h1:heE1eQoMsuZlwWOf7Xm8TKqKLNKVs11G/zMe5QyR1u4=

pkg/i2gw/ingress2gateway.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ func ToGatewayAPIResources(ctx context.Context, namespace string, inputFile stri
8383
gatewayResources = append(gatewayResources, providerGatewayResources)
8484
}
8585
notificationTablesMap := notifications.NotificationAggr.CreateNotificationTables()
86+
for _, gw := range gatewayResources {
87+
errs = append(errs, ValidateMaxItems(&gw)...)
88+
}
8689
if len(errs) > 0 {
8790
return nil, notificationTablesMap, aggregatedErrs(errs)
8891
}

pkg/i2gw/validate.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package i2gw
2+
3+
import (
4+
"fmt"
5+
"go/ast"
6+
"reflect"
7+
"strconv"
8+
"strings"
9+
10+
"golang.org/x/tools/go/packages"
11+
"k8s.io/apimachinery/pkg/util/validation/field"
12+
)
13+
14+
const (
15+
KubebuilderMaxItemsMarker = "kubebuilder:validation:MaxItems"
16+
MaxItemsPrefix = "MaxItems="
17+
)
18+
19+
// ValidateMaxItems validate fields presnet in the gateway resources object
20+
func ValidateMaxItems(gwResources *GatewayResources) field.ErrorList {
21+
var errs field.ErrorList
22+
maxItemsMap, err := loadGatewayAPITypes()
23+
if err != nil {
24+
return append(errs, field.InternalError(nil,
25+
fmt.Errorf("unable to fetch kubebuilder max items : %v", err)))
26+
}
27+
28+
for _, gw := range gwResources.Gateways {
29+
30+
if reflect.ValueOf(gw).IsZero() {
31+
continue
32+
}
33+
34+
v := reflect.ValueOf(gw)
35+
if v.Kind() == reflect.Ptr {
36+
//Get the struct
37+
v = v.Elem()
38+
}
39+
40+
spec := v.FieldByName("Spec")
41+
if !spec.IsValid() || spec.IsZero() {
42+
errs = append(errs, field.Required(
43+
field.NewPath("spec"),
44+
"spec field missing or empty in gateway resource",
45+
))
46+
return errs
47+
}
48+
49+
gatewayErrs := validateSpecFields(spec, maxItemsMap, field.NewPath("spec"))
50+
errs = append(errs, gatewayErrs...)
51+
52+
}
53+
return errs
54+
}
55+
56+
// validateSpecFields check fields in spec that are slices and whether exceeded max items limit
57+
func validateSpecFields(spec reflect.Value, maxItemsMap map[string]int, path *field.Path) field.ErrorList {
58+
var errs field.ErrorList
59+
60+
t := spec.Type()
61+
62+
for i := 0; i < spec.NumField(); i++ {
63+
fieldVal := spec.Field(i)
64+
fieldType := t.Field(i)
65+
fieldName := fieldType.Name
66+
67+
if fieldVal.Kind() == reflect.Slice && fieldVal.Len() > 0 {
68+
if maxItems, exists := maxItemsMap[fieldName]; exists {
69+
if fieldVal.Len() > maxItems {
70+
errs = append(errs, field.TooMany(
71+
path.Child(strings.ToLower(fieldName)),
72+
fieldVal.Len(),
73+
maxItems,
74+
))
75+
}
76+
}
77+
}
78+
}
79+
80+
return errs
81+
}
82+
83+
// loadGatewayAPITypes scans the Gateway API packages and extracts
84+
// the `+kubebuilder:validation:MaxItems` values defined in struct field comments
85+
func loadGatewayAPITypes() (map[string]int, error) {
86+
87+
cfg := &packages.Config{
88+
Mode: packages.NeedFiles | packages.NeedSyntax,
89+
}
90+
91+
pkgs, err := packages.Load(cfg,
92+
"sigs.k8s.io/gateway-api/apis/v1",
93+
"sigs.k8s.io/gateway-api/apis/v1alpha2",
94+
"sigs.k8s.io/gateway-api/apis/v1beta1",
95+
"sigs.k8s.io/gateway-api/apis/v1alpha3",
96+
)
97+
98+
if err != nil {
99+
return nil, fmt.Errorf("failed to load gateway-api package: %w", err)
100+
}
101+
102+
maxItemsMap := make(map[string]int)
103+
104+
for _, pkg := range pkgs {
105+
for _, file := range pkg.Syntax {
106+
ast.Inspect(file, func(n ast.Node) bool {
107+
switch x := n.(type) {
108+
case *ast.Field:
109+
if x.Doc != nil {
110+
for _, comment := range x.Doc.List {
111+
if strings.Contains(comment.Text, KubebuilderMaxItemsMarker) {
112+
fieldName := x.Names[0].Name
113+
if strings.Contains(comment.Text, MaxItemsPrefix) {
114+
parts := strings.Split(comment.Text, MaxItemsPrefix)
115+
if len(parts) > 1 {
116+
valueStr := strings.Split(parts[1], " ")[0]
117+
if maxItems, err := strconv.Atoi(valueStr); err == nil {
118+
maxItemsMap[fieldName] = maxItems
119+
}
120+
121+
}
122+
}
123+
}
124+
}
125+
}
126+
}
127+
return true
128+
})
129+
}
130+
}
131+
132+
return maxItemsMap, nil
133+
}

pkg/i2gw/validate_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package i2gw
2+
3+
import (
4+
"testing"
5+
6+
"k8s.io/apimachinery/pkg/types"
7+
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
8+
)
9+
10+
func TestValidateMaxItems(t *testing.T) {
11+
12+
gwResources := &GatewayResources{
13+
Gateways: map[types.NamespacedName]gatewayv1.Gateway{
14+
{Namespace: "default", Name: "gw1"}: {
15+
Spec: gatewayv1.GatewaySpec{
16+
//Allowed Listeners is 64
17+
Listeners: make([]gatewayv1.Listener, 65),
18+
},
19+
},
20+
},
21+
}
22+
23+
errs := ValidateMaxItems(gwResources)
24+
25+
if len(errs) < 1 {
26+
t.Errorf("expected error, got %d", len(errs))
27+
}
28+
}

0 commit comments

Comments
 (0)