Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package ingress2gateway for openSUSE:Factory
checked in at 2026-07-08 17:48:20
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/ingress2gateway (Old)
and /work/SRC/openSUSE:Factory/.ingress2gateway.new.1982 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ingress2gateway"
Wed Jul 8 17:48:20 2026 rev:4 rq:1364556 version:1.2.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/ingress2gateway/ingress2gateway.changes
2026-05-13 17:23:59.902889571 +0200
+++
/work/SRC/openSUSE:Factory/.ingress2gateway.new.1982/ingress2gateway.changes
2026-07-08 17:48:34.958576245 +0200
@@ -1,0 +2,11 @@
+Wed Jul 08 06:03:33 UTC 2026 - Johannes Kastl
<[email protected]>
+
+- Update to version 1.2.0:
+ * Feature
+ - Added airlock-micrograteway emitter support (#413, @tyxeron)
+ * Bugfix
+ - Fixed warning with regex rewrites (#412, @Stevenjin8)
+ - Docs: Report.Notifications() accessor grouped by source
+ (#410, @marshallford
+
+-------------------------------------------------------------------
Old:
----
ingress2gateway-1.1.0.obscpio
New:
----
ingress2gateway-1.2.0.obscpio
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ ingress2gateway.spec ++++++
--- /var/tmp/diff_new_pack.gKvWz8/_old 2026-07-08 17:48:38.422696314 +0200
+++ /var/tmp/diff_new_pack.gKvWz8/_new 2026-07-08 17:48:38.422696314 +0200
@@ -17,7 +17,7 @@
Name: ingress2gateway
-Version: 1.1.0
+Version: 1.2.0
Release: 0
Summary: Convert Ingress resources to Gateway API resources
License: Apache-2.0
++++++ _service ++++++
--- /var/tmp/diff_new_pack.gKvWz8/_old 2026-07-08 17:48:38.470697978 +0200
+++ /var/tmp/diff_new_pack.gKvWz8/_new 2026-07-08 17:48:38.474698116 +0200
@@ -3,7 +3,7 @@
<param
name="url">https://github.com/kubernetes-sigs/ingress2gateway.git</param>
<param name="scm">git</param>
<param name="exclude">.git</param>
- <param name="revision">refs/tags/v1.1.0</param>
+ <param name="revision">refs/tags/v1.2.0</param>
<param name="versionformat">@PARENT_TAG@</param>
<param name="versionrewrite-pattern">v(.*)</param>
<param name="changesgenerate">enable</param>
++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.gKvWz8/_old 2026-07-08 17:48:38.498698949 +0200
+++ /var/tmp/diff_new_pack.gKvWz8/_new 2026-07-08 17:48:38.502699087 +0200
@@ -1,6 +1,6 @@
<servicedata>
<service name="tar_scm">
<param
name="url">https://github.com/kubernetes-sigs/ingress2gateway.git</param>
- <param
name="changesrevision">5327b1a6a806fc5470d5f7dddeef6076fdb94d9e</param></service></servicedata>
+ <param
name="changesrevision">afddc377909bc44b3f2664445839b021c1a3d971</param></service></servicedata>
(No newline at EOF)
++++++ ingress2gateway-1.1.0.obscpio -> ingress2gateway-1.2.0.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/ingress2gateway-1.1.0/CHANGELOG.md
new/ingress2gateway-1.2.0/CHANGELOG.md
--- old/ingress2gateway-1.1.0/CHANGELOG.md 2026-04-29 17:51:26.000000000
+0200
+++ new/ingress2gateway-1.2.0/CHANGELOG.md 2026-07-07 15:32:30.000000000
+0200
@@ -2,6 +2,7 @@
## Table of Contents
+- [v1.2.0](#v120)
- [v1.1.0](#v110)
- [v1.0.0](#v100)
- [v1.0.0-rc1](#v100-rc1)
@@ -16,6 +17,19 @@
- [v0.1.0](#v010)
- [v0.1.0-rc1](#v010-rc1)
+## v1.2.0
+
+## Changes by Kind
+
+### Feature
+
+- Added airlock-micrograteway emitter support (#413, @tyxeron)
+
+### Bugfix
+
+- Fixed warning with regex rewrites (#412, @Stevenjin8)
+- Docs: Report.Notifications() accessor grouped by source (#410, @marshallford)
+
## v1.1.0
## Changes by Kind
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/ingress2gateway-1.1.0/CODEOWNERS
new/ingress2gateway-1.2.0/CODEOWNERS
--- old/ingress2gateway-1.1.0/CODEOWNERS 2026-04-29 17:51:26.000000000
+0200
+++ new/ingress2gateway-1.2.0/CODEOWNERS 2026-07-07 15:32:30.000000000
+0200
@@ -1,3 +1,4 @@
* @LiorLieberman @rikatz @robscott
@Stevenjin8 @youngnick
/pkg/i2gw/emitters/envoygateway/ @kkk777-7
/pkg/i2gw/emitters/agentgateway/ @howardjohn @npolshakova @markuskobler
@danehans @puertomontt
+/pkg/i2gw/emitters/airlockmicrogateway/ @tyxeron @snorwin @root30 @nbam-e
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/ingress2gateway-1.1.0/README.md
new/ingress2gateway-1.2.0/README.md
--- old/ingress2gateway-1.1.0/README.md 2026-04-29 17:51:26.000000000 +0200
+++ new/ingress2gateway-1.2.0/README.md 2026-07-07 15:32:30.000000000 +0200
@@ -51,6 +51,7 @@
### Supported Emitters
* [standard](https://gateway-api.sigs.k8s.io/) (default)
* [agentgateway](https://agentgateway.dev/)
+* [airlock-microgateway](https://airlock.com/en/microgateway/)
* [envoy-gateway](https://gateway.envoyproxy.io/)
*
[gce](https://docs.cloud.google.com/kubernetes-engine/docs/concepts/gateway-api)
* [kgateway](https://kgateway.dev/)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/ingress2gateway-1.1.0/cmd/print.go
new/ingress2gateway-1.2.0/cmd/print.go
--- old/ingress2gateway-1.1.0/cmd/print.go 2026-04-29 17:51:26.000000000
+0200
+++ new/ingress2gateway-1.2.0/cmd/print.go 2026-07-07 15:32:30.000000000
+0200
@@ -46,6 +46,7 @@
// Call init for emitters
_
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/emitters/agentgateway"
+ _
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/emitters/airlockmicrogateway"
_
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/emitters/envoygateway"
_ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/emitters/gce"
_
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/emitters/kgateway"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/ingress2gateway-1.1.0/pkg/i2gw/emitters/airlockmicrogateway/emitter.go
new/ingress2gateway-1.2.0/pkg/i2gw/emitters/airlockmicrogateway/emitter.go
--- old/ingress2gateway-1.1.0/pkg/i2gw/emitters/airlockmicrogateway/emitter.go
1970-01-01 01:00:00.000000000 +0100
+++ new/ingress2gateway-1.2.0/pkg/i2gw/emitters/airlockmicrogateway/emitter.go
2026-07-07 15:32:30.000000000 +0200
@@ -0,0 +1,146 @@
+/*
+Copyright 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 airlockmicrogateway_emitter
+
+import (
+ "fmt"
+
+ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw"
+ emitterir
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/emitter_intermediate"
+ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/emitters/utils"
+ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/notifications"
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/apimachinery/pkg/util/validation/field"
+ "k8s.io/utils/ptr"
+ gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+)
+
+const (
+ emitterName = "airlock-microgateway"
+
+ unsupportedRouteWarningFmtStr = "dropping %s because it is unsupported
by airlock-microgateway"
+ gatewayParametersNameSuffix = "-params"
+)
+
+var gatewayParametersGVK = schema.GroupVersionKind{Group:
"microgateway.airlock.com", Version: "v1alpha1", Kind: "GatewayParameters"}
+
+func init() {
+ i2gw.EmitterConstructorByName[emitterName] = NewEmitter
+}
+
+type Emitter struct {
+ notify notifications.NotifyFunc
+}
+
+func NewEmitter(conf *i2gw.EmitterConf) i2gw.Emitter {
+ return &Emitter{
+ notify: conf.Report.Notifier(emitterName),
+ }
+}
+
+func (e *Emitter) Emit(ir emitterir.EmitterIR) (gr i2gw.GatewayResources, errs
field.ErrorList) {
+ utils.AddHTTPRouteRuleNames(ir)
+ for ns, gw := range ir.Gateways {
+ gw.Spec.GatewayClassName = emitterName
+ ir.Gateways[ns] = gw
+ }
+ e.warnAndDropUnsupportedRoutes(&ir)
+ gr, errs = utils.ToGatewayResources(ir)
+ if len(errs) != 0 {
+ return
+ }
+ e.applyRegularExpressionPathMatchFeature(&gr)
+ utils.LogUnparsedErrors(ir, e.notify)
+ return gr, nil
+}
+
+func (e *Emitter) warnAndDropUnsupportedRoutes(ir *emitterir.EmitterIR) {
+ for key, r := range ir.TLSRoutes {
+ e.notify(notifications.WarningNotification,
fmt.Sprintf(unsupportedRouteWarningFmtStr, "TLSRoute"), &r.TLSRoute)
+ delete(ir.TLSRoutes, key)
+ }
+ for key, r := range ir.TCPRoutes {
+ e.notify(notifications.WarningNotification,
fmt.Sprintf(unsupportedRouteWarningFmtStr, "TCPRoute"), &r.TCPRoute)
+ delete(ir.TCPRoutes, key)
+ }
+ for key, r := range ir.UDPRoutes {
+ e.notify(notifications.WarningNotification,
fmt.Sprintf(unsupportedRouteWarningFmtStr, "UDPRoute"), &r.UDPRoute)
+ delete(ir.UDPRoutes, key)
+ }
+ for key, r := range ir.GRPCRoutes {
+ e.notify(notifications.WarningNotification,
fmt.Sprintf(unsupportedRouteWarningFmtStr, "GRPCRoute"), &r.GRPCRoute)
+ delete(ir.GRPCRoutes, key)
+ }
+}
+
+func (e *Emitter) applyRegularExpressionPathMatchFeature(gr
*i2gw.GatewayResources) {
+ gwsWithRegexFeatureFlag := map[types.NamespacedName]struct{}{}
+
+ for _, route := range gr.HTTPRoutes {
+ if !httpRouteUsesRegexPathMatch(&route) {
+ continue
+ }
+ for _, pRef := range route.Spec.ParentRefs {
+ namespacedName := types.NamespacedName{Namespace:
ptr.Deref((*string)(pRef.Namespace), route.Namespace), Name: string(pRef.Name)}
+ gwsWithRegexFeatureFlag[namespacedName] = struct{}{}
+ }
+ }
+
+ for key := range gwsWithRegexFeatureFlag {
+ gw := gr.Gateways[key]
+ gwParamsName := gw.Name + gatewayParametersNameSuffix
+
+ gr.GatewayExtensions = append(gr.GatewayExtensions,
gwParametersWithRegexPathMatchEnabled(gw.Namespace, gwParamsName))
+
+ if gw.Spec.Infrastructure == nil {
+ gw.Spec.Infrastructure =
&gatewayv1.GatewayInfrastructure{}
+ }
+ gw.Spec.Infrastructure.ParametersRef =
&gatewayv1.LocalParametersReference{
+ Group: gatewayv1.Group(gatewayParametersGVK.Group),
+ Kind: gatewayv1.Kind(gatewayParametersGVK.Kind),
+ Name: gwParamsName,
+ }
+ gr.Gateways[key] = gw
+ }
+}
+
+func httpRouteUsesRegexPathMatch(route *gatewayv1.HTTPRoute) bool {
+ for _, rule := range route.Spec.Rules {
+ for _, m := range rule.Matches {
+ if m.Path != nil && m.Path.Type != nil && *m.Path.Type
== gatewayv1.PathMatchRegularExpression {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+func gwParametersWithRegexPathMatchEnabled(namespace, name string)
unstructured.Unstructured {
+ gwParams := unstructured.Unstructured{Object: map[string]any{
+ "spec": map[string]any{
+ "features": map[string]any{
+ "httpRouteRegexPathMatchEnabled": true,
+ },
+ },
+ }}
+ gwParams.SetGroupVersionKind(gatewayParametersGVK)
+ gwParams.SetNamespace(namespace)
+ gwParams.SetName(name)
+ return gwParams
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/ingress2gateway-1.1.0/pkg/i2gw/emitters/airlockmicrogateway/emitter_test.go
new/ingress2gateway-1.2.0/pkg/i2gw/emitters/airlockmicrogateway/emitter_test.go
---
old/ingress2gateway-1.1.0/pkg/i2gw/emitters/airlockmicrogateway/emitter_test.go
1970-01-01 01:00:00.000000000 +0100
+++
new/ingress2gateway-1.2.0/pkg/i2gw/emitters/airlockmicrogateway/emitter_test.go
2026-07-07 15:32:30.000000000 +0200
@@ -0,0 +1,347 @@
+/*
+Copyright 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 airlockmicrogateway_emitter
+
+import (
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw"
+ emitterir
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/emitter_intermediate"
+ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/notifications"
+ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common"
+ apiequality "k8s.io/apimachinery/pkg/api/equality"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/utils/ptr"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+ gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
+)
+
+func TestEmit_Gateway(t *testing.T) {
+ e := &Emitter{notify: notifications.NoopNotify}
+ nn := types.NamespacedName{Namespace: "default", Name: "test-gateway"}
+
+ gr, errs := e.Emit(emitterir.EmitterIR{
+ Gateways: map[types.NamespacedName]emitterir.GatewayContext{
+ nn: {
+ Gateway: gatewayv1.Gateway{
+ Spec: gatewayv1.GatewaySpec{
+ Listeners:
[]gatewayv1.Listener{{
+ Name: "http",
+ Port: 80,
+ Protocol:
gatewayv1.HTTPProtocolType,
+ Hostname:
common.PtrTo(gatewayv1.Hostname("example.com")),
+ }},
+ },
+ },
+ },
+ },
+ })
+ if len(errs) != 0 {
+ t.Fatalf("unexpected errors: %v", errs)
+ }
+ gw, ok := gr.Gateways[nn]
+ if !ok {
+ t.Fatalf("missing gateway %s", nn)
+ }
+ if gw.Spec.GatewayClassName != emitterName {
+ t.Errorf("unexpected GatewayClassName %q",
gw.Spec.GatewayClassName)
+ }
+}
+
+func TestEmit_warnsAndDropsUnsupportedRoutes(t *testing.T) {
+ testCases := []struct {
+ name string
+ ir emitterir.EmitterIR
+ expectedGatewayResources i2gw.GatewayResources
+ expectedWarnings int
+ }{
+ {
+ name: "HTTPRoute is kept",
+ ir: emitterir.EmitterIR{
+ HTTPRoutes:
map[types.NamespacedName]emitterir.HTTPRouteContext{
+ {Namespace: "default", Name: "http"}: {
+ HTTPRoute: gatewayv1.HTTPRoute{
+ ObjectMeta:
metav1.ObjectMeta{Namespace: "default", Name: "http"},
+ },
+ },
+ },
+ },
+ expectedGatewayResources: i2gw.GatewayResources{
+ HTTPRoutes:
map[types.NamespacedName]gatewayv1.HTTPRoute{
+ {Namespace: "default", Name: "http"}: {
+ ObjectMeta:
metav1.ObjectMeta{Namespace: "default", Name: "http"},
+ },
+ },
+ },
+ },
+ {
+ name: "unsupported routes are dropped",
+ ir: emitterir.EmitterIR{
+ TLSRoutes:
map[types.NamespacedName]emitterir.TLSRouteContext{
+ {Namespace: "default", Name: "tls"}: {
+ TLSRoute:
gatewayv1.TLSRoute{ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name:
"tls"}},
+ },
+ },
+ TCPRoutes:
map[types.NamespacedName]emitterir.TCPRouteContext{
+ {Namespace: "default", Name: "tcp"}: {
+ TCPRoute:
gatewayv1alpha2.TCPRoute{ObjectMeta: metav1.ObjectMeta{Namespace: "default",
Name: "tcp"}},
+ },
+ },
+ UDPRoutes:
map[types.NamespacedName]emitterir.UDPRouteContext{
+ {Namespace: "default", Name: "udp"}: {
+ UDPRoute:
gatewayv1alpha2.UDPRoute{ObjectMeta: metav1.ObjectMeta{Namespace: "default",
Name: "udp"}},
+ },
+ },
+ GRPCRoutes:
map[types.NamespacedName]emitterir.GRPCRouteContext{
+ {Namespace: "default", Name: "grpc"}: {
+ GRPCRoute:
gatewayv1.GRPCRoute{ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name:
"grpc"}},
+ },
+ },
+ },
+ expectedGatewayResources: i2gw.GatewayResources{},
+ expectedWarnings: 4,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ var warnings int
+ notify := func(mt notifications.MessageType, _ string,
_ ...client.Object) {
+ if mt == notifications.WarningNotification {
+ warnings++
+ }
+ }
+ e := &Emitter{notify: notify}
+
+ gr, errs := e.Emit(tc.ir)
+ if len(errs) != 0 {
+ t.Fatalf("unexpected errors: %v", errs)
+ }
+
+ if warnings != tc.expectedWarnings {
+ t.Errorf("Expected %d warnings, got %d",
tc.expectedWarnings, warnings)
+ }
+
+ if !apiequality.Semantic.DeepEqual(gr.HTTPRoutes,
tc.expectedGatewayResources.HTTPRoutes) {
+ t.Errorf("HTTPRoutes mismatch (-want
+got):\n%s", cmp.Diff(tc.expectedGatewayResources.HTTPRoutes, gr.HTTPRoutes))
+ }
+
+ if len(gr.TLSRoutes) !=
len(tc.expectedGatewayResources.TLSRoutes) {
+ t.Errorf("Expected %d TLSRoutes, got %d",
len(tc.expectedGatewayResources.TLSRoutes), len(gr.TLSRoutes))
+ }
+ if len(gr.TCPRoutes) !=
len(tc.expectedGatewayResources.TCPRoutes) {
+ t.Errorf("Expected %d TCPRoutes, got %d",
len(tc.expectedGatewayResources.TCPRoutes), len(gr.TCPRoutes))
+ }
+ if len(gr.UDPRoutes) !=
len(tc.expectedGatewayResources.UDPRoutes) {
+ t.Errorf("Expected %d UDPRoutes, got %d",
len(tc.expectedGatewayResources.UDPRoutes), len(gr.UDPRoutes))
+ }
+ if len(gr.GRPCRoutes) !=
len(tc.expectedGatewayResources.GRPCRoutes) {
+ t.Errorf("Expected %d GRPCRoutes, got %d",
len(tc.expectedGatewayResources.GRPCRoutes), len(gr.GRPCRoutes))
+ }
+ })
+ }
+}
+
+func TestEmit_applyRegularExpressionPathMatchFeature(t *testing.T) {
+ gwName := types.NamespacedName{Namespace: "default", Name: "gw"}
+ routeName := types.NamespacedName{Namespace: "default", Name: "route"}
+ secondRouteName := types.NamespacedName{Namespace: "default", Name:
"route2"}
+
+ paramsRef := func(name string) *gatewayv1.LocalParametersReference {
+ return &gatewayv1.LocalParametersReference{
+ Group: gatewayv1.Group(gatewayParametersGVK.Group),
+ Kind: gatewayv1.Kind(gatewayParametersGVK.Kind),
+ Name: name,
+ }
+ }
+
+ testCases := []struct {
+ name string
+ gr i2gw.GatewayResources
+ wantExtensions []unstructured.Unstructured
+ wantGatewaysWithGatewayInfra
map[types.NamespacedName]*gatewayv1.GatewayInfrastructure
+ }{
+ {
+ name: "no regex path match does not modify Gateway",
+ gr: i2gw.GatewayResources{
+ Gateways:
map[types.NamespacedName]gatewayv1.Gateway{
+ gwName: {
+ ObjectMeta:
metav1.ObjectMeta{Namespace: gwName.Namespace, Name: gwName.Name},
+ },
+ },
+ HTTPRoutes:
map[types.NamespacedName]gatewayv1.HTTPRoute{
+ routeName: {
+ ObjectMeta:
metav1.ObjectMeta{Namespace: routeName.Namespace, Name: routeName.Name},
+ Spec: gatewayv1.HTTPRouteSpec{
+ CommonRouteSpec:
gatewayv1.CommonRouteSpec{
+ ParentRefs:
[]gatewayv1.ParentReference{{Name: gatewayv1.ObjectName(gwName.Name)}},
+ },
+ Rules:
[]gatewayv1.HTTPRouteRule{{
+ Matches:
[]gatewayv1.HTTPRouteMatch{{
+ Path:
&gatewayv1.HTTPPathMatch{
+
Type: ptr.To(gatewayv1.PathMatchPathPrefix),
+
Value: ptr.To("/"),
+ },
+ }},
+ }},
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "regex path match patches Gateway and adds
GatewayParameters",
+ gr: i2gw.GatewayResources{
+ Gateways:
map[types.NamespacedName]gatewayv1.Gateway{
+ gwName: {
+ ObjectMeta:
metav1.ObjectMeta{Namespace: gwName.Namespace, Name: gwName.Name},
+ },
+ },
+ HTTPRoutes:
map[types.NamespacedName]gatewayv1.HTTPRoute{
+ routeName: {
+ ObjectMeta:
metav1.ObjectMeta{Namespace: routeName.Namespace, Name: routeName.Name},
+ Spec: gatewayv1.HTTPRouteSpec{
+ CommonRouteSpec:
gatewayv1.CommonRouteSpec{
+ ParentRefs:
[]gatewayv1.ParentReference{{Name: gatewayv1.ObjectName(gwName.Name)}},
+ },
+ Rules:
[]gatewayv1.HTTPRouteRule{{
+ Matches:
[]gatewayv1.HTTPRouteMatch{{
+ Path:
&gatewayv1.HTTPPathMatch{
+
Type: ptr.To(gatewayv1.PathMatchRegularExpression),
+
Value: ptr.To("/"),
+ },
+ }},
+ }},
+ },
+ },
+ },
+ },
+ wantExtensions:
[]unstructured.Unstructured{gwParametersWithRegexPathMatchEnabled("default",
"gw-params")},
+ wantGatewaysWithGatewayInfra:
map[types.NamespacedName]*gatewayv1.GatewayInfrastructure{
+ gwName: {ParametersRef: paramsRef("gw-params")},
+ },
+ },
+ {
+ name: "multiple HTTPRoutes with regex path matching on
same Gateway create only one GatewayParameters",
+ gr: i2gw.GatewayResources{
+ Gateways:
map[types.NamespacedName]gatewayv1.Gateway{
+ gwName: {
+ ObjectMeta:
metav1.ObjectMeta{Namespace: gwName.Namespace, Name: gwName.Name},
+ },
+ },
+ HTTPRoutes:
map[types.NamespacedName]gatewayv1.HTTPRoute{
+ routeName: {
+ ObjectMeta:
metav1.ObjectMeta{Namespace: routeName.Namespace, Name: routeName.Name},
+ Spec: gatewayv1.HTTPRouteSpec{
+ CommonRouteSpec:
gatewayv1.CommonRouteSpec{
+ ParentRefs:
[]gatewayv1.ParentReference{{Name: gatewayv1.ObjectName(gwName.Name)}},
+ },
+ Rules:
[]gatewayv1.HTTPRouteRule{{
+ Matches:
[]gatewayv1.HTTPRouteMatch{{
+ Path:
&gatewayv1.HTTPPathMatch{
+
Type: ptr.To(gatewayv1.PathMatchRegularExpression),
+
Value: ptr.To("/"),
+ },
+ }},
+ }},
+ },
+ },
+ secondRouteName: {
+ ObjectMeta:
metav1.ObjectMeta{Namespace: secondRouteName.Namespace, Name:
secondRouteName.Name},
+ Spec: gatewayv1.HTTPRouteSpec{
+ CommonRouteSpec:
gatewayv1.CommonRouteSpec{
+ ParentRefs:
[]gatewayv1.ParentReference{{Name: gatewayv1.ObjectName(gwName.Name)}},
+ },
+ Rules:
[]gatewayv1.HTTPRouteRule{{
+ Matches:
[]gatewayv1.HTTPRouteMatch{{
+ Path:
&gatewayv1.HTTPPathMatch{
+
Type: ptr.To(gatewayv1.PathMatchRegularExpression),
+
Value: ptr.To("/"),
+ },
+ }},
+ }},
+ },
+ },
+ },
+ },
+ wantExtensions:
[]unstructured.Unstructured{gwParametersWithRegexPathMatchEnabled("default",
"gw-params")},
+ wantGatewaysWithGatewayInfra:
map[types.NamespacedName]*gatewayv1.GatewayInfrastructure{
+ gwName: {ParametersRef: paramsRef("gw-params")},
+ },
+ },
+ {
+ name: "existing infrastructure config is preserved when
patching ParametersRef",
+ gr: i2gw.GatewayResources{
+ Gateways:
map[types.NamespacedName]gatewayv1.Gateway{
+ gwName: {
+ ObjectMeta:
metav1.ObjectMeta{Namespace: gwName.Namespace, Name: gwName.Name},
+ Spec:
gatewayv1.GatewaySpec{Infrastructure: &gatewayv1.GatewayInfrastructure{
+ Labels:
map[gatewayv1.LabelKey]gatewayv1.LabelValue{"k": "v"},
+ }},
+ },
+ },
+ HTTPRoutes:
map[types.NamespacedName]gatewayv1.HTTPRoute{
+ routeName: {
+ ObjectMeta:
metav1.ObjectMeta{Namespace: routeName.Namespace, Name: routeName.Name},
+ Spec: gatewayv1.HTTPRouteSpec{
+ CommonRouteSpec:
gatewayv1.CommonRouteSpec{
+ ParentRefs:
[]gatewayv1.ParentReference{{Name: gatewayv1.ObjectName(gwName.Name)}},
+ },
+ Rules:
[]gatewayv1.HTTPRouteRule{{
+ Matches:
[]gatewayv1.HTTPRouteMatch{{
+ Path:
&gatewayv1.HTTPPathMatch{
+
Type: ptr.To(gatewayv1.PathMatchRegularExpression),
+
Value: ptr.To("/"),
+ },
+ }},
+ }},
+ },
+ },
+ },
+ },
+ wantExtensions:
[]unstructured.Unstructured{gwParametersWithRegexPathMatchEnabled("default",
"gw-params")},
+ wantGatewaysWithGatewayInfra:
map[types.NamespacedName]*gatewayv1.GatewayInfrastructure{
+ gwName: {
+ Labels:
map[gatewayv1.LabelKey]gatewayv1.LabelValue{"k": "v"},
+ ParametersRef: paramsRef("gw-params"),
+ },
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ e := &Emitter{notify: notifications.NoopNotify}
+ e.applyRegularExpressionPathMatchFeature(&tc.gr)
+
+ if
!apiequality.Semantic.DeepEqual(tc.gr.GatewayExtensions, tc.wantExtensions) {
+ t.Errorf("GatewayExtensions mismatch (-want
+got):\n%s", cmp.Diff(tc.wantExtensions, tc.gr.GatewayExtensions))
+ }
+
+ for key, g := range tc.gr.Gateways {
+ wantInfra :=
tc.wantGatewaysWithGatewayInfra[key]
+ if
!apiequality.Semantic.DeepEqual(g.Spec.Infrastructure, wantInfra) {
+ t.Errorf("Gateway %s Infrastructure
mismatch (-want +got):\n%s", key, cmp.Diff(wantInfra, g.Spec.Infrastructure))
+ }
+ }
+ })
+ }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/ingress2gateway-1.1.0/pkg/i2gw/notifications/report.go
new/ingress2gateway-1.2.0/pkg/i2gw/notifications/report.go
--- old/ingress2gateway-1.1.0/pkg/i2gw/notifications/report.go 2026-04-29
17:51:26.000000000 +0200
+++ new/ingress2gateway-1.2.0/pkg/i2gw/notifications/report.go 2026-07-07
15:32:30.000000000 +0200
@@ -68,6 +68,29 @@
r.mu.Unlock()
}
+func (r *Report) Notifications() map[string][]Notification {
+ if r == nil {
+ return nil
+ }
+
+ r.mu.Lock()
+ defer r.mu.Unlock()
+
+ if len(r.notifications) == 0 {
+ return nil
+ }
+
+ out := make(map[string][]Notification, len(r.notifications))
+ for source, ns := range r.notifications {
+ cloned := slices.Clone(ns)
+ for i := range cloned {
+ cloned[i].CallingObjects =
slices.Clone(cloned[i].CallingObjects)
+ }
+ out[source] = cloned
+ }
+ return out
+}
+
// Notifier returns a convenience function scoped to a single source name,
eliminating the need for
// per-package boilerplate.
func (r *Report) Notifier(source string) NotifyFunc {
@@ -84,14 +107,12 @@
// after conversion completes. Notifications are sorted by source name for
deterministic output.
// Returns "" when r is nil or there are no notifications.
func (r *Report) Render() string {
- if r == nil {
+ notifications := r.Notifications()
+ if len(notifications) == 0 {
return ""
}
- r.mu.Lock()
- defer r.mu.Unlock()
-
- sources := slices.Sorted(maps.Keys(r.notifications))
+ sources := slices.Sorted(maps.Keys(notifications))
// Returns the ANSI code, or "" when color is disabled.
c := func(code string) string {
@@ -104,7 +125,7 @@
var buf strings.Builder
for _, source := range sources {
- for _, n := range r.notifications[source] {
+ for _, n := range notifications[source] {
label, lcolor := levelLabel(n.Type)
// Top border with level.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/ingress2gateway-1.1.0/pkg/i2gw/notifications/report_test.go
new/ingress2gateway-1.2.0/pkg/i2gw/notifications/report_test.go
--- old/ingress2gateway-1.1.0/pkg/i2gw/notifications/report_test.go
2026-04-29 17:51:26.000000000 +0200
+++ new/ingress2gateway-1.2.0/pkg/i2gw/notifications/report_test.go
2026-07-07 15:32:30.000000000 +0200
@@ -167,6 +167,11 @@
_ = r.Render()
}
})
+ wg.Go(func() {
+ for range 100 {
+ _ = r.Notifications()
+ }
+ })
wg.Wait()
@@ -175,6 +180,65 @@
for _, provider := range providers {
assert.Equal(t, 100, strings.Count(result, "source:
"+strings.ToUpper(provider)))
}
+
+ m := r.Notifications()
+ for _, provider := range providers {
+ assert.Len(t, m[provider], 100)
+ }
+}
+
+func TestReportNotifications(t *testing.T) {
+ t.Run("nil report returns nil", func(t *testing.T) {
+ var r *Report
+ assert.Nil(t, r.Notifications())
+ })
+
+ t.Run("empty report returns nil", func(t *testing.T) {
+ r := NewReport(true)
+ assert.Nil(t, r.Notifications())
+ })
+
+ t.Run("grouped by source", func(t *testing.T) {
+ r := NewReport(true)
+ r.Add("kong", Notification{Type: WarningNotification, Message:
"kong-1"})
+ r.Add("istio", Notification{Type: InfoNotification, Message:
"istio-1"})
+ r.Add("istio", Notification{Type: ErrorNotification, Message:
"istio-2"})
+ r.Add("kong", Notification{Type: InfoNotification, Message:
"kong-2"})
+
+ assert.Equal(t, map[string][]Notification{
+ "istio": {
+ {Type: InfoNotification, Message: "istio-1"},
+ {Type: ErrorNotification, Message: "istio-2"},
+ },
+ "kong": {
+ {Type: WarningNotification, Message: "kong-1"},
+ {Type: InfoNotification, Message: "kong-2"},
+ },
+ }, r.Notifications())
+ })
+
+ t.Run("returned map is a defensive copy of the report state", func(t
*testing.T) {
+ ingress := &networkingv1.Ingress{
+ TypeMeta: metav1.TypeMeta{Kind: "Ingress"},
+ ObjectMeta: metav1.ObjectMeta{Name: "ing", Namespace:
"default"},
+ }
+ r := NewReport(true)
+ r.Add("p", Notification{Message: "first", CallingObjects:
[]client.Object{ingress}})
+
+ m := r.Notifications()
+ delete(m, "p")
+ m["evil"] = []Notification{{Message: "injected"}}
+
+ m2 := r.Notifications()
+ m2["p"][0].Message = "mutated"
+ m2["p"][0].CallingObjects[0] = nil
+
+ final := r.Notifications()
+ assert.Len(t, final, 1)
+ assert.NotContains(t, final, "evil")
+ assert.Equal(t, "first", final["p"][0].Message)
+ assert.Same(t, ingress, final["p"][0].CallingObjects[0])
+ })
}
func TestReportNotifier(t *testing.T) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/ingress2gateway-1.1.0/pkg/i2gw/providers/ingressnginx/rewrite.go
new/ingress2gateway-1.2.0/pkg/i2gw/providers/ingressnginx/rewrite.go
--- old/ingress2gateway-1.1.0/pkg/i2gw/providers/ingressnginx/rewrite.go
2026-04-29 17:51:26.000000000 +0200
+++ new/ingress2gateway-1.2.0/pkg/i2gw/providers/ingressnginx/rewrite.go
2026-07-07 15:32:30.000000000 +0200
@@ -18,7 +18,7 @@
import (
"fmt"
- "strings"
+ "regexp"
emitterir
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/emitter_intermediate"
providerir
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/provider_intermediate"
@@ -27,6 +27,10 @@
"k8s.io/apimachinery/pkg/util/validation/field"
)
+// captureGroupReferenceRegex matches ingress-nginx rewrite-target capture
group
+// references such as "$1", which Gateway API URLRewrite filters cannot
express.
+var captureGroupReferenceRegex = regexp.MustCompile(`\$\d`)
+
// applyRewriteTargetToEmitterIR is a temporary bridge until we decide how
rewrite
// should be integrated into the generic feature parsing flow.
//
@@ -75,13 +79,13 @@
pathRewriteIR.Headers["X-Forwarded-Prefix"] =
val
}
- if hasRegex && strings.Contains(rewriteTarget, "\\$") {
+ if hasRegex &&
captureGroupReferenceRegex.MatchString(rewriteTarget) {
pathRewriteIR.RegexCaptureGroupReferences = true
}
source := fmt.Sprintf("rewrite-target from Ingress
%s/%s", ing.Namespace, ing.Name)
paths := []*field.Path{field.NewPath("metadata",
"annotations", fmt.Sprintf("%q", RewriteTargetAnnotation))}
- if hasRegex && strings.Contains(rewriteTarget, "\\$") {
+ if hasRegex &&
captureGroupReferenceRegex.MatchString(rewriteTarget) {
// Otherwise, rewrites without capture group
references work.
pathRewriteIR.RegexCaptureGroupReferences = true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/ingress2gateway-1.1.0/pkg/i2gw/providers/ingressnginx/rewrite_test.go
new/ingress2gateway-1.2.0/pkg/i2gw/providers/ingressnginx/rewrite_test.go
--- old/ingress2gateway-1.1.0/pkg/i2gw/providers/ingressnginx/rewrite_test.go
2026-04-29 17:51:26.000000000 +0200
+++ new/ingress2gateway-1.2.0/pkg/i2gw/providers/ingressnginx/rewrite_test.go
2026-07-07 15:32:30.000000000 +0200
@@ -36,7 +36,7 @@
Namespace: "default",
Name: "ing",
Annotations: map[string]string{
- RewriteTargetAnnotation: "/rewritten/\\$1",
+ RewriteTargetAnnotation: "/rewritten/$1",
XForwardedPrefixAnnotation: "/prefix",
UseRegexAnnotation: "true",
},
@@ -75,8 +75,8 @@
if got == nil {
t.Fatalf("expected PathRewriteByRuleIdx[0] to be set")
}
- if got.ReplaceFullPath != "/rewritten/\\$1" {
- t.Fatalf("expected ReplaceFullPath=/rewritten/\\$1, got %q",
got.ReplaceFullPath)
+ if got.ReplaceFullPath != "/rewritten/$1" {
+ t.Fatalf("expected ReplaceFullPath=/rewritten/$1, got %q",
got.ReplaceFullPath)
}
if got.RegexCaptureGroupReferences != true {
t.Fatalf("expected Regex=true, got %v",
got.RegexCaptureGroupReferences)
++++++ ingress2gateway.obsinfo ++++++
--- /var/tmp/diff_new_pack.gKvWz8/_old 2026-07-08 17:48:38.962715031 +0200
+++ /var/tmp/diff_new_pack.gKvWz8/_new 2026-07-08 17:48:38.982715725 +0200
@@ -1,5 +1,5 @@
name: ingress2gateway
-version: 1.1.0
-mtime: 1777477886
-commit: 5327b1a6a806fc5470d5f7dddeef6076fdb94d9e
+version: 1.2.0
+mtime: 1783431150
+commit: afddc377909bc44b3f2664445839b021c1a3d971
++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/ingress2gateway/vendor.tar.gz
/work/SRC/openSUSE:Factory/.ingress2gateway.new.1982/vendor.tar.gz differ: char
30, line 1