Skip to content

Created a controller to manage ALB attributes #650

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

Merged
merged 15 commits into from
Sep 28, 2018
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
56 changes: 24 additions & 32 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,45 @@ module github.com/kubernetes-sigs/aws-alb-ingress-controller

require (
github.com/aws/aws-sdk-go v1.15.39
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973
github.com/blang/semver v3.5.1+incompatible
github.com/cenkalti/backoff v2.0.0+incompatible
github.com/davecgh/go-spew v1.1.1
github.com/eapache/channels v1.1.0
github.com/eapache/queue v1.1.0
github.com/ghodss/yaml v1.0.0
github.com/go-ini/ini v1.38.1
github.com/gogo/protobuf v1.1.1
github.com/eapache/queue v1.1.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-ini/ini v1.38.1 // indirect
github.com/gogo/protobuf v1.1.1 // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7
github.com/golang/protobuf v1.2.0
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf
github.com/googleapis/gnostic v0.2.0
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7
github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47
github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7 // indirect
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a // indirect
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect
github.com/googleapis/gnostic v0.2.0 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47 // indirect
github.com/imdario/mergo v0.0.0-20171009183408-7fe0c75c13ab
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8
github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be
github.com/karlseguin/ccache v2.0.2+incompatible
github.com/matttproud/golang_protobuf_extensions v1.0.1
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742
github.com/petar/GoLLRB v0.0.0-20130427215148-53be0d36a84c
github.com/peterbourgon/diskv v2.0.1+incompatible
github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect
github.com/petar/GoLLRB v0.0.0-20130427215148-53be0d36a84c // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.8.0
github.com/pmezard/go-difflib v1.0.0
github.com/prometheus/client_golang v0.9.0-pre1.0.20180919114304-73edb9af667d
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e
github.com/prometheus/procfs v0.0.0-20180920065004-418d78d0b9a7
github.com/spf13/pflag v1.0.1
github.com/stretchr/objx v0.1.1
github.com/stretchr/objx v0.1.1 // indirect
github.com/stretchr/testify v1.2.2
github.com/ticketmaster/aws-sdk-go-cache v0.0.0-20180926195306-58922816129c
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb
golang.org/x/net v0.0.0-20180724234803-3673e40ba225
golang.org/x/sys v0.0.0-20180724212812-e072cadbbdc8
golang.org/x/text v0.3.0
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb // indirect
golang.org/x/net v0.0.0-20180724234803-3673e40ba225 // indirect
golang.org/x/sys v0.0.0-20180724212812-e072cadbbdc8 // indirect
golang.org/x/text v0.3.0 // indirect
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 // indirect
gopkg.in/go-playground/pool.v3 v3.1.1
gopkg.in/inf.v0 v0.9.1
gopkg.in/yaml.v2 v2.2.1
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.2.1 // indirect
k8s.io/api v0.0.0-20180628040859-072894a440bd
k8s.io/apimachinery v0.0.0-20180621070125-103fd098999d
k8s.io/apiserver v0.0.0-20180628044425-01459b68eb5f
k8s.io/client-go v8.0.0+incompatible
k8s.io/kube-openapi v0.0.0-20180719232738-d8ea2fe547a4
k8s.io/kube-openapi v0.0.0-20180719232738-d8ea2fe547a4 // indirect
)
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+v
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.0-pre1.0.20180919114304-73edb9af667d h1:DYicbQQTRiJXT51TQULZYt3cNfpC+5y0TeG/vqwsGH4=
Expand All @@ -67,7 +68,9 @@ github.com/prometheus/procfs v0.0.0-20180920065004-418d78d0b9a7 h1:NgR6WN8nQ4SmF
github.com/prometheus/procfs v0.0.0-20180920065004-418d78d0b9a7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/ticketmaster/aws-sdk-go-cache v0.0.0-20180926195306-58922816129c h1:3cRzuTKE8C2VkXUQNHOU7FWOOMN1Ddklypzs4ZyQSBw=
github.com/ticketmaster/aws-sdk-go-cache v0.0.0-20180926195306-58922816129c/go.mod h1:H9sbOivuFYIUAS9No3MxP7K6WXz8i8Xg4qRJ/nu3zM4=
Expand Down
214 changes: 214 additions & 0 deletions internal/alb/lb/attributes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package lb

import (
"context"
"fmt"
"strconv"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/elbv2"
"github.com/aws/aws-sdk-go/service/elbv2/elbv2iface"
"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/albctx"
"github.com/kubernetes-sigs/aws-alb-ingress-controller/pkg/util/log"
api "k8s.io/api/core/v1"
)

const (
DeletionProtectionEnabledKey = "deletion_protection.enabled"
AccessLogsS3EnabledKey = "access_logs.s3.enabled"
AccessLogsS3BucketKey = "access_logs.s3.bucket"
AccessLogsS3PrefixKey = "access_logs.s3.prefix"
IdleTimeoutTimeoutSecondsKey = "idle_timeout.timeout_seconds"
RoutingHTTP2EnabledKey = "routing.http2.enabled"

DeletionProtectionEnabled = false
AccessLogsS3Enabled = false
AccessLogsS3Bucket = ""
AccessLogsS3Prefix = ""
IdleTimeoutTimeoutSeconds = 60
RoutingHTTP2Enabled = true
)

// Attributes represents the desired state of attributes for a load balancer.
type Attributes struct {
// LbArn is the ARN of the load balancer
LbArn string

// DeletionProtectionEnabled: deletion_protection.enabled - Indicates whether deletion protection
// is enabled. The value is true or false. The default is false.
DeletionProtectionEnabled bool

// AccessLogsS3Enabled: access_logs.s3.enabled - Indicates whether access logs are enabled.
// The value is true or false. The default is false.
AccessLogsS3Enabled bool

// AccessLogsS3Bucket: access_logs.s3.bucket - The name of the S3 bucket for the access logs.
// This attribute is required if access logs are enabled. The bucket must
// exist in the same region as the load balancer and have a bucket policy
// that grants Elastic Load Balancing permissions to write to the bucket.
AccessLogsS3Bucket string

// AccessLogsS3Prefix: access_logs.s3.prefix - The prefix for the location in the S3 bucket
// for the access logs.
AccessLogsS3Prefix string

// IdleTimeoutTimeoutSeconds: idle_timeout.timeout_seconds - The idle timeout value, in seconds. The
// valid range is 1-4000 seconds. The default is 60 seconds.
IdleTimeoutTimeoutSeconds int64

// RoutingHTTP2Enabled: routing.http2.enabled - Indicates whether HTTP/2 is enabled. The value
// is true or false. The default is true.
RoutingHTTP2Enabled bool
}

func NewAttributes(attrs []*elbv2.LoadBalancerAttribute) (a *Attributes, err error) {
a = &Attributes{
DeletionProtectionEnabled: DeletionProtectionEnabled,
AccessLogsS3Enabled: AccessLogsS3Enabled,
AccessLogsS3Bucket: AccessLogsS3Bucket,
AccessLogsS3Prefix: AccessLogsS3Prefix,
IdleTimeoutTimeoutSeconds: IdleTimeoutTimeoutSeconds,
RoutingHTTP2Enabled: RoutingHTTP2Enabled,
}
var e error
for _, attr := range attrs {
attrValue := aws.StringValue(attr.Value)
switch attrKey := aws.StringValue(attr.Key); attrKey {
case DeletionProtectionEnabledKey:
a.DeletionProtectionEnabled, err = strconv.ParseBool(attrValue)
if err != nil {
return a, fmt.Errorf("invalid load balancer attribute value %s=%s", attrKey, attrValue)
}
case AccessLogsS3EnabledKey:
a.AccessLogsS3Enabled, err = strconv.ParseBool(attrValue)
if err != nil {
return a, fmt.Errorf("invalid load balancer attribute value %s=%s", attrKey, attrValue)
}
case AccessLogsS3BucketKey:
a.AccessLogsS3Bucket = attrValue
case AccessLogsS3PrefixKey:
a.AccessLogsS3Prefix = attrValue
case IdleTimeoutTimeoutSecondsKey:
a.IdleTimeoutTimeoutSeconds, err = strconv.ParseInt(attrValue, 10, 64)
if err != nil {
return a, fmt.Errorf("invalid load balancer attribute value %s=%s", attrKey, attrValue)
}
if a.IdleTimeoutTimeoutSeconds < 1 || a.IdleTimeoutTimeoutSeconds > 4000 {
return a, fmt.Errorf("%s must be within 1-4000 seconds", attrKey)
}
case RoutingHTTP2EnabledKey:
a.RoutingHTTP2Enabled, err = strconv.ParseBool(attrValue)
if err != nil {
return a, fmt.Errorf("invalid load balancer attribute value %s=%s", attrKey, attrValue)
}
default:
e = NewInvalidAttribute(attrKey)
}
}
return a, e
}

// AttributesController provides functionality to manage Attributes
type AttributesController interface {
// Reconcile ensures the load balancer attributes in AWS matches the state specified by the ingress configuration.
Reconcile(context.Context, *Attributes) error
}

// NewAttributesController constructs a new attributes controller
func NewAttributesController(elbv2 elbv2iface.ELBV2API) AttributesController {
return &attributesController{
elbv2: elbv2,
}
}

type attributesController struct {
elbv2 elbv2iface.ELBV2API
}

func (c *attributesController) Reconcile(ctx context.Context, desired *Attributes) error {
raw, err := c.elbv2.DescribeLoadBalancerAttributes(&elbv2.DescribeLoadBalancerAttributesInput{
LoadBalancerArn: aws.String(desired.LbArn),
})

if err != nil {
return fmt.Errorf("failed to retrieve attributes from ELBV2 in AWS: %s", err.Error())
}

current, err := NewAttributes(raw.Attributes)
if err != nil && !IsInvalidAttribute(err) {
return fmt.Errorf("failed parsing attributes: %v", err)
}

changeSet := attributesChangeSet(current, desired)
if len(changeSet) > 0 {
albctx.GetLogger(ctx).Infof("Modifying ELBV2 attributes to %v.", log.Prettify(changeSet))
_, err = c.elbv2.ModifyLoadBalancerAttributes(&elbv2.ModifyLoadBalancerAttributesInput{
LoadBalancerArn: aws.String(desired.LbArn),
Attributes: changeSet,
})
if err != nil {
eventf, ok := albctx.GetEventf(ctx)
if ok {
eventf(api.EventTypeWarning, "ERROR", "%s attributes modification failed: %s", desired.LbArn, err.Error())
}
return fmt.Errorf("failed modifying attributes: %s", err)
}

}
return nil
}

// attributesChangeSet returns a list of elbv2.LoadBalancerAttribute required to change a into b
func attributesChangeSet(a, b *Attributes) (changeSet []*elbv2.LoadBalancerAttribute) {
if a.DeletionProtectionEnabled != b.DeletionProtectionEnabled {
changeSet = append(changeSet, lbAttribute(DeletionProtectionEnabledKey, fmt.Sprintf("%v", b.DeletionProtectionEnabled)))
}

if a.AccessLogsS3Enabled != b.AccessLogsS3Enabled {
changeSet = append(changeSet, lbAttribute(AccessLogsS3EnabledKey, fmt.Sprintf("%v", b.AccessLogsS3Enabled)))
}

if a.AccessLogsS3Bucket != b.AccessLogsS3Bucket {
changeSet = append(changeSet, lbAttribute(AccessLogsS3BucketKey, b.AccessLogsS3Bucket))
}

if a.AccessLogsS3Prefix != b.AccessLogsS3Prefix {
changeSet = append(changeSet, lbAttribute(AccessLogsS3PrefixKey, b.AccessLogsS3Prefix))
}

if a.IdleTimeoutTimeoutSeconds != b.IdleTimeoutTimeoutSeconds {
changeSet = append(changeSet, lbAttribute(IdleTimeoutTimeoutSecondsKey, fmt.Sprintf("%v", b.IdleTimeoutTimeoutSeconds)))
}

if a.RoutingHTTP2Enabled != b.RoutingHTTP2Enabled {
changeSet = append(changeSet, lbAttribute(RoutingHTTP2EnabledKey, fmt.Sprintf("%v", b.RoutingHTTP2Enabled)))
}

return
}

func lbAttribute(k, v string) *elbv2.LoadBalancerAttribute {
return &elbv2.LoadBalancerAttribute{Key: aws.String(k), Value: aws.String(v)}
}

// NewInvalidAttribute returns a new InvalidAttribute error
func NewInvalidAttribute(name string) error {
return InvalidAttribute{
Name: fmt.Sprintf("the load balancer attribute %v is not valid", name),
}
}

// InvalidAttribute error
type InvalidAttribute struct {
Name string
}

func (e InvalidAttribute) Error() string {
return e.Name
}

// IsInvalidAttribute checks if the err is from an invalid attribute
func IsInvalidAttribute(e error) bool {
_, ok := e.(InvalidAttribute)
return ok
}
Loading