Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable Attributes #267

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions pkg/alb/loadbalancer/loadbalancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type LoadBalancer struct {
Listeners listeners.Listeners
CurrentTags util.Tags
DesiredTags util.Tags
CurrentAttributes []*elbv2.LoadBalancerAttribute
DesiredAttributes []*elbv2.LoadBalancerAttribute
CurrentPorts portList
DesiredPorts portList
CurrentManagedSG *string
Expand All @@ -48,6 +50,7 @@ const (
subnetsModified
tagsModified
schemeModified
attributesModified
managedSecurityGroupsModified
)

Expand All @@ -59,6 +62,7 @@ type NewDesiredLoadBalancerOptions struct {
Logger *log.Logger
Annotations *annotations.Annotations
Tags util.Tags
Attributes []*elbv2.LoadBalancerAttribute
}

type portList []int64
Expand Down Expand Up @@ -90,6 +94,10 @@ func NewDesiredLoadBalancer(o *NewDesiredLoadBalancerOptions) *LoadBalancer {
for _, port := range o.Annotations.Ports {
lsps = append(lsps, port.Port)
}

if len(o.Annotations.Attributes) != 0 {
newLoadBalancer.DesiredAttributes = o.Annotations.Attributes
}
if len(newLoadBalancer.Desired.SecurityGroups) == 0 {
newLoadBalancer.DesiredPorts = lsps
}
Expand Down Expand Up @@ -300,6 +308,17 @@ func (lb *LoadBalancer) create(rOpts *ReconcileOptions) error {

lb.Current = o.LoadBalancers[0]

newAttributes := &elbv2.ModifyLoadBalancerAttributesInput{
LoadBalancerArn: lb.Current.LoadBalancerArn,
Attributes: lb.DesiredAttributes,
}
_, err = albelbv2.ELBV2svc.ModifyLoadBalancerAttributes(newAttributes)
if err != nil {
rOpts.Eventf(api.EventTypeWarning, "ERROR", "Error adding attributes to %s: %s", *in.Name, err.Error())
lb.logger.Errorf("Failed to modify ELBV2 attributes (ALB): %s", err.Error())
return err
}

// when a desired managed sg was present, it was used and should be set as the new CurrentManagedSG.
if lb.DesiredManagedSG != nil {
lb.CurrentManagedSG = lb.DesiredManagedSG
Expand Down Expand Up @@ -373,6 +392,19 @@ func (lb *LoadBalancer) modify(rOpts *ReconcileOptions) error {
log.Prettify(lb.CurrentTags))
}

// Modify Attributes
if needsMod&attributesModified != 0 {
lb.logger.Infof("Start ELBV2 tag modification.")
if err := albelbv2.ELBV2svc.UpdateAttributes(lb.Current.LoadBalancerArn, lb.DesiredAttributes); err != nil {
rOpts.Eventf(api.EventTypeWarning, "ERROR", "%s tag modification failed: %s", *lb.Current.LoadBalancerName, err.Error())
lb.logger.Errorf("Failed ELBV2 (ALB) tag modification: %s", err.Error())

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It needs to return the err here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return fmt.Errorf("Failure adding ALB attributes: %s", err)
}
lb.CurrentAttributes = lb.DesiredAttributes
rOpts.Eventf(api.EventTypeNormal, "MODIFY", "%s attributes modified", *lb.Current.LoadBalancerName)
lb.logger.Infof("Completed ELBV2 tag modification. Attributes are %s.",
log.Prettify(lb.CurrentAttributes))
}
} else {
// Modification is needed, but required full replacement of ALB.
lb.logger.Infof("Start ELBV2 full modification (delete and create).")
Expand Down Expand Up @@ -497,6 +529,14 @@ func (lb *LoadBalancer) needsModification() (loadBalancerChange, bool) {
changes |= tagsModified
}

currentAttributes := albelbv2.Attributes{Items: lb.CurrentAttributes}
desiredAttributes := albelbv2.Attributes{Items: lb.DesiredAttributes}
sort.Sort(currentAttributes)
sort.Sort(desiredAttributes)
if log.Prettify(currentAttributes) != log.Prettify(desiredAttributes) {
changes |= attributesModified
}

return changes, true
}

Expand Down
30 changes: 30 additions & 0 deletions pkg/annotations/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const (
clusterTagValue = "shared"
albRoleTagKey = "tag:kubernetes.io/role/alb-ingress"
albManagedSubnetsCacheKey = "alb-managed-subnets"
attributesKey = "alb.ingress.kubernetes.io/attributes"
)

// Annotations contains all of the annotation configuration for an ingress
Expand All @@ -62,6 +63,7 @@ type Annotations struct {
SuccessCodes *string
Tags []*elbv2.Tag
VPCID *string
Attributes []*elbv2.LoadBalancerAttribute
}

type PortData struct {
Expand Down Expand Up @@ -102,6 +104,7 @@ func ParseAnnotations(annotations map[string]string, clusterName string) (*Annot
a.setSubnets(annotations, clusterName),
a.setSuccessCodes(annotations),
a.setTags(annotations),
a.setAttributes(annotations),
} {
if err != nil {
cache.Set(cacheKey, err, 1*time.Hour)
Expand All @@ -111,6 +114,33 @@ func ParseAnnotations(annotations map[string]string, clusterName string) (*Annot
return a, nil
}

func (a *Annotations) setAttributes(annotations map[string]string) error {
var attrs []*elbv2.LoadBalancerAttribute
var badAttrs []string
rawAttrs := util.NewAWSStringSlice(annotations[attributesKey])

for _, rawAttr := range rawAttrs {
parts := strings.Split(*rawAttr, "=")
switch {
case *rawAttr == "":
continue
case len(parts) != 2:
badAttrs = append(badAttrs, *rawAttr)
continue
}
attrs = append(attrs, &elbv2.LoadBalancerAttribute{
Key: aws.String(parts[0]),
Value: aws.String(parts[1]),
})
}
a.Attributes = attrs

if len(badAttrs) > 0 {
return fmt.Errorf("Unable to parse `%s` into Key=Value pair(s)", strings.Join(badAttrs, ", "))
}
return nil
}

func (a *Annotations) setBackendProtocol(annotations map[string]string) error {
if annotations[backendProtocolKey] == "" {
a.BackendProtocol = aws.String("HTTP")
Expand Down
25 changes: 24 additions & 1 deletion pkg/annotations/annotations_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package annotations

import "testing"
import (
"github.com/aws/aws-sdk-go/service/elbv2"
"testing"
)

const clusterName = "testCluster"

Expand Down Expand Up @@ -39,3 +42,23 @@ func TestSetScheme(t *testing.T) {
}
}
}

func TestSetAttributesAsList(t *testing.T) {
annotations := &Annotations{}
expected := elbv2.LoadBalancerAttribute{}
expected.SetKey("access_logs.s3.enabled")
expected.SetValue("true")

attributes := map[string]string{attributesKey: "access_logs.s3.enabled=true"}
err := annotations.setAttributes(attributes)

if err != nil || len(annotations.Attributes) != 1 {
t.Errorf("setAttributes - number of attributes incorrect")
}

actual := annotations.Attributes[0]

if err == nil && *actual.Key != *expected.Key || *actual.Value != *expected.Value {
t.Errorf("setAttributes - values did not match")
}
}
39 changes: 39 additions & 0 deletions pkg/aws/elbv2/elbv2.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type ELBV2API interface {
elbv2iface.ELBV2API
ClusterLoadBalancers(clusterName *string) ([]*elbv2.LoadBalancer, error)
UpdateTags(arn *string, old util.Tags, new util.Tags) error
UpdateAttributes(arn *string, new []*elbv2.LoadBalancerAttribute) error
RemoveTargetGroup(in elbv2.DeleteTargetGroupInput) error
DescribeTagsForArn(arn *string) (util.Tags, error)
DescribeTargetGroupTargetsForArn(arn *string) (util.AWSStringSlice, error)
Expand All @@ -36,6 +37,34 @@ type ELBV2API interface {
DescribeListenersForLoadBalancer(loadBalancerArn *string) ([]*elbv2.Listener, error)
}

type AttributesAPI interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}

type Attributes struct {
AttributesAPI
Items []*elbv2.LoadBalancerAttribute
}

func (a Attributes) Len() int {
return len(a.Items)
}

func (a Attributes) Less(i, j int) bool {
comparison := strings.Compare(*a.Items[i].Key, *a.Items[j].Key)
if comparison == -1 {
return true
} else {
return false
}
}

func (a Attributes) Swap(i, j int) {
a.Items[i], a.Items[j] = a.Items[j], a.Items[i]
}

// ELBV2 is our extension to AWS's elbv2.ELBV2
type ELBV2 struct {
elbv2iface.ELBV2API
Expand Down Expand Up @@ -220,3 +249,13 @@ func (e *ELBV2) UpdateTags(arn *string, old util.Tags, new util.Tags) error {

return nil
}

// Update Attributes adds attributes to the loadbalancer.
func (e *ELBV2) UpdateAttributes(arn *string, attributes []*elbv2.LoadBalancerAttribute) error {
newAttributes := &elbv2.ModifyLoadBalancerAttributesInput{
LoadBalancerArn: arn,
Attributes: attributes,
}
_, err := e.ModifyLoadBalancerAttributes(newAttributes)
return err
}
61 changes: 61 additions & 0 deletions pkg/aws/elbv2/elbv2_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package elbv2

import (
"sort"
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/elbv2"
"github.com/aws/aws-sdk-go/service/elbv2/elbv2iface"
"github.com/coreos/alb-ingress-controller/pkg/util/log"
)

type mockedELBV2DescribeLoadBalancers struct {
Expand Down Expand Up @@ -68,3 +70,62 @@ func TestClusterLoadBalancers(t *testing.T) {
}
}
}

func TestSortLoadBalancerAttributes(t *testing.T) {
key1 := "hello"
value1 := "world"
key2 := "other"
value2 := "value"
key3 := "something"
value3 := "else"
attributes1 := Attributes{
Items: []*elbv2.LoadBalancerAttribute{
&elbv2.LoadBalancerAttribute{
Key: &key2,
Value: &value2,
},
&elbv2.LoadBalancerAttribute{
Key: &key1,
Value: &value1,
},
},
}
attributes2 := Attributes{
Items: []*elbv2.LoadBalancerAttribute{
&elbv2.LoadBalancerAttribute{
Key: &key1,
Value: &value1,
},
&elbv2.LoadBalancerAttribute{
Key: &key2,
Value: &value2,
},
},
}
sort.Sort(attributes1)
sort.Sort(attributes2)
if log.Prettify(attributes1) != log.Prettify(attributes2) {
t.Errorf("LoadBalancerAttribute sort failed, expected attributes to be inequal.")
}
attributes2 = Attributes{
Items: []*elbv2.LoadBalancerAttribute{
&elbv2.LoadBalancerAttribute{
Key: &key1,
Value: &value1,
},
&elbv2.LoadBalancerAttribute{
Key: &key2,
Value: &value2,
},
&elbv2.LoadBalancerAttribute{
Key: &key3,
Value: &value3,
},
},
}
sort.Sort(attributes1)
sort.Sort(attributes2)
if log.Prettify(attributes1) == log.Prettify(attributes2) {
t.Errorf("LoadBalancerAttribute sort failed, expected attributes to be equal.")
}
}