Repository: dotnet/yarp (csharp)

Issue:
YARP IngressController creates duplicate Routes when service has port entries with same targetPort (explicit number and not as name reference)
### Describe the bug
YARP IngressController creates duplicate Routes when targeted service has multiple port entries with same targetPort where targetPort is declared as explicit number and not as name reference

### To Reproduce
Use Yarp as Ingress Controller.

Ingress:
```
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  labels:
    app.kubernetes.io/instance: dummy
    app.kubernetes.io/name: dummy
  name: dummy
spec:
  rules:
  - host: dummy.domain.dev
    http:
      paths:
      - backend:
          service:
            name: dummy
            port:
              number: 80
        path: /
        pathType: Prefix
```

Service:
```
apiVersion: v1
kind: Service
metadata:
  name: dummy
  namespace: default
spec:
  selector:
    app: dummy
  ports:
  - name: http
    port: 80
    targetPort: 80 //this causes the issue! if we use "http" name reference it works as expected
  - name: https
    port: 443
    targetPort: 80
 - name: metrics
    port: 9000
    targetPort: 9000
  type: ClusterIP
```

Pod:
```
....
    ports:
        - containerPort: 80
          name: http
          protocol: TCP
        - containerPort: 9000
          name: metrics
          protocol: TCP
...
```

Got Exceptions? Include both the message and the stack trace
```
The error looks like this:
[15:58:08 ERR] Failed to apply the new config.
System.AggregateException: The proxy config is invalid. (Duplicate route 'dummy.dev/')
 ---> System.ArgumentException: Duplicate route 'dummy.<namespace>:dummy.dev/'
```

### Further technical details

Please note that this Error does not occur when we the name reference "http" instead of 80 for the targetPorts of both entries!!

Pull request:
Kubernetes allow to define multiple times the same port number with different names.

It can cause some duplicate routes to be created.

Fix #2819

Patch:
diff --git a/src/Kubernetes.Controller/Converters/YarpParser.cs b/src/Kubernetes.Controller/Converters/YarpParser.cs
index fe4284d92..37107c642 100644
--- a/src/Kubernetes.Controller/Converters/YarpParser.cs
+++ b/src/Kubernetes.Controller/Converters/YarpParser.cs
@@ -95,6 +95,7 @@ private static void HandleIngressRulePath(YarpIngressContext ingressContext, V1S
         // make sure cluster is present
         foreach (var subset in subsets ?? Enumerable.Empty<V1EndpointSubset>())
         {
+            var isRoutePresent = false;
             foreach (var port in subset.Ports ?? Enumerable.Empty<Corev1EndpointPort>())
             {
                 if (!MatchesPort(port, servicePort))
@@ -102,10 +103,13 @@ private static void HandleIngressRulePath(YarpIngressContext ingressContext, V1S
                     continue;
                 }
 
-                var pathMatch = FixupPathMatch(path);
-                var host = rule.Host;
-
-                routes.Add(CreateRoute(ingressContext, path, cluster, pathMatch, host));
+                if (!isRoutePresent)
+                {
+                    var pathMatch = FixupPathMatch(path);
+                    var host = rule.Host;
+                    routes.Add(CreateRoute(ingressContext, path, cluster, pathMatch, host));
+                    isRoutePresent = true;
+                }
 
                 // Add destination for every endpoint address
                 foreach (var address in subset.Addresses ?? Enumerable.Empty<V1EndpointAddress>())


Test patch:
diff --git a/test/Kubernetes.Tests/IngressConversionTests.cs b/test/Kubernetes.Tests/IngressConversionTests.cs
index 70f474fac..449f58c66 100644
--- a/test/Kubernetes.Tests/IngressConversionTests.cs
+++ b/test/Kubernetes.Tests/IngressConversionTests.cs
@@ -40,6 +40,7 @@ public IngressConversionTests()
     [Theory]
     [InlineData("basic-ingress")]
     [InlineData("multiple-endpoints-ports")]
+    [InlineData("multiple-endpoints-same-port")]
     [InlineData("https")]
     [InlineData("exact-match")]
     [InlineData("annotations")]
diff --git a/test/Kubernetes.Tests/testassets/multiple-endpoints-same-port/clusters.json b/test/Kubernetes.Tests/testassets/multiple-endpoints-same-port/clusters.json
new file mode 100644
index 000000000..77a2f2663
--- /dev/null
+++ b/test/Kubernetes.Tests/testassets/multiple-endpoints-same-port/clusters.json
@@ -0,0 +1,19 @@
+[
+  {
+    "ClusterId": "frontend.default:80",
+    "LoadBalancingPolicy": null,
+    "SessionAffinity": null,
+    "HealthCheck": null,
+    "HttpClient": null,
+    "HttpRequest": null,
+    "Destinations": {
+      "http://10.244.2.38:80": {
+        "Address": "http://10.244.2.38:80",
+        "Health": null,
+        "Metadata": null
+      }
+    },
+    "Metadata": null
+  }
+]
+
diff --git a/test/Kubernetes.Tests/testassets/multiple-endpoints-same-port/ingress.yaml b/test/Kubernetes.Tests/testassets/multiple-endpoints-same-port/ingress.yaml
new file mode 100644
index 000000000..b7df3db31
--- /dev/null
+++ b/test/Kubernetes.Tests/testassets/multiple-endpoints-same-port/ingress.yaml
@@ -0,0 +1,49 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: minimal-ingress
+  namespace: default
+spec:
+  rules:
+  - http:
+      paths:
+      - path: /foo
+        pathType: Prefix
+        backend:
+          service:
+            name: frontend
+            port:
+              number: 80
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: frontend
+  namespace: default
+spec:
+  selector:
+    app: frontend
+  ports:
+  - name: http
+    port: 80
+    targetPort: 80
+  - name: other_http
+    port: 8080
+    targetPort: 80
+  type: ClusterIP
+---
+apiVersion: v1
+kind: Endpoints
+metadata:
+  name: frontend
+  namespace: default
+subsets:
+  - addresses:
+    - ip: 10.244.2.38
+    ports:
+    - name: http
+      port: 80
+      protocol: TCP
+    - name: other_http
+      port: 80
+      protocol: TCP
diff --git a/test/Kubernetes.Tests/testassets/multiple-endpoints-same-port/routes.json b/test/Kubernetes.Tests/testassets/multiple-endpoints-same-port/routes.json
new file mode 100644
index 000000000..e52724dea
--- /dev/null
+++ b/test/Kubernetes.Tests/testassets/multiple-endpoints-same-port/routes.json
@@ -0,0 +1,19 @@
+[
+  {
+    "RouteId": "minimal-ingress.default:/foo",
+    "Match": {
+      "Methods": null,
+      "Hosts": [],
+      "Path": "/foo/{**catch-all}",
+      "Headers": null,
+      "QueryParameters": null
+    },
+    "Order": null,
+    "ClusterId": "frontend.default:80",
+    "AuthorizationPolicy": null,
+    "RateLimiterPolicy": null,
+    "CorsPolicy": null,
+    "Metadata": null,
+    "Transforms": null
+  }
+]