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

Reply via email to