diff --git a/go.mod b/go.mod index e285ac7a..d0f0a968 100644 --- a/go.mod +++ b/go.mod @@ -6,13 +6,14 @@ toolchain go1.24.6 require ( github.com/GoogleCloudPlatform/gke-gateway-api v1.3.0 - github.com/getkin/kin-openapi v0.124.0 + github.com/getkin/kin-openapi v0.133.0 github.com/google/go-cmp v0.7.0 github.com/kong/kubernetes-ingress-controller/v2 v2.12.3 github.com/olekukonko/tablewriter v0.0.5 github.com/samber/lo v1.39.0 github.com/spf13/cobra v1.9.1 github.com/stretchr/testify v1.11.0 + golang.org/x/tools v0.36.0 istio.io/api v1.20.0 k8s.io/api v0.34.1 k8s.io/apimachinery v0.34.1 @@ -27,17 +28,20 @@ require ( require ( github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect - github.com/invopop/yaml v0.2.0 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/moby/term v0.5.0 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/woodsbury/decimal128 v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/mod v0.27.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect @@ -66,6 +70,7 @@ require ( github.com/spf13/pflag v1.0.7 // indirect golang.org/x/net v0.43.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.35.0 // indirect golang.org/x/term v0.34.0 // indirect golang.org/x/text v0.28.0 // indirect diff --git a/go.sum b/go.sum index e6f63d48..d7cc5102 100644 --- a/go.sum +++ b/go.sum @@ -19,8 +19,8 @@ github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjT github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M= -github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 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= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= -github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 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 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 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 github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= +github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 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 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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 gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= istio.io/api v1.20.0 h1:heE1eQoMsuZlwWOf7Xm8TKqKLNKVs11G/zMe5QyR1u4= diff --git a/pkg/i2gw/ingress2gateway.go b/pkg/i2gw/ingress2gateway.go index 7f7c663c..bc78b597 100644 --- a/pkg/i2gw/ingress2gateway.go +++ b/pkg/i2gw/ingress2gateway.go @@ -83,6 +83,9 @@ func ToGatewayAPIResources(ctx context.Context, namespace string, inputFile stri gatewayResources = append(gatewayResources, providerGatewayResources) } notificationTablesMap := notifications.NotificationAggr.CreateNotificationTables() + for _, gw := range gatewayResources { + errs = append(errs, ValidateMaxItems(&gw)...) + } if len(errs) > 0 { return nil, notificationTablesMap, aggregatedErrs(errs) } diff --git a/pkg/i2gw/validate.go b/pkg/i2gw/validate.go new file mode 100644 index 00000000..0a4b3fef --- /dev/null +++ b/pkg/i2gw/validate.go @@ -0,0 +1,133 @@ +package i2gw + +import ( + "fmt" + "go/ast" + "reflect" + "strconv" + "strings" + + "golang.org/x/tools/go/packages" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +const ( + KubebuilderMaxItemsMarker = "kubebuilder:validation:MaxItems" + MaxItemsPrefix = "MaxItems=" +) + +// ValidateMaxItems validate fields presnet in the gateway resources object +func ValidateMaxItems(gwResources *GatewayResources) field.ErrorList { + var errs field.ErrorList + maxItemsMap, err := loadGatewayAPITypes() + if err != nil { + return append(errs, field.InternalError(nil, + fmt.Errorf("unable to fetch kubebuilder max items : %v", err))) + } + + for _, gw := range gwResources.Gateways { + + if reflect.ValueOf(gw).IsZero() { + continue + } + + v := reflect.ValueOf(gw) + if v.Kind() == reflect.Ptr { + //Get the struct + v = v.Elem() + } + + spec := v.FieldByName("Spec") + if !spec.IsValid() || spec.IsZero() { + errs = append(errs, field.Required( + field.NewPath("spec"), + "spec field missing or empty in gateway resource", + )) + return errs + } + + gatewayErrs := validateSpecFields(spec, maxItemsMap, field.NewPath("spec")) + errs = append(errs, gatewayErrs...) + + } + return errs +} + +// validateSpecFields check fields in spec that are slices and whether exceeded max items limit +func validateSpecFields(spec reflect.Value, maxItemsMap map[string]int, path *field.Path) field.ErrorList { + var errs field.ErrorList + + t := spec.Type() + + for i := 0; i < spec.NumField(); i++ { + fieldVal := spec.Field(i) + fieldType := t.Field(i) + fieldName := fieldType.Name + + if fieldVal.Kind() == reflect.Slice && fieldVal.Len() > 0 { + if maxItems, exists := maxItemsMap[fieldName]; exists { + if fieldVal.Len() > maxItems { + errs = append(errs, field.TooMany( + path.Child(strings.ToLower(fieldName)), + fieldVal.Len(), + maxItems, + )) + } + } + } + } + + return errs +} + +// loadGatewayAPITypes scans the Gateway API packages and extracts +// the `+kubebuilder:validation:MaxItems` values defined in struct field comments +func loadGatewayAPITypes() (map[string]int, error) { + + cfg := &packages.Config{ + Mode: packages.NeedFiles | packages.NeedSyntax, + } + + pkgs, err := packages.Load(cfg, + "sigs.k8s.io/gateway-api/apis/v1", + "sigs.k8s.io/gateway-api/apis/v1alpha2", + "sigs.k8s.io/gateway-api/apis/v1beta1", + "sigs.k8s.io/gateway-api/apis/v1alpha3", + ) + + if err != nil { + return nil, fmt.Errorf("failed to load gateway-api package: %w", err) + } + + maxItemsMap := make(map[string]int) + + for _, pkg := range pkgs { + for _, file := range pkg.Syntax { + ast.Inspect(file, func(n ast.Node) bool { + switch x := n.(type) { + case *ast.Field: + if x.Doc != nil { + for _, comment := range x.Doc.List { + if strings.Contains(comment.Text, KubebuilderMaxItemsMarker) { + fieldName := x.Names[0].Name + if strings.Contains(comment.Text, MaxItemsPrefix) { + parts := strings.Split(comment.Text, MaxItemsPrefix) + if len(parts) > 1 { + valueStr := strings.Split(parts[1], " ")[0] + if maxItems, err := strconv.Atoi(valueStr); err == nil { + maxItemsMap[fieldName] = maxItems + } + + } + } + } + } + } + } + return true + }) + } + } + + return maxItemsMap, nil +} diff --git a/pkg/i2gw/validate_test.go b/pkg/i2gw/validate_test.go new file mode 100644 index 00000000..3481a3f4 --- /dev/null +++ b/pkg/i2gw/validate_test.go @@ -0,0 +1,28 @@ +package i2gw + +import ( + "testing" + + "k8s.io/apimachinery/pkg/types" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" +) + +func TestValidateMaxItems(t *testing.T) { + + gwResources := &GatewayResources{ + Gateways: map[types.NamespacedName]gatewayv1.Gateway{ + {Namespace: "default", Name: "gw1"}: { + Spec: gatewayv1.GatewaySpec{ + //Allowed Listeners is 64 + Listeners: make([]gatewayv1.Listener, 65), + }, + }, + }, + } + + errs := ValidateMaxItems(gwResources) + + if len(errs) < 1 { + t.Errorf("expected error, got %d", len(errs)) + } +}