From 67d269749bd77037ceda60aa642753be825deff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Pab=C3=B3n?= Date: Mon, 15 May 2017 22:56:39 -0400 Subject: [PATCH] aws: Support for ELB tagging by users This PR provides support for tagging AWS ELBs using information in an annotation and provided as a list of comma separated key-value pairs. Closes https://github.com/kubernetes/community/pull/404 --- pkg/cloudprovider/providers/aws/aws.go | 7 ++ .../providers/aws/aws_loadbalancer.go | 41 ++++++++++-- pkg/cloudprovider/providers/aws/aws_test.go | 65 +++++++++++++++++++ 3 files changed, 109 insertions(+), 4 deletions(-) diff --git a/pkg/cloudprovider/providers/aws/aws.go b/pkg/cloudprovider/providers/aws/aws.go index 1667ff015a4..666db672867 100644 --- a/pkg/cloudprovider/providers/aws/aws.go +++ b/pkg/cloudprovider/providers/aws/aws.go @@ -130,6 +130,12 @@ const ServiceAnnotationLoadBalancerSSLPorts = "service.beta.kubernetes.io/aws-lo // a HTTP listener is used. const ServiceAnnotationLoadBalancerBEProtocol = "service.beta.kubernetes.io/aws-load-balancer-backend-protocol" +// ServiceAnnotationLoadBalancerAdditionalTags is the annotation used on the service +// to specify a comma-separated list of key-value pairs which will be recorded as +// additional tags in the ELB. +// For example: "Key1=Val1,Key2=Val2,KeyNoVal1=,KeyNoVal2" +const ServiceAnnotationLoadBalancerAdditionalTags = "service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags" + const ( // volumeAttachmentConsecutiveErrorLimit is the number of consecutive errors we will ignore when waiting for a volume to attach/detach volumeAttachmentStatusConsecutiveErrorLimit = 10 @@ -2772,6 +2778,7 @@ func (c *Cloud) EnsureLoadBalancer(clusterName string, apiService *v1.Service, n internalELB, proxyProtocol, loadBalancerAttributes, + annotations, ) if err != nil { return nil, err diff --git a/pkg/cloudprovider/providers/aws/aws_loadbalancer.go b/pkg/cloudprovider/providers/aws/aws_loadbalancer.go index 2c33e708f66..df0eb7bb632 100644 --- a/pkg/cloudprovider/providers/aws/aws_loadbalancer.go +++ b/pkg/cloudprovider/providers/aws/aws_loadbalancer.go @@ -20,6 +20,7 @@ import ( "fmt" "reflect" "strconv" + "strings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" @@ -31,7 +32,36 @@ import ( const ProxyProtocolPolicyName = "k8s-proxyprotocol-enabled" -func (c *Cloud) ensureLoadBalancer(namespacedName types.NamespacedName, loadBalancerName string, listeners []*elb.Listener, subnetIDs []string, securityGroupIDs []string, internalELB, proxyProtocol bool, loadBalancerAttributes *elb.LoadBalancerAttributes) (*elb.LoadBalancerDescription, error) { +// getLoadBalancerAdditionalTags converts the comma separated list of key-value +// pairs in the ServiceAnnotationLoadBalancerAdditionalTags annotation and returns +// it as a map. +func getLoadBalancerAdditionalTags(annotations map[string]string) map[string]string { + additionalTags := make(map[string]string) + if additionalTagsList, ok := annotations[ServiceAnnotationLoadBalancerAdditionalTags]; ok { + additionalTagsList = strings.TrimSpace(additionalTagsList) + + // Break up list of "Key1=Val,Key2=Val2" + tagList := strings.Split(additionalTagsList, ",") + + // Break up "Key=Val" + for _, tagSet := range tagList { + tag := strings.Split(strings.TrimSpace(tagSet), "=") + + // Accept "Key=val" or "Key=" or just "Key" + if len(tag) >= 2 && len(tag[0]) != 0 { + // There is a key and a value, so save it + additionalTags[tag[0]] = tag[1] + } else if len(tag) == 1 && len(tag[0]) != 0 { + // Just "Key" + additionalTags[tag[0]] = "" + } + } + } + + return additionalTags +} + +func (c *Cloud) ensureLoadBalancer(namespacedName types.NamespacedName, loadBalancerName string, listeners []*elb.Listener, subnetIDs []string, securityGroupIDs []string, internalELB, proxyProtocol bool, loadBalancerAttributes *elb.LoadBalancerAttributes, annotations map[string]string) (*elb.LoadBalancerDescription, error) { loadBalancer, err := c.describeLoadBalancer(loadBalancerName) if err != nil { return nil, err @@ -55,9 +85,12 @@ func (c *Cloud) ensureLoadBalancer(namespacedName types.NamespacedName, loadBala createRequest.SecurityGroups = stringPointerArray(securityGroupIDs) - tags := c.tagging.buildTags(ResourceLifecycleOwned, map[string]string{ - TagNameKubernetesService: namespacedName.String(), - }) + // Get additional tags set by the user + tags := getLoadBalancerAdditionalTags(annotations) + + // Add default tags + tags[TagNameKubernetesService] = namespacedName.String() + tags = c.tagging.buildTags(ResourceLifecycleOwned, tags) for k, v := range tags { createRequest.Tags = append(createRequest.Tags, &elb.Tag{ diff --git a/pkg/cloudprovider/providers/aws/aws_test.go b/pkg/cloudprovider/providers/aws/aws_test.go index 11b896c52d9..ff45d403710 100644 --- a/pkg/cloudprovider/providers/aws/aws_test.go +++ b/pkg/cloudprovider/providers/aws/aws_test.go @@ -1284,3 +1284,68 @@ func TestProxyProtocolEnabled(t *testing.T) { result = proxyProtocolEnabled(fakeBackend) assert.False(t, result, "did not expect to find %s in %s", ProxyProtocolPolicyName, policies) } + +func TestGetLoadBalancerAdditionalTags(t *testing.T) { + tagTests := []struct { + Annotations map[string]string + Tags map[string]string + }{ + { + Annotations: map[string]string{ + ServiceAnnotationLoadBalancerAdditionalTags: "Key=Val", + }, + Tags: map[string]string{ + "Key": "Val", + }, + }, + { + Annotations: map[string]string{ + ServiceAnnotationLoadBalancerAdditionalTags: "Key1=Val1, Key2=Val2", + }, + Tags: map[string]string{ + "Key1": "Val1", + "Key2": "Val2", + }, + }, + { + Annotations: map[string]string{ + ServiceAnnotationLoadBalancerAdditionalTags: "Key1=, Key2=Val2", + "anotherKey": "anotherValue", + }, + Tags: map[string]string{ + "Key1": "", + "Key2": "Val2", + }, + }, + { + Annotations: map[string]string{ + "Nothing": "Key1=, Key2=Val2, Key3", + }, + Tags: map[string]string{}, + }, + { + Annotations: map[string]string{ + ServiceAnnotationLoadBalancerAdditionalTags: "K=V K1=V2,Key1========, =====, ======Val, =Val, , 234,", + }, + Tags: map[string]string{ + "K": "V K1", + "Key1": "", + "234": "", + }, + }, + } + + for _, tagTest := range tagTests { + result := getLoadBalancerAdditionalTags(tagTest.Annotations) + for k, v := range result { + if len(result) != len(tagTest.Tags) { + t.Errorf("incorrect expected length: %v != %v", result, tagTest.Tags) + continue + } + if tagTest.Tags[k] != v { + t.Errorf("%s != %s", tagTest.Tags[k], v) + continue + } + } + } +}