Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ab7c4f5
feat: Implement PR1 Infrastructure and Standard CORS support
rajashish Feb 23, 2026
d333cf8
Restore PR 303 feature files
rajashish Feb 24, 2026
0eaa032
feat: apply gke session cookie parser stacked on main
rajashish Feb 24, 2026
d2e9200
fix: suppress warnings for affinity annotations
rajashish Feb 24, 2026
fc84e68
refactor: use generic session affinity in EmitterIR
rajashish Feb 24, 2026
9f0c4db
refactor: use provider-neutral SessionAffinity enum
rajashish Feb 24, 2026
ac0f2ce
fix(gce): correctly strip HTTPRoute rule names without breaking gener…
rajashish Feb 25, 2026
b4dc4df
fix(common): remove unsupported HTTPRouteRule Name generation and fix…
rajashish Mar 9, 2026
eb63d16
fix(gce): remove implicit GatewayClass upselling to gke-l7-global-ext…
rajashish Mar 9, 2026
6ac87dd
style: update copyright header to remove specific year
rajashish Mar 9, 2026
92c06a2
updated formatting
rajashish Mar 16, 2026
4550283
Implement session affinity tracking and restore rule naming
rajashish Mar 19, 2026
20866d3
Update standard emitter to warn on unconsumed SessionAffinity
rajashish Mar 19, 2026
fc473ed
remove copyright years (#389)
Stevenjin8 Mar 10, 2026
8604bcb
fix: use binary suffixes (Ki/Mi/Gi) for body size conversion (#375)
Stevenjin8 Mar 11, 2026
4d3a22d
Update main and ingress-nginx README (#390)
Stevenjin8 Mar 12, 2026
80239bd
Reorganize e2e tests (#366)
johananl Mar 12, 2026
e9de933
Remove LiorLieberman from reviewers list (#392)
LiorLieberman Mar 16, 2026
0efc4c5
Clean up notifications (#384)
Stevenjin8 Mar 17, 2026
f5be5e1
fix tests and unparsed stuff
Stevenjin8 Mar 20, 2026
3fa92b6
Merge remote-tracking branch 'up/main' into feat/gke-session-cookie-s…
Stevenjin8 Mar 20, 2026
41d0bcb
clean up service errors
Stevenjin8 Mar 20, 2026
7bf61c6
simplify notification cahnge
Stevenjin8 Mar 20, 2026
a97d914
undo nginx changes
Stevenjin8 Mar 20, 2026
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: 0 additions & 1 deletion pkg/i2gw/emitter_intermediate/gce/gce.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ type GatewayIR struct {
type SslPolicyConfig struct {
Name string
}
type HTTPRouteIR struct{}
type ServiceIR struct {
SessionAffinity *SessionAffinityConfig
SecurityPolicy *SecurityPolicyConfig
Expand Down
12 changes: 12 additions & 0 deletions pkg/i2gw/emitter_intermediate/intermediate_representation.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,21 @@ type EmitterIR struct {
BackendTLSPolicies map[types.NamespacedName]BackendTLSPolicyContext
ReferenceGrants map[types.NamespacedName]ReferenceGrantContext

Services map[types.NamespacedName]ServiceContext

GceServices map[types.NamespacedName]gce.ServiceIR
}

type SessionAffinity struct {
Metadata ExtensionFeatureMetadata
Type string
CookieTTLSec *int64
}

type ServiceContext struct {
SessionAffinity *SessionAffinity
}

type GatewayContext struct {
gatewayv1.Gateway
// Emitter IR should be provider/emitter neutral,
Expand Down
61 changes: 52 additions & 9 deletions pkg/i2gw/emitters/gce/gce.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,23 @@ func (c *Emitter) Emit(ir emitterir.EmitterIR) (i2gw.GatewayResources, field.Err
}
buildGceGatewayExtensions(c.notify, ir, &gatewayResources)
buildGceServiceExtensions(c.notify, ir, &gatewayResources)

removeHTTPRouteRuleNames(&gatewayResources)
return gatewayResources, nil
}

func removeHTTPRouteRuleNames(gatewayResources *i2gw.GatewayResources) {
for k, route := range gatewayResources.HTTPRoutes {
rules := make([]gatewayv1.HTTPRouteRule, len(route.Spec.Rules))
copy(rules, route.Spec.Rules)
for j := range rules {
rules[j].Name = nil
}
route.Spec.Rules = rules
gatewayResources.HTTPRoutes[k] = route
}
}

func buildGceGatewayExtensions(notify notifications.NotifyFunc, ir emitterir.EmitterIR, gatewayResources *i2gw.GatewayResources) {
for gwyKey, gatewayContext := range ir.Gateways {
gwyPolicy := addGatewayPolicyIfConfigured(gwyKey, &gatewayContext)
Expand Down Expand Up @@ -122,8 +136,25 @@ func addGatewayPolicyIfConfigured(gatewayNamespacedName types.NamespacedName, ga
}

func buildGceServiceExtensions(notify notifications.NotifyFunc, ir emitterir.EmitterIR, gatewayResources *i2gw.GatewayResources) {
for svcKey, gceServiceIR := range ir.GceServices {
bePolicy := addGCPBackendPolicyIfConfigured(svcKey, gceServiceIR)
svcKeys := make(map[types.NamespacedName]bool)
for k := range ir.GceServices {
svcKeys[k] = true
}
for k := range ir.Services {
svcKeys[k] = true
}

for svcKey := range svcKeys {
var gceServiceIR *gce.ServiceIR
if gceIR, ok := ir.GceServices[svcKey]; ok {
gceServiceIR = &gceIR
}
var genericServiceIR *emitterir.ServiceContext
if genIR, ok := ir.Services[svcKey]; ok {
genericServiceIR = &genIR
}

bePolicy := addGCPBackendPolicyIfConfigured(svcKey, genericServiceIR, gceServiceIR)
if bePolicy != nil {
obj, err := i2gw.CastToUnstructured(bePolicy)
if err != nil {
Expand All @@ -133,7 +164,7 @@ func buildGceServiceExtensions(notify notifications.NotifyFunc, ir emitterir.Emi
gatewayResources.GatewayExtensions = append(gatewayResources.GatewayExtensions, *obj)
}

hcPolicy := addHealthCheckPolicyIfConfigured(svcKey, &gceServiceIR)
hcPolicy := addHealthCheckPolicyIfConfigured(svcKey, gceServiceIR)
if hcPolicy != nil {
obj, err := i2gw.CastToUnstructured(hcPolicy)
if err != nil {
Expand All @@ -145,9 +176,21 @@ func buildGceServiceExtensions(notify notifications.NotifyFunc, ir emitterir.Emi
}
}

func addGCPBackendPolicyIfConfigured(serviceNamespacedName types.NamespacedName, gceServiceIR gce.ServiceIR) *gkegatewayv1.GCPBackendPolicy {
func addGCPBackendPolicyIfConfigured(serviceNamespacedName types.NamespacedName, genericServiceIR *emitterir.ServiceContext, gceServiceIR *gce.ServiceIR) *gkegatewayv1.GCPBackendPolicy {
// If there is no specification related to GCPBackendPolicy feature, return nil.
if gceServiceIR.SessionAffinity == nil && gceServiceIR.SecurityPolicy == nil {
var hasSessionAffinity bool
if genericServiceIR != nil && genericServiceIR.SessionAffinity != nil {
hasSessionAffinity = true
} else if gceServiceIR != nil && gceServiceIR.SessionAffinity != nil {
hasSessionAffinity = true
}

var hasSecurityPolicy bool
if gceServiceIR != nil && gceServiceIR.SecurityPolicy != nil {
hasSecurityPolicy = true
}

if !hasSessionAffinity && !hasSecurityPolicy {
return nil
}

Expand All @@ -167,11 +210,11 @@ func addGCPBackendPolicyIfConfigured(serviceNamespacedName types.NamespacedName,
}
gcpBackendPolicy.SetGroupVersionKind(GCPBackendPolicyGVK)

if gceServiceIR.SessionAffinity != nil {
gcpBackendPolicy.Spec.Default.SessionAffinity = BuildGCPBackendPolicySessionAffinityConfig(gceServiceIR)
if hasSessionAffinity {
gcpBackendPolicy.Spec.Default.SessionAffinity = BuildGCPBackendPolicySessionAffinityConfig(genericServiceIR, gceServiceIR)
}
if gceServiceIR.SecurityPolicy != nil {
gcpBackendPolicy.Spec.Default.SecurityPolicy = BuildGCPBackendPolicySecurityPolicyConfig(gceServiceIR)
if hasSecurityPolicy {
gcpBackendPolicy.Spec.Default.SecurityPolicy = BuildGCPBackendPolicySecurityPolicyConfig(*gceServiceIR)
}

return &gcpBackendPolicy
Expand Down
21 changes: 18 additions & 3 deletions pkg/i2gw/emitters/gce/output_extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,28 @@ import (
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/emitter_intermediate/gce"
)

func BuildGCPBackendPolicySessionAffinityConfig(gceServiceIR gce.ServiceIR) *gkegatewayv1.SessionAffinityConfig {
affinityType := gceServiceIR.SessionAffinity.AffinityType
func BuildGCPBackendPolicySessionAffinityConfig(genericServiceIR *emitterir.ServiceContext, gceServiceIR *gce.ServiceIR) *gkegatewayv1.SessionAffinityConfig {
var affinityType string
var cookieTTL *int64

if genericServiceIR != nil && genericServiceIR.SessionAffinity != nil {
affinityType = genericServiceIR.SessionAffinity.Type
if affinityType == "Cookie" {
affinityType = "GENERATED_COOKIE"
}
cookieTTL = genericServiceIR.SessionAffinity.CookieTTLSec
} else if gceServiceIR != nil && gceServiceIR.SessionAffinity != nil {
affinityType = gceServiceIR.SessionAffinity.AffinityType
cookieTTL = gceServiceIR.SessionAffinity.CookieTTLSec
} else {
return nil
}

saConfig := gkegatewayv1.SessionAffinityConfig{
Type: &affinityType,
}
if affinityType == "GENERATED_COOKIE" {
saConfig.CookieTTLSec = gceServiceIR.SessionAffinity.CookieTTLSec
saConfig.CookieTTLSec = cookieTTL
}
return &saConfig
}
Expand Down
51 changes: 51 additions & 0 deletions pkg/i2gw/emitters/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,55 @@ func LogUnparsedErrors(ir emitterir.EmitterIR, notify notifications.NotifyFunc)
)
}
}

for svcKey, serviceContext := range ir.Services {
if serviceContext.SessionAffinity != nil {
meta := serviceContext.SessionAffinity.Metadata
source := meta.Source()
paths := strings.Builder{}
for _, p := range meta.Paths() {
paths.WriteString(p.String())
paths.WriteString(", ")
}

message := meta.FailureMessage()
if message == "" {
message = "Session Affinity is not supported by this emitter"
}

var associatedHR *gatewayv1.HTTPRoute
for _, hrContext := range ir.HTTPRoutes {
for _, rule := range hrContext.HTTPRoute.Spec.Rules {
for _, br := range rule.BackendRefs {
if string(br.Name) == svcKey.Name {
associatedHR = &hrContext.HTTPRoute
break
}
}
if associatedHR != nil {
break
}
}
if associatedHR != nil {
break
}
}

var warnObj *gatewayv1.HTTPRoute
if associatedHR != nil {
// Create a copy to modify type meta safely
copyHR := associatedHR.DeepCopy()
if copyHR.TypeMeta.Kind == "" {
copyHR.TypeMeta.Kind = "HTTPRoute"
copyHR.TypeMeta.APIVersion = "gateway.networking.k8s.io/v1"
}
warnObj = copyHR
}

notify(notifications.WarningNotification,
fmt.Sprintf("failed to parse %s from Ingress %s: %s", source, strings.TrimSuffix(paths.String(), ", "), message),
warnObj,
)
}
}
}
16 changes: 12 additions & 4 deletions pkg/i2gw/notifications/notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,18 @@ type Notification struct {
}

func objectsToStr(ob []client.Object) string {
strs := make([]string, len(ob))

for i, o := range ob {
strs[i] = o.GetObjectKind().GroupVersionKind().Kind + ": " + client.ObjectKeyFromObject(o).String()
strs := make([]string, 0, len(ob))

for _, o := range ob {
if o == nil {
strs = append(strs, "Unknown")
continue
}
kind := o.GetObjectKind().GroupVersionKind().Kind
if kind == "" {
kind = "Object"
}
strs = append(strs, kind+": "+client.ObjectKeyFromObject(o).String())
}

return strings.Join(strs, ", ")
Expand Down
20 changes: 18 additions & 2 deletions pkg/i2gw/provider_intermediate/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package providerir

import (
emitterir "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/emitter_intermediate"
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/emitter_intermediate/gce"
"k8s.io/apimachinery/pkg/types"
)

Expand All @@ -32,13 +33,20 @@ func ToEmitterIR(pIR ProviderIR) emitterir.EmitterIR {
GRPCRoutes: make(map[types.NamespacedName]emitterir.GRPCRouteContext),
BackendTLSPolicies: make(map[types.NamespacedName]emitterir.BackendTLSPolicyContext),
ReferenceGrants: make(map[types.NamespacedName]emitterir.ReferenceGrantContext),
Services: make(map[types.NamespacedName]emitterir.ServiceContext),
GceServices: make(map[types.NamespacedName]gce.ServiceIR),
}

for k, v := range pIR.Gateways {
eIR.Gateways[k] = emitterir.GatewayContext{Gateway: v.Gateway}
ctx := emitterir.GatewayContext{Gateway: v.Gateway}
if v.ProviderSpecificIR.Gce != nil {
ctx.Gce = v.ProviderSpecificIR.Gce
}
eIR.Gateways[k] = ctx
}
for k, v := range pIR.HTTPRoutes {
eIR.HTTPRoutes[k] = emitterir.HTTPRouteContext{HTTPRoute: v.HTTPRoute}
ctx := emitterir.HTTPRouteContext{HTTPRoute: v.HTTPRoute}
eIR.HTTPRoutes[k] = ctx
}
for k, v := range pIR.GatewayClasses {
eIR.GatewayClasses[k] = emitterir.GatewayClassContext{GatewayClass: v}
Expand All @@ -61,6 +69,14 @@ func ToEmitterIR(pIR ProviderIR) emitterir.EmitterIR {
for k, v := range pIR.ReferenceGrants {
eIR.ReferenceGrants[k] = emitterir.ReferenceGrantContext{ReferenceGrant: v}
}
for k, v := range pIR.Services {
eIR.Services[k] = emitterir.ServiceContext{
SessionAffinity: v.SessionAffinity,
}
if v.Gce != nil {
eIR.GceServices[k] = *v.Gce
}
}

return eIR
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package providerir

import (
emitterir "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/emitter_intermediate"
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/emitter_intermediate/gce"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/types"
Expand Down Expand Up @@ -72,7 +73,6 @@ type HTTPRouteContext struct {
}

type ProviderSpecificHTTPRouteIR struct {
Gce *gce.HTTPRouteIR
}

// GRPCRouteContext contains the Gateway-API GRPCRoute object and GRPCRouteIR,
Expand All @@ -89,7 +89,8 @@ type GRPCRouteContext struct {
// ServiceIR contains a dedicated field for each provider to specify their
// extension features on Service.
type ProviderSpecificServiceIR struct {
Gce *gce.ServiceIR
SessionAffinity *emitterir.SessionAffinity
Gce *gce.ServiceIR
}

// BackendSource tracks the source Ingress resource that contributed
Expand Down
3 changes: 3 additions & 0 deletions pkg/i2gw/providers/common/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,9 @@ func (a *ingressAggregator) toRoutesAndGateways(options i2gw.ProviderImplementat
BackendRefs: []gatewayv1.HTTPBackendRef{{BackendRef: *backendRef}},
})
}
for idx := range httpRoute.Spec.Rules {
httpRoute.Spec.Rules[idx].Name = ptr.To(gatewayv1.SectionName(fmt.Sprintf("rule-%d", idx)))
}
// Set the single source for this default backend.
sources := [][]providerir.BackendSource{
{
Expand Down
1 change: 1 addition & 0 deletions pkg/i2gw/providers/common/converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ func Test_ToIR(t *testing.T) {
}},
},
Rules: []gatewayv1.HTTPRouteRule{{
Name: ptr.To(gatewayv1.SectionName("rule-0")),
BackendRefs: []gatewayv1.HTTPBackendRef{{
BackendRef: gatewayv1.BackendRef{
BackendObjectReference: gatewayv1.BackendObjectReference{
Expand Down
7 changes: 6 additions & 1 deletion pkg/i2gw/providers/ingressnginx/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ const (

// SSL Redirect annotation
SSLRedirectAnnotation = "nginx.ingress.kubernetes.io/ssl-redirect"

// CORS annotations
EnableCorsAnnotation = "nginx.ingress.kubernetes.io/enable-cors"
CorsAllowOriginAnnotation = "nginx.ingress.kubernetes.io/cors-allow-origin"
Expand All @@ -83,6 +82,10 @@ const (
ProxySSLServerNameAnnotation = "nginx.ingress.kubernetes.io/proxy-ssl-server-name"
ProxySSLVerifyDepthAnnotation = "nginx.ingress.kubernetes.io/proxy-ssl-verify-depth"
ProxySSLProtocolsAnnotation = "nginx.ingress.kubernetes.io/proxy-ssl-protocols"

// Affinity annotations
AffinityAnnotation = "nginx.ingress.kubernetes.io/affinity"
SessionCookieExpiresAnnotation = "nginx.ingress.kubernetes.io/session-cookie-expires"
)

const ingressNGINXAnnotationsPrefix = "nginx.ingress.kubernetes.io/"
Expand Down Expand Up @@ -132,4 +135,6 @@ var parsedAnnotations = map[string]struct{}{
ProxySSLServerNameAnnotation: {},
ProxySSLVerifyDepthAnnotation: {},
ProxySSLProtocolsAnnotation: {},
AffinityAnnotation: {},
SessionCookieExpiresAnnotation: {},
}
1 change: 1 addition & 0 deletions pkg/i2gw/providers/ingressnginx/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func newResourcesToIRConverter(notify notifications.NotifyFunc) *resourcesToIRCo
headerModifierFeature,
regexFeature,
backendTLSFeature,
sessionAffinityFeature,
},
notify: notify,
}
Expand Down
Loading
Loading