diff --git a/internal/adc/translator/apisixconsumer.go b/internal/adc/translator/apisixconsumer.go index 406f1c2cd..823d65bb8 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 000000000..e6a1ee4ab --- /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.LabelResourceKey: "user-resource-key", + }, + }, + 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, "ApisixConsumer/default/demo", translated.Labels[label.LabelResourceKey]) +} diff --git a/internal/adc/translator/apisixroute.go b/internal/adc/translator/apisixroute.go index a015f2098..f89a2053f 100644 --- a/internal/adc/translator/apisixroute.go +++ b/internal/adc/translator/apisixroute.go @@ -186,7 +186,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 @@ -199,9 +199,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 7dd71d63d..f295757b4 100644 --- a/internal/adc/translator/apisixroute_test.go +++ b/internal/adc/translator/apisixroute_test.go @@ -82,3 +82,40 @@ 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", + "k8s/resource-key": "user-resource-key", + }, + }, + } + + 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, "ApisixRoute/default/test-route", route.Labels["k8s/resource-key"]) +} diff --git a/internal/adc/translator/consumer.go b/internal/adc/translator/consumer.go index 703287652..93601fc30 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 000000000..c62527e36 --- /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.LabelResourceKey: "user-resource-key", + }, + }, + } + + 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, "Consumer/default/demo", translated.Labels[label.LabelResourceKey]) +} diff --git a/internal/controller/label/label.go b/internal/controller/label/label.go index e761b9ca4..cb5a539da 100644 --- a/internal/controller/label/label.go +++ b/internal/controller/label/label.go @@ -44,8 +44,19 @@ func GenLabel(obj client.Object, args ...string) Label { label[LabelControllerName] = config.ControllerConfig.ControllerName label[LabelManagedBy] = "apisix-ingress-controller" label[LabelResourceKey] = fmt.Sprintf("%s/%s/%s", label[LabelKind], label[LabelNamespace], label[LabelName]) - 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 000000000..3ca7f8fa5 --- /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", + LabelResourceKey: "user-resource-key", + LabelManagedBy: "user-manager", + LabelNamespace: "user-namespace", + LabelControllerName: "user-controller", + }, + }, + } + + 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, config.ControllerConfig.ControllerName, labels[LabelControllerName]) + require.Equal(t, "apisix-ingress-controller", labels[LabelManagedBy]) + require.Equal(t, "ApisixConsumer/default/demo", labels[LabelResourceKey]) +} + +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") +}