Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
_ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/gce"
_ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/ingressnginx"
_ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/istio"
_ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/kgateway"
_ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/kong"
_ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/nginx"
_ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/openapi3"
Expand Down
37 changes: 18 additions & 19 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
module github.com/kubernetes-sigs/ingress2gateway

go 1.24.0

toolchain go1.24.6
go 1.25.3

require (
github.com/GoogleCloudPlatform/gke-gateway-api v1.3.0
github.com/getkin/kin-openapi v0.124.0
github.com/google/go-cmp v0.7.0
github.com/kgateway-dev/kgateway/v2 v2.1.1
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
istio.io/api v1.20.0
github.com/spf13/cobra v1.10.1
github.com/stretchr/testify v1.11.1
istio.io/api v1.26.0-alpha.0.0.20251002142010-859b66f07fad
k8s.io/api v0.34.1
k8s.io/apimachinery v0.34.1
k8s.io/cli-runtime v0.34.0
Expand All @@ -25,19 +24,19 @@ require (
)

require (
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // 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/mattn/go-runewidth v0.0.16 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // 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/rivo/uniseg v0.4.7 // 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/exp v0.0.0-20250506013437-ce4c2cf36ca6 // 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
Expand All @@ -63,18 +62,18 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/spf13/pflag v1.0.7 // indirect
golang.org/x/net v0.43.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
golang.org/x/net v0.44.0 // indirect
golang.org/x/oauth2 v0.30.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
golang.org/x/sys v0.36.0 // indirect
golang.org/x/term v0.35.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/time v0.12.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/protobuf v1.36.8
google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a // indirect
google.golang.org/protobuf v1.36.10
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
istio.io/client-go v1.19.0-alpha.1.0.20231130185426-9f1859c8ff42
istio.io/client-go v1.26.0-alpha.0.0.20251002142408-752760d8f171
k8s.io/klog/v2 v2.130.1
k8s.io/kube-openapi v0.0.0-20250814151709-d7b6acb124c3 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
Expand Down
100 changes: 54 additions & 46 deletions go.sum

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions pkg/i2gw/intermediate/intermediate_representation.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type ProviderSpecificGatewayIR struct {
Istio *IstioGatewayIR
Kong *KongGatewayIR
Openapi3 *Openapi3GatewayIR
Kgateway *KGatewayIR
}

// HTTPRouteContext contains the Gateway-API HTTPRoute object and HTTPRouteIR,
Expand All @@ -84,6 +85,7 @@ type ProviderSpecificHTTPRouteIR struct {
Istio *IstioHTTPRouteIR
Kong *KongHTTPRouteIR
Openapi3 *Openapi3HTTPRouteIR
Kgateway *KgatewayHTTPRouteIR
}

// ServiceIR contains a dedicated field for each provider to specify their
Expand All @@ -97,6 +99,7 @@ type ProviderSpecificServiceIR struct {
Kong *KongServiceIR
Openapi3 *Openapi3ServiceIR
Nginx *NginxServiceIR
Kgateway *KgatewayServiceIR
}

// BackendSource tracks the source Ingress resource that contributed
Expand Down
34 changes: 34 additions & 0 deletions pkg/i2gw/intermediate/provider_kgateway.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
Copyright 2024 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package intermediate

import "k8s.io/apimachinery/pkg/api/resource"

type KGatewayIR struct{}
type KgatewayHTTPRouteIR struct {
Policies map[string]KgatewayPolicy
}
type KgatewayServiceIR struct{}

type KgatewayPolicy struct {
Buffer *resource.Quantity
RuleBackendSources []KgatewayPolicyIndex
}
type KgatewayPolicyIndex struct {
Rule int
Backend int
}
27 changes: 27 additions & 0 deletions pkg/i2gw/providers/kgateway/features.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package kgateway

import (
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/intermediate"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation/field"
)

func bufferFeature(ing2pol map[string]intermediate.KgatewayPolicy) func(ingresses []networkingv1.Ingress, svcPorts map[types.NamespacedName]map[string]int32, ir *intermediate.IR) field.ErrorList {
return func(ingresses []networkingv1.Ingress, svcPorts map[types.NamespacedName]map[string]int32, ir *intermediate.IR) field.ErrorList {
var errList field.ErrorList
for _, ingress := range ingresses {
if buffersize := ingress.Annotations["nginx.ingress.kubernetes.io/client-body-buffer-size"]; buffersize != "" {
buffer, err := resource.ParseQuantity(buffersize)
if err != nil {
errList = append(errList, field.Invalid(field.NewPath("metadata").Child("annotations").Key("nginx.ingress.kubernetes.io/client-body-buffer-size"), buffersize, "invalid buffer size"))
}
pol := ing2pol[ingress.Name]
pol.Buffer = &buffer
ing2pol[ingress.Name] = pol
}
}
return errList
}
}
102 changes: 102 additions & 0 deletions pkg/i2gw/providers/kgateway/gateway_converter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package kgateway

import (
kgwv1a1 "github.com/kgateway-dev/kgateway/v2/api/v1alpha1"
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw"
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/intermediate"
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/notifications"
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
)

type irToGatewayResourcesConverter struct{}

// newIRToGatewayResourcesConverter returns a kgateway irToGatewayResourcesConverter instance.
func newIRToGatewayResourcesConverter() irToGatewayResourcesConverter {
return irToGatewayResourcesConverter{}
}

func (c *irToGatewayResourcesConverter) irToGateway(ir intermediate.IR) (i2gw.GatewayResources, field.ErrorList) {
gatewayResources, errs := common.ToGatewayResources(ir)
if len(errs) != 0 {
return i2gw.GatewayResources{}, errs
}
buildTrafficPolicies(ir, &gatewayResources)
return gatewayResources, nil
}

func buildTrafficPolicies(ir intermediate.IR, gatewayResources *i2gw.GatewayResources) {
for httpRouteKey, httpRouteContext := range ir.HTTPRoutes {
kgw := httpRouteContext.ProviderSpecificIR.Kgateway
if kgw == nil {
continue
}
tp := map[string]*kgwv1a1.TrafficPolicy{}
createIfNeeded := func(s string) {
if tp[s] == nil {
tp[s] = &kgwv1a1.TrafficPolicy{
ObjectMeta: v1.ObjectMeta{
Name: s,
Namespace: httpRouteKey.Namespace,
},
Spec: kgwv1a1.TrafficPolicySpec{},
}
tp[s].SetGroupVersionKind(TrafficPolicyGVK)
}
}

for polSourceIngressName, pol := range kgw.Policies {
var t *kgwv1a1.TrafficPolicy
if pol.Buffer != nil {
createIfNeeded(polSourceIngressName)
t = tp[polSourceIngressName]
t.Spec.Buffer = &kgwv1a1.Buffer{
MaxRequestSize: pol.Buffer,
}
}
if t == nil {
continue
}
// if the entire http route is covered by this policy, set targetRef to the http route
if len(pol.RuleBackendSources) == numRules(httpRouteContext.HTTPRoute) {
t.Spec.TargetRefs = []kgwv1a1.LocalPolicyTargetReferenceWithSectionName{{
LocalPolicyTargetReference: kgwv1a1.LocalPolicyTargetReference{
Name: gatewayv1.ObjectName(httpRouteKey.Name),
},
}}
} else {
// otherwise this httprule is combined from multiple ingress objects. use extensionRefs
// filters to point to the traffic policy from the relevant rules/backends
for _, idx := range pol.RuleBackendSources {
httpRouteContext.Spec.Rules[idx.Rule].BackendRefs[idx.Backend].Filters = append(httpRouteContext.Spec.Rules[idx.Rule].BackendRefs[idx.Backend].Filters, gatewayv1.HTTPRouteFilter{
Type: gatewayv1.HTTPRouteFilterExtensionRef,
ExtensionRef: &gatewayv1.LocalObjectReference{
Group: gatewayv1.Group(TrafficPolicyGVK.Group),
Kind: gatewayv1.Kind(TrafficPolicyGVK.Kind),
Name: gatewayv1.ObjectName(t.Name),
},
})
}
}
}

for _, tp := range tp {
obj, err := i2gw.CastToUnstructured(tp)
if err != nil {
notify(notifications.ErrorNotification, "Failed to cast TrafficPolicy to unstructured", tp)
continue
}
gatewayResources.GatewayExtensions = append(gatewayResources.GatewayExtensions, *obj)
}
}
}

func numRules(hr gatewayv1.HTTPRoute) int {
numRules := 0
for _, rule := range hr.Spec.Rules {
numRules += len(rule.BackendRefs)
}
return numRules
}
97 changes: 97 additions & 0 deletions pkg/i2gw/providers/kgateway/ir_converter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package kgateway

import (
"context"

"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw"
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/intermediate"
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation/field"
)

type resourcesToIRConverter struct {
conf *i2gw.ProviderConf
featureParsers []i2gw.FeatureParser

implementationSpecificOptions i2gw.ProviderImplementationSpecificOptions
ctx context.Context
ing2pol map[string]intermediate.KgatewayPolicy
}

// newResourcesToIRConverter returns an ingress-kgateway resourcesToIRConverter instance.
func newResourcesToIRConverter(conf *i2gw.ProviderConf) resourcesToIRConverter {

ing2pol := make(map[string]intermediate.KgatewayPolicy)
return resourcesToIRConverter{
conf: conf,
implementationSpecificOptions: i2gw.ProviderImplementationSpecificOptions{},
ctx: context.Background(),
ing2pol: ing2pol,
featureParsers: []i2gw.FeatureParser{
bufferFeature(ing2pol),
},
}
}

func (c *resourcesToIRConverter) convertToIR(storage *storage) (intermediate.IR, field.ErrorList) {
ingressList := []networkingv1.Ingress{}
for _, ing := range storage.Ingresses {
ingressList = append(ingressList, *ing)
}

// Convert plain ingress resources to gateway resources, ignoring all
// provider-specific features.
ir, errs := common.ToIR(ingressList, storage.ServicePorts, c.implementationSpecificOptions)
if len(errs) > 0 {
return intermediate.IR{}, errs
}
// fix gateway class in gateways
for i := range ir.Gateways {
g := ir.Gateways[i]
g.Spec.GatewayClassName = "kgateway"
ir.Gateways[i] = g
}

for _, parseFeatureFunc := range c.featureParsers {
// Apply the feature parsing function to the gateway resources, one by one.
parseErrs := parseFeatureFunc(ingressList, storage.ServicePorts, &ir)
// Append the parsing errors to the error list.
errs = append(errs, parseErrs...)
}

ruleGroups := common.GetRuleGroups(ingressList)

for _, rg := range ruleGroups {
for _, rule := range rg.Rules {
ingress := rule.Ingress
if pol, ok := c.ing2pol[ingress.Name]; ok {

key := types.NamespacedName{Namespace: rg.Namespace, Name: common.RouteName(rg.Name, rg.Host)}
httpRouteContext, ok := ir.HTTPRoutes[key]
if !ok {
continue
}
if httpRouteContext.ProviderSpecificIR.Kgateway == nil {
httpRouteContext.ProviderSpecificIR.Kgateway = &intermediate.KgatewayHTTPRouteIR{
Policies: make(map[string]intermediate.KgatewayPolicy),
}
}
for i, backEndSources := range httpRouteContext.RuleBackendSources {
for j, backEndSource := range backEndSources {
if backEndSource.Ingress.Name == ingress.Name {
key := intermediate.KgatewayPolicyIndex{Rule: i, Backend: j}
pol.RuleBackendSources = append(pol.RuleBackendSources, key)
}
}
}
httpRouteContext.ProviderSpecificIR.Kgateway.Policies[ingress.Name] = pol

ir.HTTPRoutes[key] = httpRouteContext
}
}
}

return ir, errs
}
Loading