Skip to content
Merged
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
33 changes: 33 additions & 0 deletions pkg/i2gw/providers/ingressnginx/annotations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
Copyright 2026 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 ingressnginx

const (
// Canary annotations
CanaryAnnotation = "nginx.ingress.kubernetes.io/canary"
CanaryWeightAnnotation = "nginx.ingress.kubernetes.io/canary-weight"
CanaryWeightTotalAnnotation = "nginx.ingress.kubernetes.io/canary-weight-total"

// Rewrite annotations
RewriteTargetAnnotation = "nginx.ingress.kubernetes.io/rewrite-target"

// Header annotations
XForwardedPrefixAnnotation = "nginx.ingress.kubernetes.io/x-forwarded-prefix"
UpstreamVhostAnnotation = "nginx.ingress.kubernetes.io/upstream-vhost"
ConnectionProxyHeaderAnnotation = "nginx.ingress.kubernetes.io/connection-proxy-header"
CustomHeadersAnnotation = "nginx.ingress.kubernetes.io/custom-headers"
)
12 changes: 3 additions & 9 deletions pkg/i2gw/providers/ingressnginx/canary.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,6 @@ import (
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
)

const (
canaryAnnotation = "nginx.ingress.kubernetes.io/canary"
canaryWeightAnnotation = "nginx.ingress.kubernetes.io/canary-weight"
canaryWeightTotalAnnotation = "nginx.ingress.kubernetes.io/canary-weight-total"
)

// canaryConfig holds the parsed canary configuration from a single Ingress
type canaryConfig struct {
weight int32
Expand All @@ -48,7 +42,7 @@ func parseCanaryConfig(ingress *networkingv1.Ingress) (canaryConfig, error) {
weightTotal: 100, // default
}

if weight := ingress.Annotations[canaryWeightAnnotation]; weight != "" {
if weight := ingress.Annotations[CanaryWeightAnnotation]; weight != "" {
w, err := strconv.ParseInt(weight, 10, 32)
if err != nil {
return config, fmt.Errorf("invalid canary-weight annotation %q: %w", weight, err)
Expand All @@ -59,7 +53,7 @@ func parseCanaryConfig(ingress *networkingv1.Ingress) (canaryConfig, error) {
config.weight = int32(w)
}

if total := ingress.Annotations[canaryWeightTotalAnnotation]; total != "" {
if total := ingress.Annotations[CanaryWeightTotalAnnotation]; total != "" {
wt, err := strconv.ParseInt(total, 10, 32)
if err != nil {
return config, fmt.Errorf("invalid canary-weight-total annotation %q: %w", total, err)
Expand Down Expand Up @@ -112,7 +106,7 @@ func canaryFeature(ingresses []networkingv1.Ingress, _ map[types.NamespacedName]

backendRef := &httpRouteContext.HTTPRoute.Spec.Rules[ruleIdx].BackendRefs[backendIdx]

if source.Ingress.Annotations[canaryAnnotation] == "true" {
if source.Ingress.Annotations[CanaryAnnotation] == "true" {
if canaryBackend != nil {
errList = append(errList, field.Invalid(
field.NewPath("httproute", httpRouteContext.HTTPRoute.Name, "spec", "rules").Index(ruleIdx).Child("backendRefs"),
Expand Down
1 change: 1 addition & 0 deletions pkg/i2gw/providers/ingressnginx/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func newResourcesToIRConverter() *resourcesToIRConverter {
return &resourcesToIRConverter{
featureParsers: []i2gw.FeatureParser{
canaryFeature,
headerModifierFeature,
},
}
}
Expand Down
109 changes: 109 additions & 0 deletions pkg/i2gw/providers/ingressnginx/headers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
Copyright 2026 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 ingressnginx

import (
"fmt"

"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/notifications"
providerir "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/provider_intermediate"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation/field"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
)

func headerModifierFeature(_ []networkingv1.Ingress, _ map[types.NamespacedName]map[string]int32, ir *providerir.ProviderIR) field.ErrorList {
for _, httpRouteContext := range ir.HTTPRoutes {
for i := range httpRouteContext.HTTPRoute.Spec.Rules {
if i >= len(httpRouteContext.RuleBackendSources) {
continue
}
sources := httpRouteContext.RuleBackendSources[i]

ingress := getNonCanaryIngress(sources)
if ingress == nil {
Comment thread
eladmotola marked this conversation as resolved.
notify(notifications.InfoNotification, "Found canary ingress rule without non-canary ingress rule", &httpRouteContext.HTTPRoute)
continue
}

headersToSet := make(map[string]string)

_, hasRewriteTarget := ingress.Annotations[RewriteTargetAnnotation]

// 1. x-forwarded-prefix
// This annotation only works if rewrite-target is also present.
Comment thread
eladmotola marked this conversation as resolved.
// TODO: X-Forwarded-Prefix is complex because it depends on rewrite-target.
Comment thread
robscott marked this conversation as resolved.
// Deferring this to a future PR.
if val, ok := ingress.Annotations[XForwardedPrefixAnnotation]; ok && val != "" && hasRewriteTarget {
headersToSet["X-Forwarded-Prefix"] = val
}

// 2. upstream-vhost -> Host header
if val, ok := ingress.Annotations[UpstreamVhostAnnotation]; ok && val != "" {
headersToSet["Host"] = val
}

// 3. connection-proxy-header -> Connection header
if val, ok := ingress.Annotations[ConnectionProxyHeaderAnnotation]; ok && val != "" {
headersToSet["Connection"] = val
}

// 4. custom-headers -> Warn unsupported
// TODO: implement custom-headers annotation.
if _, ok := ingress.Annotations[CustomHeadersAnnotation]; ok {
notify(notifications.WarningNotification, fmt.Sprintf("Ingress %s/%s uses '%s' which is not supported.", ingress.Namespace, ingress.Name, CustomHeadersAnnotation), &httpRouteContext.HTTPRoute)
}

if len(headersToSet) > 0 {
applyHeaderModifiers(&httpRouteContext.HTTPRoute, i, headersToSet)
}
}
}
return nil
}

func applyHeaderModifiers(httpRoute *gatewayv1.HTTPRoute, ruleIndex int, headersToSet map[string]string) {
// Find existing RequestHeaderModifier filter or create new one
var filter *gatewayv1.HTTPRouteFilter
for j, f := range httpRoute.Spec.Rules[ruleIndex].Filters {
if f.Type == gatewayv1.HTTPRouteFilterRequestHeaderModifier && f.RequestHeaderModifier != nil {
filter = &httpRoute.Spec.Rules[ruleIndex].Filters[j]
break
}
}

if filter == nil {
f := gatewayv1.HTTPRouteFilter{
Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier,
RequestHeaderModifier: &gatewayv1.HTTPHeaderFilter{
Set: []gatewayv1.HTTPHeader{},
},
}
httpRoute.Spec.Rules[ruleIndex].Filters = append(httpRoute.Spec.Rules[ruleIndex].Filters, f)
Comment thread
eladmotola marked this conversation as resolved.
filter = &httpRoute.Spec.Rules[ruleIndex].Filters[len(httpRoute.Spec.Rules[ruleIndex].Filters)-1]
}

for name, value := range headersToSet {
// Used standard append as suggested in PR review
filter.RequestHeaderModifier.Set = append(filter.RequestHeaderModifier.Set, gatewayv1.HTTPHeader{
Name: gatewayv1.HTTPHeaderName(name),
Value: value,
})
notify(notifications.InfoNotification, fmt.Sprintf("Applied header modifier %s: %s to rule %d of route %s/%s", name, value, ruleIndex, httpRoute.Namespace, httpRoute.Name), httpRoute)
}
}
Loading