This is an automated email from the ASF dual-hosted git repository.

AlinsRan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git


The following commit(s) were added to refs/heads/master by this push:
     new b0fa3e54 feat: support consumer labels from metadata labels (#2760)
b0fa3e54 is described below

commit b0fa3e5459e26b98a2c409a34d70af438ab79353
Author: AlinsRan <[email protected]>
AuthorDate: Tue May 12 11:48:59 2026 +0800

    feat: support consumer labels from metadata labels (#2760)
---
 internal/adc/translator/apisixconsumer.go      |  2 +-
 internal/adc/translator/apisixconsumer_test.go | 71 ++++++++++++++++++++++++
 internal/adc/translator/apisixroute.go         |  5 +-
 internal/adc/translator/apisixroute_test.go    | 39 +++++++++++++
 internal/adc/translator/consumer.go            |  2 +-
 internal/adc/translator/consumer_test.go       | 61 +++++++++++++++++++++
 internal/controller/label/label.go             | 13 ++++-
 internal/controller/label/label_test.go        | 76 ++++++++++++++++++++++++++
 8 files changed, 262 insertions(+), 7 deletions(-)

diff --git a/internal/adc/translator/apisixconsumer.go 
b/internal/adc/translator/apisixconsumer.go
index 406f1c2c..823d65bb 100644
--- a/internal/adc/translator/apisixconsumer.go
+++ b/internal/adc/translator/apisixconsumer.go
@@ -98,7 +98,7 @@ func (t *Translator) TranslateApisixConsumer(tctx 
*provider.TranslateContext, ac
                Username: username,
        }
        consumer.Plugins = plugins
-       consumer.Labels = label.GenLabel(ac)
+       consumer.Labels = label.GenLabelWithObjectLabels(ac)
        result.Consumers = append(result.Consumers, consumer)
        return result, nil
 }
diff --git a/internal/adc/translator/apisixconsumer_test.go 
b/internal/adc/translator/apisixconsumer_test.go
new file mode 100644
index 00000000..7dbe131c
--- /dev/null
+++ b/internal/adc/translator/apisixconsumer_test.go
@@ -0,0 +1,71 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 translator
+
+import (
+       "context"
+       "testing"
+
+       "github.com/go-logr/logr"
+       "github.com/stretchr/testify/require"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+       apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
+       "github.com/apache/apisix-ingress-controller/internal/controller/label"
+       "github.com/apache/apisix-ingress-controller/internal/provider"
+)
+
+func 
TestTranslateApisixConsumer_UsesMetadataLabelsWithoutOverwritingControllerLabels(t
 *testing.T) {
+       translator := NewTranslator(logr.Discard())
+       tctx := provider.NewDefaultTranslateContext(context.Background())
+
+       consumer := &apiv2.ApisixConsumer{
+               TypeMeta: metav1.TypeMeta{
+                       Kind:       "ApisixConsumer",
+                       APIVersion: apiv2.GroupVersion.String(),
+               },
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "demo",
+                       Namespace: "default",
+                       Labels: map[string]string{
+                               "team":               "payments",
+                               label.LabelName:      "user-value",
+                               label.LabelManagedBy: "user-manager",
+                       },
+               },
+               Spec: apiv2.ApisixConsumerSpec{
+                       AuthParameter: apiv2.ApisixConsumerAuthParameter{
+                               BasicAuth: &apiv2.ApisixConsumerBasicAuth{
+                                       Value: 
&apiv2.ApisixConsumerBasicAuthValue{
+                                               Username: "demo",
+                                               Password: "secret",
+                                       },
+                               },
+                       },
+               },
+       }
+
+       result, err := translator.TranslateApisixConsumer(tctx, consumer)
+       require.NoError(t, err)
+       require.Len(t, result.Consumers, 1)
+
+       translated := result.Consumers[0]
+       require.Equal(t, "payments", translated.Labels["team"])
+       require.Equal(t, consumer.Name, translated.Labels[label.LabelName])
+       require.Equal(t, "apisix-ingress-controller", 
translated.Labels[label.LabelManagedBy])
+}
diff --git a/internal/adc/translator/apisixroute.go 
b/internal/adc/translator/apisixroute.go
index 76b32970..b002d6f9 100644
--- a/internal/adc/translator/apisixroute.go
+++ b/internal/adc/translator/apisixroute.go
@@ -184,7 +184,7 @@ func (t *Translator) buildRoute(ar *apiv2.ApisixRoute, 
service *adc.Service, rul
        route.Name = adc.ComposeRouteName(ar.Namespace, ar.Name, rule.Name)
        route.ID = id.GenID(route.Name)
        route.Desc = "Created by apisix-ingress-controller, DO NOT modify it 
manually"
-       route.Labels = label.GenLabel(ar)
+       route.Labels = label.GenLabelWithObjectLabels(ar)
        route.EnableWebsocket = rule.Websocket
        if route.EnableWebsocket == nil && *enableWebsocket != nil {
                route.EnableWebsocket = *enableWebsocket
@@ -197,9 +197,6 @@ func (t *Translator) buildRoute(ar *apiv2.ApisixRoute, 
service *adc.Service, rul
        route.Timeout = timeout
        route.Uris = rule.Match.Paths
        route.Vars = vars
-       for key, value := range ar.GetObjectMeta().GetLabels() {
-               route.Labels[key] = value
-       }
 
        service.Routes = []*adc.Route{route}
 }
diff --git a/internal/adc/translator/apisixroute_test.go 
b/internal/adc/translator/apisixroute_test.go
index 6ea15aec..9feda221 100644
--- a/internal/adc/translator/apisixroute_test.go
+++ b/internal/adc/translator/apisixroute_test.go
@@ -82,3 +82,42 @@ func TestBuildService_HostsSet(t *testing.T) {
        // service.Hosts SHOULD be set — this is the canonical location for 
hosts.
        assert.Equal(t, []string{"example.com", "foo.com"}, service.Hosts)
 }
+
+func TestBuildRoute_MetadataLabelsDoNotOverwriteControllerLabels(t *testing.T) 
{
+       translator := NewTranslator(logr.Discard())
+
+       ar := &apiv2.ApisixRoute{
+               TypeMeta: metav1.TypeMeta{
+                       Kind:       "ApisixRoute",
+                       APIVersion: apiv2.GroupVersion.String(),
+               },
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "test-route",
+                       Namespace: "default",
+                       Labels: map[string]string{
+                               "team":          "payments",
+                               "k8s/name":      "user-value",
+                               "manager-by":    "user-manager",
+                               "k8s/namespace": "user-namespace",
+                       },
+               },
+       }
+
+       service := &adc.Service{}
+       rule := apiv2.ApisixRouteHTTP{
+               Name: "rule1",
+               Match: apiv2.ApisixRouteHTTPMatch{
+                       Paths: []string{"/api/*"},
+               },
+       }
+
+       var enableWebsocket *bool
+       translator.buildRoute(ar, service, rule, nil, nil, nil, 
&enableWebsocket)
+
+       assert.Len(t, service.Routes, 1)
+       route := service.Routes[0]
+       assert.Equal(t, "payments", route.Labels["team"])
+       assert.Equal(t, ar.Name, route.Labels["k8s/name"])
+       assert.Equal(t, ar.Namespace, route.Labels["k8s/namespace"])
+       assert.Equal(t, "apisix-ingress-controller", route.Labels["manager-by"])
+}
diff --git a/internal/adc/translator/consumer.go 
b/internal/adc/translator/consumer.go
index 70328765..93601fc3 100644
--- a/internal/adc/translator/consumer.go
+++ b/internal/adc/translator/consumer.go
@@ -71,7 +71,7 @@ func (t *Translator) TranslateConsumerV1alpha1(tctx 
*provider.TranslateContext,
                credentials = append(credentials, credential)
        }
        consumer.Credentials = credentials
-       consumer.Labels = label.GenLabel(consumerV)
+       consumer.Labels = label.GenLabelWithObjectLabels(consumerV)
        plugins := adctypes.Plugins{}
        for _, plugin := range consumerV.Spec.Plugins {
                pluginName := plugin.Name
diff --git a/internal/adc/translator/consumer_test.go 
b/internal/adc/translator/consumer_test.go
new file mode 100644
index 00000000..cd572d87
--- /dev/null
+++ b/internal/adc/translator/consumer_test.go
@@ -0,0 +1,61 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 translator
+
+import (
+       "context"
+       "testing"
+
+       "github.com/go-logr/logr"
+       "github.com/stretchr/testify/require"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+       "github.com/apache/apisix-ingress-controller/api/v1alpha1"
+       "github.com/apache/apisix-ingress-controller/internal/controller/label"
+       "github.com/apache/apisix-ingress-controller/internal/provider"
+)
+
+func 
TestTranslateConsumerV1alpha1_UsesMetadataLabelsWithoutOverwritingControllerLabels(t
 *testing.T) {
+       translator := NewTranslator(logr.Discard())
+       tctx := provider.NewDefaultTranslateContext(context.Background())
+
+       consumer := &v1alpha1.Consumer{
+               TypeMeta: metav1.TypeMeta{
+                       Kind:       "Consumer",
+                       APIVersion: v1alpha1.GroupVersion.String(),
+               },
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "demo",
+                       Namespace: "default",
+                       Labels: map[string]string{
+                               "team":               "payments",
+                               label.LabelName:      "user-value",
+                               label.LabelManagedBy: "user-manager",
+                       },
+               },
+       }
+
+       result, err := translator.TranslateConsumerV1alpha1(tctx, consumer)
+       require.NoError(t, err)
+       require.Len(t, result.Consumers, 1)
+
+       translated := result.Consumers[0]
+       require.Equal(t, "payments", translated.Labels["team"])
+       require.Equal(t, consumer.Name, translated.Labels[label.LabelName])
+       require.Equal(t, "apisix-ingress-controller", 
translated.Labels[label.LabelManagedBy])
+}
diff --git a/internal/controller/label/label.go 
b/internal/controller/label/label.go
index 694e262b..0c636f72 100644
--- a/internal/controller/label/label.go
+++ b/internal/controller/label/label.go
@@ -40,8 +40,19 @@ func GenLabel(client client.Object, args ...string) Label {
        label[LabelName] = client.GetName()
        label[LabelControllerName] = config.ControllerConfig.ControllerName
        label[LabelManagedBy] = "apisix-ingress-controller"
-       for i := 0; i < len(args); i += 2 {
+       for i := 0; i+1 < len(args); i += 2 {
                label[args[i]] = args[i+1]
        }
        return label
 }
+
+func GenLabelWithObjectLabels(obj client.Object, args ...string) Label {
+       label := make(Label)
+       for key, value := range obj.GetLabels() {
+               label[key] = value
+       }
+       for key, value := range GenLabel(obj, args...) {
+               label[key] = value
+       }
+       return label
+}
diff --git a/internal/controller/label/label_test.go 
b/internal/controller/label/label_test.go
new file mode 100644
index 00000000..81403447
--- /dev/null
+++ b/internal/controller/label/label_test.go
@@ -0,0 +1,76 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 label
+
+import (
+       "testing"
+
+       "github.com/stretchr/testify/require"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+       apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
+       "github.com/apache/apisix-ingress-controller/internal/controller/config"
+)
+
+func TestGenLabelWithObjectLabels(t *testing.T) {
+       consumer := &apiv2.ApisixConsumer{
+               TypeMeta: metav1.TypeMeta{
+                       Kind:       "ApisixConsumer",
+                       APIVersion: apiv2.GroupVersion.String(),
+               },
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "demo",
+                       Namespace: "default",
+                       Labels: map[string]string{
+                               "team":              "payments",
+                               LabelName:           "user-value",
+                               LabelManagedBy:      "user-manager",
+                               LabelNamespace:      "user-namespace",
+                               LabelControllerName: "user-controller",
+                               LabelKind:           "user-kind",
+                       },
+               },
+       }
+
+       labels := GenLabelWithObjectLabels(consumer)
+
+       require.Equal(t, "payments", labels["team"])
+       require.Equal(t, consumer.Name, labels[LabelName])
+       require.Equal(t, consumer.Namespace, labels[LabelNamespace])
+       require.Equal(t, "ApisixConsumer", labels[LabelKind])
+       require.Equal(t, config.ControllerConfig.ControllerName, 
labels[LabelControllerName])
+       require.Equal(t, "apisix-ingress-controller", labels[LabelManagedBy])
+}
+
+func TestGenLabel_IgnoresDanglingKeyArg(t *testing.T) {
+       consumer := &apiv2.ApisixConsumer{
+               TypeMeta: metav1.TypeMeta{
+                       Kind:       "ApisixConsumer",
+                       APIVersion: apiv2.GroupVersion.String(),
+               },
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "demo",
+                       Namespace: "default",
+               },
+       }
+
+       labels := GenLabel(consumer, "team", "payments", "dangling")
+
+       require.Equal(t, "payments", labels["team"])
+       require.NotContains(t, labels, "dangling")
+}

Reply via email to