Repository: k8gb-io/k8gb

Issue:
Ignore 'mesh' gateway when counting referenced gateways in VirtualService
### Description:
Currently, when counting the referenced gateways in the VirtualService, the gateway named "mesh" is included in the count, which can cause issues when trying to combine internal and external VirtualServices.

### Proposed Change:
- Ignore the "mesh" gateway when counting gateways in the VirtualService.
- Allow for the combined use of internal and external VirtualServices, as described in the Istio documentation.

The reserved word "mesh" is used to imply all the sidecars in the mesh. 
For more details: [Istio VirtualService Documentation](https://istio.io/latest/docs/reference/config/networking/virtual-service/#VirtualService-gateways)

### Example Code Change:
The following code in `k8gb/controllers/refresolver/istiovirtualservice/istiovirtualservice.go:105` is proposed to ignore the "mesh" gateway:

```go
var validGateways []string
for _, gateway := range virtualService.Spec.Gateways {
    if gateway != "mesh" {
        validGateways = append(validGateways, gateway)
    }
}

if len(validGateways) != 1 {
    return nil, fmt.Errorf("expected exactly 1 Gateway to be referenced by the VirtualService but %d were found", len(virtualService.Spec.Gateways))
}
gatewayRef := strings.Split(validGateways[0], "/")
```

### Example usage in Istio:
The "mesh" gateway can be combined with external gateways, allowing the same VirtualService rule to apply both internally and externally:

```yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: myapp
spec:
  hosts:
  - myapp.com
  - helloworld.default.svc.cluster.local
  gateways:
  - mesh
  - myapp-gateway
  http:
  - match:
    - uri:
        prefix: /hello
      gateways:
      - myapp-gateway
    route:
    - destination:
        host: helloworld.default.svc.cluster.local
        subset: v1
  - match:
    - gateways:
      - mesh
    route:
    - destination:
        host: helloworld.default.svc.cluster.local
        subset: v1


Hints from issue comments:

Solution patch:
diff --git a/chart/k8gb/templates/external-dns/external-dns.yaml b/chart/k8gb/templates/external-dns/external-dns.yaml
index ddf5d65b..f8933950 100644
--- a/chart/k8gb/templates/external-dns/external-dns.yaml
+++ b/chart/k8gb/templates/external-dns/external-dns.yaml
@@ -35,7 +35,7 @@ spec:
         - --managed-record-types=CNAME
         - --managed-record-types=NS
         - --interval={{ .Values.externaldns.interval }}
-        - --annotation-filter=k8gb.absa.oss/dnstype=extdns # filter out only relevant DNSEntrypoints
+        - --label-filter=k8gb.absa.oss/dnstype=extdns # filter out only relevant DNSEntrypoints
         - --txt-owner-id={{ include "k8gb.extdnsOwnerID" . }}
         - --txt-prefix=k8gb-{{ .Values.k8gb.clusterGeoTag }}- # add custom prefix to TXT records, critical for Cloudflare NS record creation
         - --provider={{ include "k8gb.extdnsProvider" . }}
diff --git a/controllers/providers/dns/external.go b/controllers/providers/dns/external.go
index 7d3b2ae5..22843344 100644
--- a/controllers/providers/dns/external.go
+++ b/controllers/providers/dns/external.go
@@ -78,9 +78,9 @@ func (p *ExternalDNSProvider) CreateZoneDelegationForExternalDNS(gslb *k8gbv1bet
 	}
 	NSRecord := &externaldns.DNSEndpoint{
 		ObjectMeta: metav1.ObjectMeta{
-			Name:        domainInfo.GetExternalDNSEndpointName(),
-			Namespace:   p.config.K8gbNamespace,
-			Annotations: map[string]string{"k8gb.absa.oss/dnstype": externalDNSTypeCommon},
+			Name:      domainInfo.GetExternalDNSEndpointName(),
+			Namespace: p.config.K8gbNamespace,
+			Labels:    map[string]string{"k8gb.absa.oss/dnstype": externalDNSTypeCommon},
 		},
 		Spec: externaldns.DNSEndpointSpec{
 			Endpoints: []*externaldns.Endpoint{
diff --git a/controllers/refresolver/istiovirtualservice/istiovirtualservice.go b/controllers/refresolver/istiovirtualservice/istiovirtualservice.go
index 99493685..830080b3 100644
--- a/controllers/refresolver/istiovirtualservice/istiovirtualservice.go
+++ b/controllers/refresolver/istiovirtualservice/istiovirtualservice.go
@@ -102,10 +102,18 @@ func getGslbVirtualServiceRef(gslb *k8gbv1beta1.Gslb, k8sClient client.Client) (
 
 // getGateway retrieves the istio gateway referenced by the istio virtual service
 func getGateway(virtualService *istio.VirtualService, k8sClient client.Client) (*istio.Gateway, error) {
-	if len(virtualService.Spec.Gateways) != 1 {
-		return nil, fmt.Errorf("expected exactly 1 Gateway to be referenced by the VirtualService but %d were found", len(virtualService.Spec.Gateways))
+	var ingressGateways []string
+	for _, gateway := range virtualService.Spec.Gateways {
+		// count only dedicated ingress gateways
+		if gateway != "mesh" {
+			ingressGateways = append(ingressGateways, gateway)
+		}
+	}
+
+	if len(ingressGateways) != 1 {
+		return nil, fmt.Errorf("expected exactly 1 Gateway to be referenced by the VirtualService but %d were found", len(ingressGateways))
 	}
-	gatewayRef := strings.Split(virtualService.Spec.Gateways[0], "/")
+	gatewayRef := strings.Split(ingressGateways[0], "/")
 	gatewayNamespace := gatewayRef[0]
 	gatewayName := gatewayRef[1]
 
diff --git a/docs/deploy_cloudflare.md b/docs/deploy_cloudflare.md
index e3b76e22..f55d59e4 100644
--- a/docs/deploy_cloudflare.md
+++ b/docs/deploy_cloudflare.md
@@ -116,7 +116,7 @@ $ kubectl -n k8gb get dnsendpoints.externaldns.k8s.io k8gb-ns-extdns -o yaml
 apiVersion: externaldns.k8s.io/v1alpha1
 kind: DNSEndpoint
 metadata:
-  annotations:
+  labels:
     k8gb.absa.oss/dnstype: extdns
   creationTimestamp: "2023-11-12T19:55:20Z"
   generation: 3
diff --git a/go.mod b/go.mod
index 05a92383..d830238b 100644
--- a/go.mod
+++ b/go.mod
@@ -22,7 +22,7 @@ require (
 	k8s.io/api v0.32.1
 	k8s.io/apimachinery v0.32.1
 	k8s.io/client-go v0.32.1
-	sigs.k8s.io/controller-runtime v0.20.2
+	sigs.k8s.io/controller-runtime v0.20.3
 	sigs.k8s.io/external-dns v0.15.1
 )
 
diff --git a/go.sum b/go.sum
index 71990cfe..1cf4921e 100644
--- a/go.sum
+++ b/go.sum
@@ -401,6 +401,8 @@ sigs.k8s.io/controller-runtime v0.20.1 h1:JbGMAG/X94NeM3xvjenVUaBjy6Ui4Ogd/J5Ztj
 sigs.k8s.io/controller-runtime v0.20.1/go.mod h1:BrP3w158MwvB3ZbNpaAcIKkHQ7YGpYnzpoSTZ8E14WU=
 sigs.k8s.io/controller-runtime v0.20.2 h1:/439OZVxoEc02psi1h4QO3bHzTgu49bb347Xp4gW1pc=
 sigs.k8s.io/controller-runtime v0.20.2/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY=
+sigs.k8s.io/controller-runtime v0.20.3 h1:I6Ln8JfQjHH7JbtCD2HCYHoIzajoRxPNuvhvcDbZgkI=
+sigs.k8s.io/controller-runtime v0.20.3/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY=
 sigs.k8s.io/external-dns v0.15.1 h1:7UXUtMrEuS4DZM/1A7gtuooJh2cIYSn3RiUX3buqPHs=
 sigs.k8s.io/external-dns v0.15.1/go.mod h1:wuDYInL5buZ56sqSXFc3Wj72diZ4Mw/i2UTL0nJBHYw=
 sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=



Test patch:
diff --git a/controllers/gslb_controller_reconciliation_test.go b/controllers/gslb_controller_reconciliation_test.go
index 386d81ac..df976230 100644
--- a/controllers/gslb_controller_reconciliation_test.go
+++ b/controllers/gslb_controller_reconciliation_test.go
@@ -954,13 +954,13 @@ func TestCreatesDNSNSRecordsForExtDNS(t *testing.T) {
 				client.ObjectKey{Namespace: predefinedConfig.K8gbNamespace, Name: "k8gb-ns-extdns-cloud-example-com"},
 				dnsEndpoint)
 			require.NoError(t, err, "Failed to get expected DNSEndpoint")
-			got := dnsEndpoint.Annotations["k8gb.absa.oss/dnstype"]
+			got := dnsEndpoint.Labels["k8gb.absa.oss/dnstype"]
 			gotEp := dnsEndpoint.Spec.Endpoints
 			prettyGot := str.ToString(gotEp)
 			prettyWant := str.ToString(wantEp)
 
 			// assert
-			assert.Equal(t, want, got, "got:\n %q annotation value,\n\n want:\n %q", got, want)
+			assert.Equal(t, want, got, "got:\n %q label value,\n\n want:\n %q", got, want)
 			assert.Equal(t, wantEp, gotEp, "got:\n %s DNSEndpoint,\n\n want:\n %s", prettyGot, prettyWant)
 		})
 }
@@ -1039,13 +1039,13 @@ func TestCreatesDNSNSRecordsForLoadBalancer(t *testing.T) {
 				client.ObjectKey{Namespace: predefinedConfig.K8gbNamespace, Name: "k8gb-ns-extdns-cloud-example-com"},
 				dnsEndpoint)
 			require.NoError(t, err, "Failed to get expected DNSEndpoint")
-			got := dnsEndpoint.Annotations["k8gb.absa.oss/dnstype"]
+			got := dnsEndpoint.Labels["k8gb.absa.oss/dnstype"]
 			gotEp := dnsEndpoint.Spec.Endpoints
 			prettyGot := str.ToString(gotEp)
 			prettyWant := str.ToString(wantEp)
 
 			// assert
-			assert.Equal(t, want, got, "got:\n %q annotation value,\n\n want:\n %q", got, want)
+			assert.Equal(t, want, got, "got:\n %q label value,\n\n want:\n %q", got, want)
 			assert.Equal(t, wantEp, gotEp, "got:\n %s DNSEndpoint,\n\n want:\n %s", prettyGot, prettyWant)
 		})
 }
diff --git a/controllers/providers/dns/external_test.go b/controllers/providers/dns/external_test.go
index e8f0a54b..62e75b45 100644
--- a/controllers/providers/dns/external_test.go
+++ b/controllers/providers/dns/external_test.go
@@ -94,9 +94,9 @@ var a = struct {
 
 var expectedDNSEndpoint = &externaldns.DNSEndpoint{
 	ObjectMeta: metav1.ObjectMeta{
-		Name:        fmt.Sprintf("k8gb-ns-%s", externalDNSTypeCommon),
-		Namespace:   a.Config.K8gbNamespace,
-		Annotations: map[string]string{"k8gb.absa.oss/dnstype": string(externalDNSTypeCommon)},
+		Name:      fmt.Sprintf("k8gb-ns-%s", externalDNSTypeCommon),
+		Namespace: a.Config.K8gbNamespace,
+		Labels:    map[string]string{"k8gb.absa.oss/dnstype": string(externalDNSTypeCommon)},
 	},
 	Spec: externaldns.DNSEndpointSpec{
 		Endpoints: []*externaldns.Endpoint{