From 9a684a4dbbb8677ea0e052ee6af20edb891d86ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilson=20J=C3=BAnior?= Date: Fri, 18 Oct 2024 15:38:54 -0300 Subject: [PATCH] Propagate App tags through a annotation with "tsuru.io/custom-tag-" prefix --- kubernetes/ingress.go | 30 ++++++++++++++++++++--- kubernetes/ingress_test.go | 49 ++++++++++++++++++++++++++++++++++++++ kubernetes/service.go | 2 ++ router/service.go | 1 + 4 files changed, 79 insertions(+), 3 deletions(-) diff --git a/kubernetes/ingress.go b/kubernetes/ingress.go index d5c8700..8bde657 100644 --- a/kubernetes/ingress.go +++ b/kubernetes/ingress.go @@ -24,6 +24,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/validation" typedV1 "k8s.io/client-go/kubernetes/typed/core/v1" networkingTypedV1 "k8s.io/client-go/kubernetes/typed/networking/v1" ) @@ -192,7 +193,7 @@ func (k *IngressService) Ensure(ctx context.Context, id router.InstanceID, o rou }, Spec: buildIngressSpec(vhosts, o.Opts.Route, backendServices, k), } - k.fillIngressMeta(ingress, o.Opts, id, o.Team) + k.fillIngressMeta(ingress, o.Opts, id, o.Team, o.Tags) if o.Opts.Acme { k.fillIngressTLS(ingress, id) ingress.ObjectMeta.Annotations[AnnotationsACMEKey] = "true" @@ -218,6 +219,7 @@ func (k *IngressService) Ensure(ctx context.Context, id router.InstanceID, o rou certIssuer: o.CertIssuers[cname], service: backendServices["default"], routerOpts: o.Opts, + tags: o.Tags, }) if err != nil { err = errors.Wrapf(err, "could not ensure CName: %q", cname) @@ -328,6 +330,7 @@ type ensureCNameBackendOpts struct { certIssuer string service *v1.Service routerOpts router.Opts + tags []string } func (k *IngressService) ensureCNameBackend(ctx context.Context, opts ensureCNameBackendOpts) error { @@ -377,7 +380,7 @@ func (k *IngressService) ensureCNameBackend(ctx context.Context, opts ensureCNam Spec: buildIngressSpec(map[string]string{"ensureCnameBackend": opts.cname}, opts.routerOpts.Route, map[string]*v1.Service{"ensureCnameBackend": opts.service}, k), } - k.fillIngressMeta(ingress, opts.routerOpts, opts.id, opts.team) + k.fillIngressMeta(ingress, opts.routerOpts, opts.id, opts.team, opts.tags) if opts.routerOpts.AcmeCName { k.fillIngressTLS(ingress, opts.id) ingress.ObjectMeta.Annotations[AnnotationsACMEKey] = "true" @@ -775,7 +778,7 @@ func (s *IngressService) SupportedOptions(ctx context.Context) map[string]string return opts } -func (s *IngressService) fillIngressMeta(i *networkingV1.Ingress, routerOpts router.Opts, id router.InstanceID, team string) { +func (s *IngressService) fillIngressMeta(i *networkingV1.Ingress, routerOpts router.Opts, id router.InstanceID, team string, tags []string) { if i.ObjectMeta.Labels == nil { i.ObjectMeta.Labels = map[string]string{} } @@ -814,6 +817,27 @@ func (s *IngressService) fillIngressMeta(i *networkingV1.Ingress, routerOpts rou i.ObjectMeta.Annotations[labelName] = optValue } } + + for _, tag := range tags { + parts := strings.SplitN(tag, "=", 2) + var key, value string + if len(parts) != 2 { + continue + } + + key = parts[0] + value = parts[1] + + if key == "" { + continue + } + labelName := customTagPrefixLabel + key + if len(validation.IsQualifiedName(labelName)) > 0 { + // Ignoring tags that are not valid identifiers for labels or annotations + continue + } + i.ObjectMeta.Labels[labelName] = value + } } func (s *IngressService) validateCustomIssuer(ctx context.Context, resource CertManagerIssuerData, ns string) error { diff --git a/kubernetes/ingress_test.go b/kubernetes/ingress_test.go index 23e2a4a..4cf9876 100644 --- a/kubernetes/ingress_test.go +++ b/kubernetes/ingress_test.go @@ -526,6 +526,55 @@ func TestIngressEnsureWithCNames(t *testing.T) { assert.Equal(t, foundIngress.Annotations[AnnotationsCNames], "") } +func TestIngressEnsureWithTags(t *testing.T) { + svc := createFakeService() + svc.Labels = map[string]string{"controller": "my-controller", "XPTO": "true"} + svc.Annotations = map[string]string{"ann1": "val1", "ann2": "val2"} + err := svc.Ensure(ctx, idForApp("test"), router.EnsureBackendOpts{ + Opts: router.Opts{ + Route: "/admin", + AdditionalOpts: map[string]string{ + "tsuru.io/some-annotation": "true", + "cert-manager.io/cluster-issuer": "letsencrypt-prod", + }, + }, + Tags: []string{"test.io", "product=myproduct"}, + Team: "default", + Prefixes: []router.BackendPrefix{ + { + Target: router.BackendTarget{ + Service: "test-web", + Namespace: "default", + }, + }, + { + Prefix: "subscriber", + Target: router.BackendTarget{ + Service: "test-subscriber", + Namespace: "default", + }, + }, + }, + }) + require.NoError(t, err) + foundIngress, err := svc.Client.NetworkingV1().Ingresses(svc.Namespace).Get(ctx, "kubernetes-router-test-ingress", metav1.GetOptions{}) + require.NoError(t, err) + + expectedIngress := defaultIngress("test", "default") + + expectedIngress.Spec.Rules[0].HTTP.Paths[0].Path = "/admin" + expectedIngress.Labels["controller"] = "my-controller" + expectedIngress.Labels["XPTO"] = "true" + expectedIngress.Labels["tsuru.io/app-name"] = "test" + expectedIngress.Labels["tsuru.io/app-team"] = "default" + expectedIngress.Labels["tsuru.io/custom-tag-product"] = "myproduct" + expectedIngress.Annotations["ann1"] = "val1" + expectedIngress.Annotations["ann2"] = "val2" + expectedIngress.Annotations["tsuru.io/some-annotation"] = "true" + + assert.Equal(t, expectedIngress, foundIngress) +} + func TestEnsureCertManagerIssuer(t *testing.T) { svc := createFakeService(false) diff --git a/kubernetes/service.go b/kubernetes/service.go index e979c56..6ac5117 100644 --- a/kubernetes/service.go +++ b/kubernetes/service.go @@ -53,6 +53,8 @@ const ( processLabel = "tsuru.io/app-process" appPoolLabel = "tsuru.io/app-pool" + customTagPrefixLabel = "tsuru.io/custom-tag-" + appCRDName = "apps.tsuru.io" ) diff --git a/router/service.go b/router/service.go index 7e52510..84e8da9 100644 --- a/router/service.go +++ b/router/service.go @@ -113,6 +113,7 @@ type EnsureBackendOpts struct { Opts Opts `json:"opts"` CNames []string `json:"cnames"` Team string `json:"team"` + Tags []string `json:"tags,omitempty"` CertIssuers map[string]string `json:"certIssuers"` Prefixes []BackendPrefix `json:"prefixes"` }