Repository: traefik/traefik (language: go)

Issue:
KubernetesGateway provider updates all gateways, ignoring gatewayclass reference
### Welcome!

- [X] Yes, I've searched similar issues on [GitHub](https://github.com/traefik/traefik/issues) and didn't find any.
- [X] Yes, I've searched similar issues on the [Traefik community forum](https://community.traefik.io) and didn't find any.

### What did you do?

Two traefik-proxy instances in one namespace: one for internal LB, another for external.

One gateway in a different namespace, intended to be used for external connections:
```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: example
  namespace: example
spec:
  gatewayClassName: external
```
Expectation:
* The gateway status is updated by Traefik 1 and ignored by Traefik 2

### What did you see instead?

Both instances of Traefik update status of the gateway

### What version of Traefik are you using?

```
Version:      3.1.2
Codename:     comte
Go version:   go1.22.5
Built:        2024-08-06T13:37:51Z
OS/Arch:      linux/amd64
```

### What is your environment & configuration?

* Arguments
  * Traefik 1:
  ```
  --providers.kubernetesgateway.statusaddress.service.namespace=traefik
  --providers.kubernetesgateway.statusaddress.service.name=external
  --providers.kubernetesgateway.labelselector=traefik-lb-type=external
  ```
  * Traefik 2:
  ```
  --providers.kubernetesgateway.statusaddress.service.namespace=traefik
  --providers.kubernetesgateway.statusaddress.service.name=internal
  --providers.kubernetesgateway.labelselector=traefik-lb-type=internal
  ```
* GatewayClasses
  * Traefik 1:
  ```yaml
  apiVersion: gateway.networking.k8s.io/v1
  kind: GatewayClass
  metadata:
    labels:
      traefik-lb-type: external
    name: external
  spec:
    controllerName: traefik.io/gateway-controller
  ```
  * Traefik 2:
  ```yaml
  apiVersion: gateway.networking.k8s.io/v1
  kind: GatewayClass
  metadata:
    labels:
      traefik-lb-type: internal
    name: internal
  spec:
    controllerName: traefik.io/gateway-controller
  ```


### If applicable, please paste the log output in DEBUG level

_No response_


Discussion / hints:
We have something very similar. In our situation, we have two Traefik instances running in different namespaces that should manage their own gateways, such as internal and external.

To achieve this, we were considering using `labelSelector` in each instance to separate which instance manages which gatewayClass and dependent resources.

In reality, what occurs is that the Traefik controllers attempt to update status for both gateways.

UPDATE:
We think we found what is causing this issue here.

The problem seems to be related to this [part](https://github.com/traefik/traefik/blob/a398536688148c774f2c0f3bcc8ddc15280a0dad/pkg/provider/kubernetes/gateway/kubernetes.go#L327-L352), where Traefik will try to match `gatewayClass.spec.ControllerName` with the constant, i.e. `traefik.io/gateway-controller`. Since all gatewayClasses that Traefik manages will have the same controller name, it will always continue. Then, [here](https://github.com/traefik/traefik/blob/a398536688148c774f2c0f3bcc8ddc15280a0dad/pkg/provider/kubernetes/gateway/kubernetes.go#L407), it will try to update all gateways in all namespaces.

Fix patch:
diff --git a/pkg/provider/kubernetes/gateway/fixtures/gatewayclass_labelselector.yaml b/pkg/provider/kubernetes/gateway/fixtures/gatewayclass_labelselector.yaml
new file mode 100644
index 0000000000..a012ce69b6
--- /dev/null
+++ b/pkg/provider/kubernetes/gateway/fixtures/gatewayclass_labelselector.yaml
@@ -0,0 +1,51 @@
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: GatewayClass
+metadata:
+  name: traefik-internal
+  labels:
+    name: traefik-internal
+spec:
+  controllerName: traefik.io/gateway-controller
+
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: traefik-internal
+  namespace: default
+spec:
+  gatewayClassName: traefik-internal
+  listeners:
+    - name: http
+      protocol: HTTP
+      port: 9080
+      allowedRoutes:
+        namespaces:
+          from: Same
+
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: GatewayClass
+metadata:
+  name: traefik-external
+  labels:
+    name: traefik-external
+spec:
+  controllerName: traefik.io/gateway-controller
+
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: traefik-external
+  namespace: default
+spec:
+  gatewayClassName: traefik-external
+  listeners:
+    - name: http
+      protocol: HTTP
+      port: 9080
+      allowedRoutes:
+        namespaces:
+          from: Same
diff --git a/pkg/provider/kubernetes/gateway/kubernetes.go b/pkg/provider/kubernetes/gateway/kubernetes.go
index 2588182b58..faea170e3d 100644
--- a/pkg/provider/kubernetes/gateway/kubernetes.go
+++ b/pkg/provider/kubernetes/gateway/kubernetes.go
@@ -357,7 +357,13 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context) *dynamic.C
 		}
 	}
 
-	gateways := p.client.ListGateways()
+	var gateways []*gatev1.Gateway
+	for _, gateway := range p.client.ListGateways() {
+		if _, ok := gatewayClassNames[string(gateway.Spec.GatewayClassName)]; !ok {
+			continue
+		}
+		gateways = append(gateways, gateway)
+	}
 
 	var gatewayListeners []gatewayListener
 	for _, gateway := range gateways {
@@ -366,10 +372,6 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context) *dynamic.C
 			Str("namespace", gateway.Namespace).
 			Logger()
 
-		if _, ok := gatewayClassNames[string(gateway.Spec.GatewayClassName)]; !ok {
-			continue
-		}
-
 		gatewayListeners = append(gatewayListeners, p.loadGatewayListeners(logger.WithContext(ctx), gateway, conf)...)
 	}
 


Test patch:
diff --git a/pkg/provider/kubernetes/gateway/kubernetes_test.go b/pkg/provider/kubernetes/gateway/kubernetes_test.go
index e8929b639d..f885515c3a 100644
--- a/pkg/provider/kubernetes/gateway/kubernetes_test.go
+++ b/pkg/provider/kubernetes/gateway/kubernetes_test.go
@@ -49,6 +49,47 @@ func init() {
 	}
 }
 
+func TestGatewayClassLabelSelector(t *testing.T) {
+	k8sObjects, gwObjects := readResources(t, []string{"gatewayclass_labelselector.yaml"})
+
+	kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
+	gwClient := newGatewaySimpleClientSet(t, gwObjects...)
+
+	client := newClientImpl(kubeClient, gwClient)
+
+	// This is initialized by the Provider init method but this cannot be called in a unit test.
+	client.labelSelector = "name=traefik-internal"
+
+	eventCh, err := client.WatchAll(nil, make(chan struct{}))
+	require.NoError(t, err)
+
+	if len(k8sObjects) > 0 || len(gwObjects) > 0 {
+		// just wait for the first event
+		<-eventCh
+	}
+
+	p := Provider{
+		EntryPoints:   map[string]Entrypoint{"http": {Address: ":9080"}},
+		StatusAddress: &StatusAddress{IP: "1.2.3.4"},
+		client:        client,
+	}
+
+	_ = p.loadConfigurationFromGateways(context.Background())
+
+	gw, err := gwClient.GatewayV1().Gateways("default").Get(context.Background(), "traefik-external", metav1.GetOptions{})
+	require.NoError(t, err)
+
+	assert.Empty(t, gw.Status.Addresses)
+
+	gw, err = gwClient.GatewayV1().Gateways("default").Get(context.Background(), "traefik-internal", metav1.GetOptions{})
+	require.NoError(t, err)
+	require.Len(t, gw.Status.Addresses, 1)
+	require.NotNil(t, gw.Status.Addresses[0].Type)
+
+	assert.Equal(t, gatev1.IPAddressType, *gw.Status.Addresses[0].Type)
+	assert.Equal(t, "1.2.3.4", gw.Status.Addresses[0].Value)
+}
+
 func TestLoadHTTPRoutes(t *testing.T) {
 	testCases := []struct {
 		desc                string