kops/upup/pkg/fi/cloudup/awstasks/load_balancer.go

849 lines
26 KiB
Go

/*
Copyright 2016 The Kubernetes Authors.
Licensed 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 awstasks
import (
"fmt"
"sort"
"strconv"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/elb"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
"k8s.io/kops/util/pkg/slice"
)
// LoadBalancer manages an ELB. We find the existing ELB using the Name tag.
//go:generate fitask -type=LoadBalancer
type LoadBalancer struct {
// We use the Name tag to find the existing ELB, because we are (more or less) unrestricted when
// it comes to tag values, but the LoadBalancerName is length limited
Name *string
Lifecycle *fi.Lifecycle
// LoadBalancerName is the name in ELB, possibly different from our name
// (ELB is restricted as to names, so we have limited choices!)
// We use the Name tag to find the existing ELB.
LoadBalancerName *string
DNSName *string
HostedZoneId *string
Subnets []*Subnet
SecurityGroups []*SecurityGroup
Listeners map[string]*LoadBalancerListener
Scheme *string
HealthCheck *LoadBalancerHealthCheck
AccessLog *LoadBalancerAccessLog
ConnectionDraining *LoadBalancerConnectionDraining
ConnectionSettings *LoadBalancerConnectionSettings
CrossZoneLoadBalancing *LoadBalancerCrossZoneLoadBalancing
SSLCertificateID string
}
var _ fi.CompareWithID = &LoadBalancer{}
func (e *LoadBalancer) CompareWithID() *string {
return e.Name
}
type LoadBalancerListener struct {
InstancePort int
SSLCertificateID string
}
func (e *LoadBalancerListener) mapToAWS(loadBalancerPort int64) *elb.Listener {
return &elb.Listener{
LoadBalancerPort: aws.Int64(loadBalancerPort),
Protocol: aws.String("SSL"),
InstanceProtocol: aws.String("SSL"),
InstancePort: aws.Int64(int64(e.InstancePort)),
SSLCertificateId: aws.String(e.SSLCertificateID),
}
}
var _ fi.HasDependencies = &LoadBalancerListener{}
func (e *LoadBalancerListener) GetDependencies(tasks map[string]fi.Task) []fi.Task {
return nil
}
func findLoadBalancerByLoadBalancerName(cloud awsup.AWSCloud, loadBalancerName string) (*elb.LoadBalancerDescription, error) {
request := &elb.DescribeLoadBalancersInput{
LoadBalancerNames: []*string{&loadBalancerName},
}
found, err := describeLoadBalancers(cloud, request, func(lb *elb.LoadBalancerDescription) bool {
// TODO: Filter by cluster?
if aws.StringValue(lb.LoadBalancerName) == loadBalancerName {
return true
}
glog.Warningf("Got ELB with unexpected name: %q", lb.LoadBalancerName)
return false
})
if err != nil {
if awsError, ok := err.(awserr.Error); ok {
if awsError.Code() == "LoadBalancerNotFound" {
return nil, nil
}
}
return nil, fmt.Errorf("error listing ELBs: %v", err)
}
if len(found) == 0 {
return nil, nil
}
if len(found) != 1 {
return nil, fmt.Errorf("Found multiple ELBs with name %q", loadBalancerName)
}
return found[0], nil
}
func findLoadBalancerByAlias(cloud awsup.AWSCloud, alias *route53.AliasTarget) (*elb.LoadBalancerDescription, error) {
// TODO: Any way to avoid listing all ELBs?
request := &elb.DescribeLoadBalancersInput{}
dnsName := aws.StringValue(alias.DNSName)
matchDnsName := strings.TrimSuffix(dnsName, ".")
if matchDnsName == "" {
return nil, fmt.Errorf("DNSName not set on AliasTarget")
}
matchHostedZoneId := aws.StringValue(alias.HostedZoneId)
found, err := describeLoadBalancers(cloud, request, func(lb *elb.LoadBalancerDescription) bool {
// TODO: Filter by cluster?
if matchHostedZoneId != aws.StringValue(lb.CanonicalHostedZoneNameID) {
return false
}
lbDnsName := aws.StringValue(lb.DNSName)
lbDnsName = strings.TrimSuffix(lbDnsName, ".")
return lbDnsName == matchDnsName || "dualstack."+lbDnsName == matchDnsName
})
if err != nil {
return nil, fmt.Errorf("error listing ELBs: %v", err)
}
if len(found) == 0 {
return nil, nil
}
if len(found) != 1 {
return nil, fmt.Errorf("Found multiple ELBs with DNSName %q", dnsName)
}
return found[0], nil
}
func FindLoadBalancerByNameTag(cloud awsup.AWSCloud, findNameTag string) (*elb.LoadBalancerDescription, error) {
// TODO: Any way around this?
glog.V(2).Infof("Listing all ELBs for findLoadBalancerByNameTag")
request := &elb.DescribeLoadBalancersInput{}
// ELB DescribeTags has a limit of 20 names, so we set the page size here to 20 also
request.PageSize = aws.Int64(20)
var found []*elb.LoadBalancerDescription
var innerError error
err := cloud.ELB().DescribeLoadBalancersPages(request, func(p *elb.DescribeLoadBalancersOutput, lastPage bool) bool {
if len(p.LoadBalancerDescriptions) == 0 {
return true
}
// TODO: Filter by cluster?
var names []string
nameToELB := make(map[string]*elb.LoadBalancerDescription)
for _, elb := range p.LoadBalancerDescriptions {
name := aws.StringValue(elb.LoadBalancerName)
nameToELB[name] = elb
names = append(names, name)
}
tagMap, err := describeLoadBalancerTags(cloud, names)
if err != nil {
innerError = err
return false
}
for loadBalancerName, tags := range tagMap {
name, foundNameTag := awsup.FindELBTag(tags, "Name")
if !foundNameTag || name != findNameTag {
continue
}
elb := nameToELB[loadBalancerName]
found = append(found, elb)
}
return true
})
if err != nil {
return nil, fmt.Errorf("error describing LoadBalancers: %v", err)
}
if innerError != nil {
return nil, fmt.Errorf("error describing LoadBalancers: %v", innerError)
}
if len(found) == 0 {
return nil, nil
}
if len(found) != 1 {
return nil, fmt.Errorf("Found multiple ELBs with Name %q", findNameTag)
}
return found[0], nil
}
func describeLoadBalancers(cloud awsup.AWSCloud, request *elb.DescribeLoadBalancersInput, filter func(*elb.LoadBalancerDescription) bool) ([]*elb.LoadBalancerDescription, error) {
var found []*elb.LoadBalancerDescription
err := cloud.ELB().DescribeLoadBalancersPages(request, func(p *elb.DescribeLoadBalancersOutput, lastPage bool) (shouldContinue bool) {
for _, lb := range p.LoadBalancerDescriptions {
if filter(lb) {
found = append(found, lb)
}
}
return true
})
if err != nil {
return nil, fmt.Errorf("error listing ELBs: %v", err)
}
return found, nil
}
func describeLoadBalancerTags(cloud awsup.AWSCloud, loadBalancerNames []string) (map[string][]*elb.Tag, error) {
// TODO: Filter by cluster?
request := &elb.DescribeTagsInput{}
request.LoadBalancerNames = aws.StringSlice(loadBalancerNames)
// TODO: Cache?
glog.V(2).Infof("Querying ELB tags for %s", loadBalancerNames)
response, err := cloud.ELB().DescribeTags(request)
if err != nil {
return nil, err
}
tagMap := make(map[string][]*elb.Tag)
for _, tagset := range response.TagDescriptions {
tagMap[aws.StringValue(tagset.LoadBalancerName)] = tagset.Tags
}
return tagMap, nil
}
func (e *LoadBalancer) Find(c *fi.Context) (*LoadBalancer, error) {
cloud := c.Cloud.(awsup.AWSCloud)
lb, err := FindLoadBalancerByNameTag(cloud, fi.StringValue(e.Name))
if err != nil {
return nil, err
}
if lb == nil {
return nil, nil
}
actual := &LoadBalancer{}
actual.Name = e.Name
actual.LoadBalancerName = lb.LoadBalancerName
actual.DNSName = lb.DNSName
actual.HostedZoneId = lb.CanonicalHostedZoneNameID
actual.Scheme = lb.Scheme
actual.Lifecycle = e.Lifecycle
for _, subnet := range lb.Subnets {
actual.Subnets = append(actual.Subnets, &Subnet{ID: subnet})
}
for _, sg := range lb.SecurityGroups {
actual.SecurityGroups = append(actual.SecurityGroups, &SecurityGroup{ID: sg})
}
actual.Listeners = make(map[string]*LoadBalancerListener)
for _, ld := range lb.ListenerDescriptions {
l := ld.Listener
loadBalancerPort := strconv.FormatInt(aws.Int64Value(l.LoadBalancerPort), 10)
actualListener := &LoadBalancerListener{}
actualListener.InstancePort = int(aws.Int64Value(l.InstancePort))
actual.Listeners[loadBalancerPort] = actualListener
}
healthcheck, err := findHealthCheck(lb)
if err != nil {
return nil, err
}
actual.HealthCheck = healthcheck
// Extract attributes
lbAttributes, err := findELBAttributes(cloud, aws.StringValue(lb.LoadBalancerName))
if err != nil {
return nil, err
}
glog.V(4).Info("ELB attributes: %+v", lbAttributes)
if lbAttributes != nil {
actual.AccessLog = &LoadBalancerAccessLog{}
if lbAttributes.AccessLog.EmitInterval != nil {
actual.AccessLog.EmitInterval = lbAttributes.AccessLog.EmitInterval
}
if lbAttributes.AccessLog.Enabled != nil {
actual.AccessLog.Enabled = lbAttributes.AccessLog.Enabled
}
if lbAttributes.AccessLog.S3BucketName != nil {
actual.AccessLog.S3BucketName = lbAttributes.AccessLog.S3BucketName
}
if lbAttributes.AccessLog.S3BucketPrefix != nil {
actual.AccessLog.S3BucketPrefix = lbAttributes.AccessLog.S3BucketPrefix
}
actual.ConnectionDraining = &LoadBalancerConnectionDraining{}
if lbAttributes.ConnectionDraining.Enabled != nil {
actual.ConnectionDraining.Enabled = lbAttributes.ConnectionDraining.Enabled
}
if lbAttributes.ConnectionDraining.Timeout != nil {
actual.ConnectionDraining.Timeout = lbAttributes.ConnectionDraining.Timeout
}
actual.ConnectionSettings = &LoadBalancerConnectionSettings{}
if lbAttributes.ConnectionSettings.IdleTimeout != nil {
actual.ConnectionSettings.IdleTimeout = lbAttributes.ConnectionSettings.IdleTimeout
}
actual.CrossZoneLoadBalancing = &LoadBalancerCrossZoneLoadBalancing{}
if lbAttributes.CrossZoneLoadBalancing.Enabled != nil {
actual.CrossZoneLoadBalancing.Enabled = lbAttributes.CrossZoneLoadBalancing.Enabled
}
}
// Avoid spurious mismatches
if subnetSlicesEqualIgnoreOrder(actual.Subnets, e.Subnets) {
actual.Subnets = e.Subnets
}
if e.DNSName == nil {
e.DNSName = actual.DNSName
}
if e.HostedZoneId == nil {
e.HostedZoneId = actual.HostedZoneId
}
if e.LoadBalancerName == nil {
e.LoadBalancerName = actual.LoadBalancerName
}
// We allow for the LoadBalancerName to be wrong:
// 1. We don't want to force a rename of the ELB, because that is a destructive operation
// 2. We were creating ELBs with insufficiently qualified names previously
if fi.StringValue(e.LoadBalancerName) != fi.StringValue(actual.LoadBalancerName) {
glog.V(2).Infof("Reusing existing load balancer with name: %q", actual.LoadBalancerName)
e.LoadBalancerName = actual.LoadBalancerName
}
// TODO: Make Normalize a standard method
actual.Normalize()
glog.V(4).Infof("Found ELB %+v", actual)
return actual, nil
}
var _ fi.HasAddress = &LoadBalancer{}
func (e *LoadBalancer) FindIPAddress(context *fi.Context) (*string, error) {
cloud := context.Cloud.(awsup.AWSCloud)
lb, err := FindLoadBalancerByNameTag(cloud, fi.StringValue(e.Name))
if err != nil {
return nil, err
}
if lb == nil {
return nil, nil
}
lbDnsName := fi.StringValue(lb.DNSName)
if lbDnsName == "" {
return nil, nil
}
return &lbDnsName, nil
}
func (e *LoadBalancer) Run(c *fi.Context) error {
// TODO: Make Normalize a standard method
e.Normalize()
return fi.DefaultDeltaRunMethod(e, c)
}
func (e *LoadBalancer) Normalize() {
// We need to sort our arrays consistently, so we don't get spurious changes
sort.Stable(OrderSubnetsById(e.Subnets))
sort.Stable(OrderSecurityGroupsById(e.SecurityGroups))
}
func (s *LoadBalancer) CheckChanges(a, e, changes *LoadBalancer) error {
if a == nil {
if fi.StringValue(e.Name) == "" {
return fi.RequiredField("Name")
}
if len(e.SecurityGroups) == 0 {
return fi.RequiredField("SecurityGroups")
}
if len(e.Subnets) == 0 {
return fi.RequiredField("Subnets")
}
if e.AccessLog != nil {
if e.AccessLog.Enabled == nil {
return fi.RequiredField("Acceslog.Enabled")
}
if *e.AccessLog.Enabled {
if e.AccessLog.S3BucketName == nil {
return fi.RequiredField("Acceslog.S3Bucket")
}
}
}
if e.ConnectionDraining != nil {
if e.ConnectionDraining.Enabled == nil {
return fi.RequiredField("ConnectionDraining.Enabled")
}
}
if e.CrossZoneLoadBalancing != nil {
if e.CrossZoneLoadBalancing.Enabled == nil {
return fi.RequiredField("CrossZoneLoadBalancing.Enabled")
}
}
}
return nil
}
func (_ *LoadBalancer) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *LoadBalancer) error {
var loadBalancerName string
if a == nil {
if e.LoadBalancerName == nil {
return fi.RequiredField("LoadBalancerName")
}
loadBalancerName = *e.LoadBalancerName
request := &elb.CreateLoadBalancerInput{}
request.LoadBalancerName = e.LoadBalancerName
request.Scheme = e.Scheme
for _, subnet := range e.Subnets {
request.Subnets = append(request.Subnets, subnet.ID)
}
for _, sg := range e.SecurityGroups {
request.SecurityGroups = append(request.SecurityGroups, sg.ID)
}
request.Listeners = []*elb.Listener{}
for loadBalancerPort, listener := range e.Listeners {
loadBalancerPortInt, err := strconv.ParseInt(loadBalancerPort, 10, 64)
if err != nil {
return fmt.Errorf("error parsing load balancer listener port: %q", loadBalancerPort)
}
awsListener := listener.mapToAWS(loadBalancerPortInt)
request.Listeners = append(request.Listeners, awsListener)
}
glog.V(2).Infof("Creating ELB with Name:%q", loadBalancerName)
response, err := t.Cloud.ELB().CreateLoadBalancer(request)
if err != nil {
return fmt.Errorf("error creating ELB: %v", err)
}
e.DNSName = response.DNSName
// Requery to get the CanonicalHostedZoneNameID
lb, err := findLoadBalancerByLoadBalancerName(t.Cloud, loadBalancerName)
if err != nil {
return err
}
if lb == nil {
// TODO: Retry? Is this async
return fmt.Errorf("Unable to find newly created ELB %q", loadBalancerName)
}
e.HostedZoneId = lb.CanonicalHostedZoneNameID
} else {
loadBalancerName = fi.StringValue(a.LoadBalancerName)
if changes.Subnets != nil {
var expectedSubnets []string
for _, s := range e.Subnets {
expectedSubnets = append(expectedSubnets, fi.StringValue(s.ID))
}
var actualSubnets []string
for _, s := range a.Subnets {
actualSubnets = append(actualSubnets, fi.StringValue(s.ID))
}
oldSubnetIDs := slice.GetUniqueStrings(expectedSubnets, actualSubnets)
if len(oldSubnetIDs) > 0 {
request := &elb.DetachLoadBalancerFromSubnetsInput{}
request.SetLoadBalancerName(loadBalancerName)
request.SetSubnets(aws.StringSlice(oldSubnetIDs))
glog.V(2).Infof("Detaching Load Balancer from old subnets")
if _, err := t.Cloud.ELB().DetachLoadBalancerFromSubnets(request); err != nil {
return fmt.Errorf("Error detaching Load Balancer from old subnets: %v", err)
}
}
newSubnetIDs := slice.GetUniqueStrings(actualSubnets, expectedSubnets)
if len(newSubnetIDs) > 0 {
request := &elb.AttachLoadBalancerToSubnetsInput{}
request.SetLoadBalancerName(loadBalancerName)
request.SetSubnets(aws.StringSlice(newSubnetIDs))
glog.V(2).Infof("Attaching Load Balancer to new subnets")
if _, err := t.Cloud.ELB().AttachLoadBalancerToSubnets(request); err != nil {
return fmt.Errorf("Error attaching Load Balancer to new subnets: %v", err)
}
}
}
if changes.Listeners != nil {
request := &elb.CreateLoadBalancerListenersInput{}
request.LoadBalancerName = aws.String(loadBalancerName)
for loadBalancerPort, listener := range changes.Listeners {
loadBalancerPortInt, err := strconv.ParseInt(loadBalancerPort, 10, 64)
if err != nil {
return fmt.Errorf("error parsing load balancer listener port: %q", loadBalancerPort)
}
awsListener := listener.mapToAWS(loadBalancerPortInt)
request.Listeners = append(request.Listeners, awsListener)
}
glog.V(2).Infof("Creating LoadBalancer listeners")
_, err := t.Cloud.ELB().CreateLoadBalancerListeners(request)
if err != nil {
return fmt.Errorf("error creating LoadBalancerListeners: %v", err)
}
}
}
if err := t.AddELBTags(loadBalancerName, t.Cloud.BuildTags(e.Name)); err != nil {
return err
}
if changes.HealthCheck != nil && e.HealthCheck != nil {
request := &elb.ConfigureHealthCheckInput{}
request.LoadBalancerName = aws.String(loadBalancerName)
request.HealthCheck = &elb.HealthCheck{
Target: e.HealthCheck.Target,
HealthyThreshold: e.HealthCheck.HealthyThreshold,
UnhealthyThreshold: e.HealthCheck.UnhealthyThreshold,
Interval: e.HealthCheck.Interval,
Timeout: e.HealthCheck.Timeout,
}
glog.V(2).Infof("Configuring health checks on ELB %q", loadBalancerName)
_, err := t.Cloud.ELB().ConfigureHealthCheck(request)
if err != nil {
return fmt.Errorf("error configuring health checks on ELB: %v", err)
}
}
if err := e.modifyLoadBalancerAttributes(t, a, e, changes); err != nil {
glog.Infof("error modifying ELB attributes: %v", err)
return err
}
return nil
}
type terraformLoadBalancer struct {
LoadBalancerName *string `json:"name"`
Listener []*terraformLoadBalancerListener `json:"listener"`
SecurityGroups []*terraform.Literal `json:"security_groups"`
Subnets []*terraform.Literal `json:"subnets"`
Internal *bool `json:"internal,omitempty"`
HealthCheck *terraformLoadBalancerHealthCheck `json:"health_check,omitempty"`
AccessLog *terraformLoadBalancerAccessLog `json:"access_logs,omitempty"`
ConnectionDraining *bool `json:"connection_draining,omitempty"`
ConnectionDrainingTimeout *int64 `json:"connection_draining_timeout,omitempty"`
CrossZoneLoadBalancing *bool `json:"cross_zone_load_balancing,omitempty"`
IdleTimeout *int64 `json:"idle_timeout,omitempty"`
Tags map[string]string `json:"tags,omitempty"`
}
type terraformLoadBalancerListener struct {
InstancePort int `json:"instance_port"`
InstanceProtocol string `json:"instance_protocol"`
LBPort int64 `json:"lb_port"`
LBProtocol string `json:"lb_protocol"`
}
type terraformLoadBalancerHealthCheck struct {
Target *string `json:"target"`
HealthyThreshold *int64 `json:"healthy_threshold"`
UnhealthyThreshold *int64 `json:"unhealthy_threshold"`
Interval *int64 `json:"interval"`
Timeout *int64 `json:"timeout"`
}
func (_ *LoadBalancer) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *LoadBalancer) error {
cloud := t.Cloud.(awsup.AWSCloud)
if e.LoadBalancerName == nil {
return fi.RequiredField("LoadBalancerName")
}
tf := &terraformLoadBalancer{
LoadBalancerName: e.LoadBalancerName,
}
if fi.StringValue(e.Scheme) == "internal" {
tf.Internal = fi.Bool(true)
}
for _, subnet := range e.Subnets {
tf.Subnets = append(tf.Subnets, subnet.TerraformLink())
}
terraform.SortLiterals(tf.Subnets)
for _, sg := range e.SecurityGroups {
tf.SecurityGroups = append(tf.SecurityGroups, sg.TerraformLink())
}
terraform.SortLiterals(tf.SecurityGroups)
for loadBalancerPort, listener := range e.Listeners {
loadBalancerPortInt, err := strconv.ParseInt(loadBalancerPort, 10, 64)
if err != nil {
return fmt.Errorf("error parsing load balancer listener port: %q", loadBalancerPort)
}
tf.Listener = append(tf.Listener, &terraformLoadBalancerListener{
InstanceProtocol: "TCP",
InstancePort: listener.InstancePort,
LBPort: loadBalancerPortInt,
LBProtocol: "TCP",
})
}
if e.HealthCheck != nil {
tf.HealthCheck = &terraformLoadBalancerHealthCheck{
Target: e.HealthCheck.Target,
HealthyThreshold: e.HealthCheck.HealthyThreshold,
UnhealthyThreshold: e.HealthCheck.UnhealthyThreshold,
Interval: e.HealthCheck.Interval,
Timeout: e.HealthCheck.Timeout,
}
}
if e.AccessLog != nil {
tf.AccessLog = &terraformLoadBalancerAccessLog{
EmitInterval: e.AccessLog.EmitInterval,
Enabled: e.AccessLog.Enabled,
S3BucketName: e.AccessLog.S3BucketName,
S3BucketPrefix: e.AccessLog.S3BucketPrefix,
}
}
if e.ConnectionDraining != nil {
tf.ConnectionDraining = e.ConnectionDraining.Enabled
tf.ConnectionDrainingTimeout = e.ConnectionDraining.Timeout
}
if e.ConnectionSettings != nil {
tf.IdleTimeout = e.ConnectionSettings.IdleTimeout
}
if e.CrossZoneLoadBalancing != nil {
tf.CrossZoneLoadBalancing = e.CrossZoneLoadBalancing.Enabled
}
tf.Tags = cloud.BuildTags(e.Name)
return t.RenderResource("aws_elb", *e.Name, tf)
}
func (e *LoadBalancer) TerraformLink(params ...string) *terraform.Literal {
prop := "id"
if len(params) > 0 {
prop = params[0]
}
return terraform.LiteralProperty("aws_elb", *e.Name, prop)
}
type cloudformationLoadBalancer struct {
LoadBalancerName *string `json:"LoadBalancerName,omitempty"`
Listener []*cloudformationLoadBalancerListener `json:"Listeners,omitempty"`
SecurityGroups []*cloudformation.Literal `json:"SecurityGroups,omitempty"`
Subnets []*cloudformation.Literal `json:"Subnets,omitempty"`
Scheme *string `json:"Scheme,omitempty"`
HealthCheck *cloudformationLoadBalancerHealthCheck `json:"HealthCheck,omitempty"`
AccessLog *cloudformationLoadBalancerAccessLog `json:"AccessLoggingPolicy,omitempty"`
ConnectionDrainingPolicy *cloudformationConnectionDrainingPolicy `json:"ConnectionDrainingPolicy,omitempty"`
ConnectionSettings *cloudformationConnectionSettings `json:"ConnectionSettings,omitempty"`
CrossZoneLoadBalancing *bool `json:"CrossZone,omitempty"`
Tags []cloudformationTag `json:"Tags,omitempty"`
}
type cloudformationLoadBalancerListener struct {
InstancePort int `json:"InstancePort"`
InstanceProtocol string `json:"InstanceProtocol"`
LoadBalancerPort int64 `json:"LoadBalancerPort"`
LoadBalancerProtocol string `json:"Protocol"`
}
type cloudformationLoadBalancerHealthCheck struct {
Target *string `json:"Target"`
HealthyThreshold *int64 `json:"HealthyThreshold"`
UnhealthyThreshold *int64 `json:"UnhealthyThreshold"`
Interval *int64 `json:"Interval"`
Timeout *int64 `json:"Timeout"`
}
type cloudformationConnectionDrainingPolicy struct {
Enabled *bool `json:"Enabled,omitempty"`
Timeout *int64 `json:"Timeout,omitempty"`
}
type cloudformationConnectionSettings struct {
IdleTimeout *int64 `json:"IdleTimeout,omitempty"`
}
func (_ *LoadBalancer) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *LoadBalancer) error {
// TODO: From http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-elb.html:
// If this resource has a public IP address and is also in a VPC that is defined in the same template,
// you must use the DependsOn attribute to declare a dependency on the VPC-gateway attachment.
cloud := t.Cloud.(awsup.AWSCloud)
if e.LoadBalancerName == nil {
return fi.RequiredField("LoadBalancerName")
}
tf := &cloudformationLoadBalancer{
LoadBalancerName: e.LoadBalancerName,
Scheme: e.Scheme,
}
for _, subnet := range e.Subnets {
tf.Subnets = append(tf.Subnets, subnet.CloudformationLink())
}
for _, sg := range e.SecurityGroups {
tf.SecurityGroups = append(tf.SecurityGroups, sg.CloudformationLink())
}
for loadBalancerPort, listener := range e.Listeners {
loadBalancerPortInt, err := strconv.ParseInt(loadBalancerPort, 10, 64)
if err != nil {
return fmt.Errorf("error parsing load balancer listener port: %q", loadBalancerPort)
}
tf.Listener = append(tf.Listener, &cloudformationLoadBalancerListener{
InstanceProtocol: "TCP",
InstancePort: listener.InstancePort,
LoadBalancerPort: loadBalancerPortInt,
LoadBalancerProtocol: "TCP",
})
}
if e.HealthCheck != nil {
tf.HealthCheck = &cloudformationLoadBalancerHealthCheck{
Target: e.HealthCheck.Target,
HealthyThreshold: e.HealthCheck.HealthyThreshold,
UnhealthyThreshold: e.HealthCheck.UnhealthyThreshold,
Interval: e.HealthCheck.Interval,
Timeout: e.HealthCheck.Timeout,
}
}
if e.AccessLog != nil {
tf.AccessLog = &cloudformationLoadBalancerAccessLog{
EmitInterval: e.AccessLog.EmitInterval,
Enabled: e.AccessLog.Enabled,
S3BucketName: e.AccessLog.S3BucketName,
S3BucketPrefix: e.AccessLog.S3BucketPrefix,
}
}
if e.ConnectionDraining != nil {
tf.ConnectionDrainingPolicy = &cloudformationConnectionDrainingPolicy{
Enabled: e.ConnectionDraining.Enabled,
Timeout: e.ConnectionDraining.Timeout,
}
}
if e.ConnectionSettings != nil {
tf.ConnectionSettings = &cloudformationConnectionSettings{
IdleTimeout: e.ConnectionSettings.IdleTimeout,
}
}
if e.CrossZoneLoadBalancing != nil {
tf.CrossZoneLoadBalancing = e.CrossZoneLoadBalancing.Enabled
}
tf.Tags = buildCloudformationTags(cloud.BuildTags(e.Name))
return t.RenderResource("AWS::ElasticLoadBalancing::LoadBalancer", *e.Name, tf)
}
func (e *LoadBalancer) CloudformationLink() *cloudformation.Literal {
return cloudformation.Ref("AWS::ElasticLoadBalancing::LoadBalancer", *e.Name)
}
func (e *LoadBalancer) CloudformationAttrCanonicalHostedZoneNameID() *cloudformation.Literal {
return cloudformation.GetAtt("AWS::ElasticLoadBalancing::LoadBalancer", *e.Name, "CanonicalHostedZoneNameID")
}
func (e *LoadBalancer) CloudformationAttrDNSName() *cloudformation.Literal {
return cloudformation.GetAtt("AWS::ElasticLoadBalancing::LoadBalancer", *e.Name, "DNSName")
}