mirror of https://github.com/kubernetes/kops.git
876 lines
26 KiB
Go
876 lines
26 KiB
Go
/*
|
|
Copyright 2019 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/elbv2"
|
|
"github.com/aws/aws-sdk-go/service/route53"
|
|
"k8s.io/klog/v2"
|
|
"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"
|
|
)
|
|
|
|
// NetworkLoadBalancer manages an NLB. We find the existing NLB using the Name tag.
|
|
var _ DNSTarget = &NetworkLoadBalancer{}
|
|
|
|
//go:generate fitask -type=NetworkLoadBalancer
|
|
type NetworkLoadBalancer struct {
|
|
// We use the Name tag to find the existing NLB, 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 NLB, possibly different from our name
|
|
// (NLB is restricted as to names, so we have limited choices!)
|
|
// We use the Name tag to find the existing NLB.
|
|
LoadBalancerName *string
|
|
|
|
DNSName *string
|
|
HostedZoneId *string
|
|
|
|
Subnets []*Subnet
|
|
|
|
Listeners []*NetworkLoadBalancerListener
|
|
|
|
Scheme *string
|
|
|
|
CrossZoneLoadBalancing *bool
|
|
|
|
Tags map[string]string
|
|
ForAPIServer bool
|
|
|
|
Type *string
|
|
|
|
VPC *VPC
|
|
TargetGroups []*TargetGroup
|
|
}
|
|
|
|
var _ fi.CompareWithID = &NetworkLoadBalancer{}
|
|
|
|
func (e *NetworkLoadBalancer) CompareWithID() *string {
|
|
return e.Name
|
|
}
|
|
|
|
type NetworkLoadBalancerListener struct {
|
|
Port int
|
|
TargetGroupName string
|
|
SSLCertificateID string
|
|
SSLPolicy string
|
|
}
|
|
|
|
func (e *NetworkLoadBalancerListener) mapToAWS(targetGroups []*TargetGroup, loadBalancerArn string) (*elbv2.CreateListenerInput, error) {
|
|
var tgARN string
|
|
for _, tg := range targetGroups {
|
|
if fi.StringValue(tg.Name) == e.TargetGroupName {
|
|
tgARN = fi.StringValue(tg.ARN)
|
|
}
|
|
}
|
|
if tgARN == "" {
|
|
return nil, fmt.Errorf("target group not found for NLB listener %+v", e)
|
|
}
|
|
|
|
l := &elbv2.CreateListenerInput{
|
|
DefaultActions: []*elbv2.Action{
|
|
{
|
|
TargetGroupArn: aws.String(tgARN),
|
|
Type: aws.String(elbv2.ActionTypeEnumForward),
|
|
},
|
|
},
|
|
LoadBalancerArn: aws.String(loadBalancerArn),
|
|
Port: aws.Int64(int64(e.Port)),
|
|
}
|
|
|
|
if e.SSLCertificateID != "" {
|
|
l.Certificates = []*elbv2.Certificate{}
|
|
l.Certificates = append(l.Certificates, &elbv2.Certificate{
|
|
CertificateArn: aws.String(e.SSLCertificateID),
|
|
})
|
|
l.Protocol = aws.String(elbv2.ProtocolEnumTls)
|
|
if e.SSLPolicy != "" {
|
|
l.SslPolicy = aws.String(e.SSLPolicy)
|
|
}
|
|
} else {
|
|
l.Protocol = aws.String(elbv2.ProtocolEnumTcp)
|
|
}
|
|
|
|
return l, nil
|
|
}
|
|
|
|
var _ fi.HasDependencies = &NetworkLoadBalancerListener{}
|
|
|
|
func (e *NetworkLoadBalancerListener) GetDependencies(tasks map[string]fi.Task) []fi.Task {
|
|
return nil
|
|
}
|
|
|
|
// OrderListenersByPort implements sort.Interface for []OrderListenersByPort, based on port number
|
|
type OrderListenersByPort []*NetworkLoadBalancerListener
|
|
|
|
func (a OrderListenersByPort) Len() int { return len(a) }
|
|
func (a OrderListenersByPort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
func (a OrderListenersByPort) Less(i, j int) bool {
|
|
return a[i].Port < a[j].Port
|
|
}
|
|
|
|
//The load balancer name 'api.renamenlbcluster.k8s.local' can only contain characters that are alphanumeric characters and hyphens(-)\n\tstatus code: 400,
|
|
func findNetworkLoadBalancerByLoadBalancerName(cloud awsup.AWSCloud, loadBalancerName string) (*elbv2.LoadBalancer, error) {
|
|
request := &elbv2.DescribeLoadBalancersInput{
|
|
Names: []*string{&loadBalancerName},
|
|
}
|
|
found, err := describeNetworkLoadBalancers(cloud, request, func(lb *elbv2.LoadBalancer) bool {
|
|
// TODO: Filter by cluster?
|
|
|
|
if aws.StringValue(lb.LoadBalancerName) == loadBalancerName {
|
|
return true
|
|
}
|
|
|
|
klog.Warningf("Got NLB with unexpected name: %q", aws.StringValue(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 NLBs: %v", err)
|
|
}
|
|
|
|
if len(found) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
if len(found) != 1 {
|
|
return nil, fmt.Errorf("Found multiple NLBs with name %q", loadBalancerName)
|
|
}
|
|
|
|
return found[0], nil
|
|
}
|
|
|
|
func findNetworkLoadBalancerByAlias(cloud awsup.AWSCloud, alias *route53.AliasTarget) (*elbv2.LoadBalancer, error) {
|
|
// TODO: Any way to avoid listing all NLBs?
|
|
request := &elbv2.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 := describeNetworkLoadBalancers(cloud, request, func(lb *elbv2.LoadBalancer) bool {
|
|
// TODO: Filter by cluster?
|
|
|
|
if matchHostedZoneId != aws.StringValue(lb.CanonicalHostedZoneId) {
|
|
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 NLBs: %v", err)
|
|
}
|
|
|
|
if len(found) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
if len(found) != 1 {
|
|
return nil, fmt.Errorf("Found multiple NLBs with DNSName %q", dnsName)
|
|
}
|
|
|
|
return found[0], nil
|
|
}
|
|
|
|
func FindNetworkLoadBalancerByNameTag(cloud awsup.AWSCloud, findNameTag string) (*elbv2.LoadBalancer, error) {
|
|
// TODO: Any way around this?
|
|
klog.V(2).Infof("Listing all NLBs for findNetworkLoadBalancerByNameTag")
|
|
|
|
request := &elbv2.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 []*elbv2.LoadBalancer
|
|
|
|
var innerError error
|
|
err := cloud.ELBV2().DescribeLoadBalancersPages(request, func(p *elbv2.DescribeLoadBalancersOutput, lastPage bool) bool {
|
|
if len(p.LoadBalancers) == 0 {
|
|
return true
|
|
}
|
|
|
|
// TODO: Filter by cluster?
|
|
|
|
var arns []string
|
|
arnToELB := make(map[string]*elbv2.LoadBalancer)
|
|
for _, elb := range p.LoadBalancers {
|
|
arn := aws.StringValue(elb.LoadBalancerArn)
|
|
arnToELB[arn] = elb
|
|
arns = append(arns, arn)
|
|
}
|
|
|
|
tagMap, err := describeNetworkLoadBalancerTags(cloud, arns)
|
|
if err != nil {
|
|
innerError = err
|
|
return false
|
|
}
|
|
|
|
for loadBalancerArn, tags := range tagMap {
|
|
name, foundNameTag := awsup.FindELBV2Tag(tags, "Name")
|
|
if !foundNameTag || name != findNameTag {
|
|
continue
|
|
}
|
|
elb := arnToELB[loadBalancerArn]
|
|
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 NLBs with Name %q", findNameTag)
|
|
}
|
|
|
|
return found[0], nil
|
|
}
|
|
|
|
func describeNetworkLoadBalancers(cloud awsup.AWSCloud, request *elbv2.DescribeLoadBalancersInput, filter func(*elbv2.LoadBalancer) bool) ([]*elbv2.LoadBalancer, error) {
|
|
var found []*elbv2.LoadBalancer
|
|
err := cloud.ELBV2().DescribeLoadBalancersPages(request, func(p *elbv2.DescribeLoadBalancersOutput, lastPage bool) (shouldContinue bool) {
|
|
for _, lb := range p.LoadBalancers {
|
|
if filter(lb) {
|
|
found = append(found, lb)
|
|
}
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error listing NLBs: %v", err)
|
|
}
|
|
|
|
return found, nil
|
|
}
|
|
|
|
func describeNetworkLoadBalancerTags(cloud awsup.AWSCloud, loadBalancerArns []string) (map[string][]*elbv2.Tag, error) {
|
|
// TODO: Filter by cluster?
|
|
|
|
request := &elbv2.DescribeTagsInput{}
|
|
request.ResourceArns = aws.StringSlice(loadBalancerArns)
|
|
|
|
// TODO: Cache?
|
|
klog.V(2).Infof("Querying ELBV2 api for tags for %s", loadBalancerArns)
|
|
response, err := cloud.ELBV2().DescribeTags(request)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tagMap := make(map[string][]*elbv2.Tag)
|
|
for _, tagset := range response.TagDescriptions {
|
|
tagMap[aws.StringValue(tagset.ResourceArn)] = tagset.Tags
|
|
}
|
|
return tagMap, nil
|
|
}
|
|
|
|
func (e *NetworkLoadBalancer) getDNSName() *string {
|
|
return e.DNSName
|
|
}
|
|
|
|
func (e *NetworkLoadBalancer) getHostedZoneId() *string {
|
|
return e.HostedZoneId
|
|
}
|
|
|
|
func (e *NetworkLoadBalancer) Find(c *fi.Context) (*NetworkLoadBalancer, error) {
|
|
cloud := c.Cloud.(awsup.AWSCloud)
|
|
|
|
lb, err := FindNetworkLoadBalancerByNameTag(cloud, e.Tags["Name"])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if lb == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
loadBalancerArn := lb.LoadBalancerArn
|
|
|
|
actual := &NetworkLoadBalancer{}
|
|
actual.Name = e.Name
|
|
actual.LoadBalancerName = lb.LoadBalancerName
|
|
actual.DNSName = lb.DNSName
|
|
actual.HostedZoneId = lb.CanonicalHostedZoneId //CanonicalHostedZoneNameID
|
|
actual.Scheme = lb.Scheme
|
|
actual.VPC = &VPC{ID: lb.VpcId}
|
|
actual.Type = lb.Type
|
|
|
|
tagMap, err := describeNetworkLoadBalancerTags(cloud, []string{*loadBalancerArn})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
actual.Tags = make(map[string]string)
|
|
for _, tag := range tagMap[*loadBalancerArn] {
|
|
if strings.HasPrefix(aws.StringValue(tag.Key), "aws:cloudformation:") {
|
|
continue
|
|
}
|
|
actual.Tags[aws.StringValue(tag.Key)] = aws.StringValue(tag.Value)
|
|
}
|
|
|
|
for _, az := range lb.AvailabilityZones {
|
|
actual.Subnets = append(actual.Subnets, &Subnet{ID: az.SubnetId})
|
|
}
|
|
|
|
{
|
|
request := &elbv2.DescribeListenersInput{
|
|
LoadBalancerArn: loadBalancerArn,
|
|
}
|
|
response, err := cloud.ELBV2().DescribeListeners(request)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error querying for NLB listeners :%v", err)
|
|
}
|
|
|
|
actual.Listeners = []*NetworkLoadBalancerListener{}
|
|
actual.TargetGroups = []*TargetGroup{}
|
|
for _, l := range response.Listeners {
|
|
actualListener := &NetworkLoadBalancerListener{}
|
|
actualListener.Port = int(aws.Int64Value(l.Port))
|
|
if len(l.Certificates) != 0 {
|
|
actualListener.SSLCertificateID = aws.StringValue(l.Certificates[0].CertificateArn) // What if there is more then one certificate, can we just grab the default certificate? we don't set it as default, we only set the one.
|
|
if l.SslPolicy != nil {
|
|
actualListener.SSLPolicy = aws.StringValue(l.SslPolicy)
|
|
}
|
|
}
|
|
|
|
// This will need to be rearranged when we recognized multiple listeners and target groups per NLB
|
|
if len(l.DefaultActions) > 0 {
|
|
targetGroupARN := l.DefaultActions[0].TargetGroupArn
|
|
if targetGroupARN != nil {
|
|
targetGroupName, err := awsup.GetTargetGroupNameFromARN(fi.StringValue(targetGroupARN))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
actual.TargetGroups = append(actual.TargetGroups, &TargetGroup{ARN: targetGroupARN, Name: fi.String(targetGroupName)})
|
|
|
|
cloud := c.Cloud.(awsup.AWSCloud)
|
|
descResp, err := cloud.ELBV2().DescribeTargetGroups(&elbv2.DescribeTargetGroupsInput{
|
|
TargetGroupArns: []*string{targetGroupARN},
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error querying for NLB listener target groups: %v", err)
|
|
}
|
|
if len(descResp.TargetGroups) != 1 {
|
|
return nil, fmt.Errorf("unexpected DescribeTargetGroups response: %v", descResp)
|
|
}
|
|
|
|
actualListener.TargetGroupName = aws.StringValue(descResp.TargetGroups[0].TargetGroupName)
|
|
}
|
|
}
|
|
actual.Listeners = append(actual.Listeners, actualListener)
|
|
}
|
|
sort.Stable(OrderTargetGroupsByName(actual.TargetGroups))
|
|
|
|
}
|
|
|
|
{
|
|
lbAttributes, err := findNetworkLoadBalancerAttributes(cloud, aws.StringValue(loadBalancerArn))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
klog.V(4).Infof("NLB Load Balancer attributes: %+v", lbAttributes)
|
|
|
|
for _, attribute := range lbAttributes {
|
|
if attribute.Value == nil {
|
|
continue
|
|
}
|
|
switch key, value := attribute.Key, attribute.Value; *key {
|
|
case "load_balancing.cross_zone.enabled":
|
|
b, err := strconv.ParseBool(*value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
actual.CrossZoneLoadBalancing = fi.Bool(b)
|
|
default:
|
|
klog.V(2).Infof("unsupported key -- ignoring, %v.\n", key)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 NLB, because that is a destructive operation
|
|
if fi.StringValue(e.LoadBalancerName) != fi.StringValue(actual.LoadBalancerName) {
|
|
klog.V(2).Infof("Reusing existing load balancer with name: %q", aws.StringValue(actual.LoadBalancerName))
|
|
e.LoadBalancerName = actual.LoadBalancerName
|
|
}
|
|
|
|
// TODO: Make Normalize a standard method
|
|
actual.Normalize()
|
|
actual.ForAPIServer = e.ForAPIServer
|
|
actual.Lifecycle = e.Lifecycle
|
|
|
|
klog.V(4).Infof("Found NLB %+v", actual)
|
|
|
|
return actual, nil
|
|
}
|
|
|
|
var _ fi.HasAddress = &NetworkLoadBalancer{}
|
|
|
|
func (e *NetworkLoadBalancer) IsForAPIServer() bool {
|
|
return e.ForAPIServer
|
|
}
|
|
|
|
func (e *NetworkLoadBalancer) FindIPAddress(context *fi.Context) (*string, error) {
|
|
cloud := context.Cloud.(awsup.AWSCloud)
|
|
|
|
lb, err := FindNetworkLoadBalancerByNameTag(cloud, e.Tags["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 *NetworkLoadBalancer) Run(c *fi.Context) error {
|
|
// TODO: Make Normalize a standard method
|
|
e.Normalize()
|
|
|
|
return fi.DefaultDeltaRunMethod(e, c)
|
|
}
|
|
|
|
func (e *NetworkLoadBalancer) Normalize() {
|
|
// We need to sort our arrays consistently, so we don't get spurious changes
|
|
sort.Stable(OrderSubnetsById(e.Subnets))
|
|
sort.Stable(OrderListenersByPort(e.Listeners))
|
|
sort.Stable(OrderTargetGroupsByName(e.TargetGroups))
|
|
}
|
|
|
|
func (s *NetworkLoadBalancer) CheckChanges(a, e, changes *NetworkLoadBalancer) error {
|
|
if a == nil {
|
|
if fi.StringValue(e.Name) == "" {
|
|
return fi.RequiredField("Name")
|
|
}
|
|
if len(e.Subnets) == 0 {
|
|
return fi.RequiredField("Subnets")
|
|
}
|
|
|
|
if e.CrossZoneLoadBalancing != nil {
|
|
if e.CrossZoneLoadBalancing == nil {
|
|
return fi.RequiredField("CrossZoneLoadBalancing")
|
|
}
|
|
}
|
|
} else {
|
|
if len(changes.Subnets) > 0 {
|
|
expectedSubnets := make(map[string]*string)
|
|
for _, s := range e.Subnets {
|
|
expectedSubnets[*s.ID] = nil
|
|
}
|
|
|
|
for _, s := range a.Subnets {
|
|
if _, ok := expectedSubnets[*s.ID]; !ok {
|
|
return fmt.Errorf("network load balancers do not support detaching subnets")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (_ *NetworkLoadBalancer) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *NetworkLoadBalancer) error {
|
|
var loadBalancerName string
|
|
var loadBalancerArn string
|
|
|
|
if len(e.Listeners) != len(e.TargetGroups) {
|
|
return fmt.Errorf("nlb listeners and target groups do not match: %v listeners vs %v target groups", len(e.Listeners), len(e.TargetGroups))
|
|
}
|
|
|
|
if a == nil {
|
|
if e.LoadBalancerName == nil {
|
|
return fi.RequiredField("LoadBalancerName")
|
|
}
|
|
for _, tg := range e.TargetGroups {
|
|
if tg.ARN == nil {
|
|
return fmt.Errorf("missing required target group ARN for NLB creation %v", tg)
|
|
}
|
|
}
|
|
|
|
loadBalancerName = *e.LoadBalancerName
|
|
|
|
request := &elbv2.CreateLoadBalancerInput{}
|
|
request.Name = e.LoadBalancerName
|
|
request.Scheme = e.Scheme
|
|
request.Type = e.Type
|
|
|
|
for _, subnet := range e.Subnets {
|
|
request.Subnets = append(request.Subnets, subnet.ID)
|
|
}
|
|
|
|
{
|
|
klog.V(2).Infof("Creating NLB with Name:%q", loadBalancerName)
|
|
|
|
response, err := t.Cloud.ELBV2().CreateLoadBalancer(request)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating NLB: %v", err)
|
|
}
|
|
|
|
if len(response.LoadBalancers) != 1 {
|
|
return fmt.Errorf("Either too many or too few NLBs were created, wanted to find %q", loadBalancerName)
|
|
} else {
|
|
lb := response.LoadBalancers[0]
|
|
e.DNSName = lb.DNSName
|
|
e.HostedZoneId = lb.CanonicalHostedZoneId
|
|
e.VPC = &VPC{ID: lb.VpcId}
|
|
loadBalancerArn = fi.StringValue(lb.LoadBalancerArn)
|
|
}
|
|
}
|
|
|
|
{
|
|
for _, listener := range e.Listeners {
|
|
createListenerInput, err := listener.mapToAWS(e.TargetGroups, loadBalancerArn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
klog.V(2).Infof("Creating Listener for NLB with port %v", listener.Port)
|
|
_, err = t.Cloud.ELBV2().CreateListener(createListenerInput)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating listener for NLB: %v", err)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
loadBalancerName = fi.StringValue(a.LoadBalancerName)
|
|
|
|
lb, err := findNetworkLoadBalancerByLoadBalancerName(t.Cloud, loadBalancerName)
|
|
if err != nil {
|
|
return fmt.Errorf("error getting load balancer by name: %v", err)
|
|
}
|
|
|
|
loadBalancerArn = fi.StringValue(lb.LoadBalancerArn)
|
|
|
|
if changes.Subnets != nil {
|
|
actualSubnets := make(map[string]*string)
|
|
for _, s := range a.Subnets {
|
|
actualSubnets[*s.ID] = nil
|
|
}
|
|
|
|
var awsSubnets []*string
|
|
hasChanges := false
|
|
for _, s := range e.Subnets {
|
|
_, ok := actualSubnets[*s.ID]
|
|
if !ok {
|
|
hasChanges = true
|
|
}
|
|
awsSubnets = append(awsSubnets, s.ID)
|
|
}
|
|
|
|
if hasChanges {
|
|
request := &elbv2.SetSubnetsInput{}
|
|
request.SetLoadBalancerArn(loadBalancerArn)
|
|
request.SetSubnets(awsSubnets)
|
|
|
|
klog.V(2).Infof("Attaching Load Balancer to new subnets")
|
|
if _, err := t.Cloud.ELBV2().SetSubnets(request); err != nil {
|
|
return fmt.Errorf("error attaching load balancer to new subnets: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if changes.Listeners != nil {
|
|
|
|
if lb != nil {
|
|
|
|
request := &elbv2.DescribeListenersInput{
|
|
LoadBalancerArn: lb.LoadBalancerArn,
|
|
}
|
|
response, err := t.Cloud.ELBV2().DescribeListeners(request)
|
|
if err != nil {
|
|
return fmt.Errorf("error querying for NLB listeners :%v", err)
|
|
}
|
|
|
|
for _, l := range response.Listeners {
|
|
// delete the listener before recreating it
|
|
_, err := t.Cloud.ELBV2().DeleteListener(&elbv2.DeleteListenerInput{
|
|
ListenerArn: l.ListenerArn,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("error deleting load balancer listener with arn = : %v : %v", l.ListenerArn, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, listener := range changes.Listeners {
|
|
|
|
awsListener, err := listener.mapToAWS(e.TargetGroups, loadBalancerArn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
klog.V(2).Infof("Creating Listener for NLB with port %v", listener.Port)
|
|
_, err = t.Cloud.ELBV2().CreateListener(awsListener)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating NLB listener: %v", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := t.AddELBV2Tags(loadBalancerArn, e.Tags); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := t.RemoveELBV2Tags(loadBalancerArn, e.Tags); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := e.modifyLoadBalancerAttributes(t, a, e, changes, loadBalancerArn); err != nil {
|
|
klog.Infof("error modifying NLB attributes: %v", err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type terraformNetworkLoadBalancer struct {
|
|
Name string `json:"name" cty:"name"`
|
|
Internal bool `json:"internal" cty:"internal"`
|
|
Type string `json:"load_balancer_type" cty:"load_balancer_type"`
|
|
Subnets []*terraform.Literal `json:"subnets" cty:"subnets"`
|
|
CrossZoneLoadBalancing bool `json:"enable_cross_zone_load_balancing" cty:"enable_cross_zone_load_balancing"`
|
|
|
|
Tags map[string]string `json:"tags" cty:"tags"`
|
|
}
|
|
|
|
type terraformNetworkLoadBalancerListener struct {
|
|
LoadBalancer *terraform.Literal `json:"load_balancer_arn" cty:"load_balancer_arn"`
|
|
Port int64 `json:"port" cty:"port"`
|
|
Protocol string `json:"protocol" cty:"protocol"`
|
|
CertificateARN *string `json:"certificate_arn,omitempty" cty:"certificate_arn"`
|
|
SSLPolicy *string `json:"ssl_policy,omitempty" cty:"ssl_policy"`
|
|
DefaultAction []terraformNetworkLoadBalancerListenerAction `json:"default_action" cty:"default_action"`
|
|
}
|
|
|
|
type terraformNetworkLoadBalancerListenerAction struct {
|
|
Type string `json:"type" cty:"type"`
|
|
TargetGroupARN *terraform.Literal `json:"target_group_arn,omitempty" cty:"target_group_arn"`
|
|
}
|
|
|
|
func (_ *NetworkLoadBalancer) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *NetworkLoadBalancer) error {
|
|
nlbTF := &terraformNetworkLoadBalancer{
|
|
Name: *e.LoadBalancerName,
|
|
Internal: fi.StringValue(e.Scheme) == elbv2.LoadBalancerSchemeEnumInternal,
|
|
Type: elbv2.LoadBalancerTypeEnumNetwork,
|
|
Tags: e.Tags,
|
|
Subnets: make([]*terraform.Literal, 0),
|
|
CrossZoneLoadBalancing: fi.BoolValue(e.CrossZoneLoadBalancing),
|
|
}
|
|
|
|
for _, subnet := range e.Subnets {
|
|
nlbTF.Subnets = append(nlbTF.Subnets, subnet.TerraformLink())
|
|
}
|
|
|
|
err := t.RenderResource("aws_lb", *e.Name, nlbTF)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, listener := range e.Listeners {
|
|
var listenerTG *TargetGroup
|
|
for _, tg := range e.TargetGroups {
|
|
if aws.StringValue(tg.Name) == listener.TargetGroupName {
|
|
listenerTG = tg
|
|
break
|
|
}
|
|
}
|
|
if listenerTG == nil {
|
|
return fmt.Errorf("target group not found for NLB listener %+v", e)
|
|
}
|
|
listenerTF := &terraformNetworkLoadBalancerListener{
|
|
LoadBalancer: e.TerraformLink(),
|
|
Port: int64(listener.Port),
|
|
DefaultAction: []terraformNetworkLoadBalancerListenerAction{
|
|
{
|
|
Type: elbv2.ActionTypeEnumForward,
|
|
TargetGroupARN: listenerTG.TerraformLink(),
|
|
},
|
|
},
|
|
}
|
|
if listener.SSLCertificateID != "" {
|
|
listenerTF.CertificateARN = &listener.SSLCertificateID
|
|
listenerTF.Protocol = elbv2.ProtocolEnumTls
|
|
if listener.SSLPolicy != "" {
|
|
listenerTF.SSLPolicy = &listener.SSLPolicy
|
|
}
|
|
} else {
|
|
listenerTF.Protocol = elbv2.ProtocolEnumTcp
|
|
}
|
|
|
|
err = t.RenderResource("aws_lb_listener", fmt.Sprintf("%v-%v", *e.Name, listener.Port), listenerTF)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (e *NetworkLoadBalancer) TerraformLink(params ...string) *terraform.Literal {
|
|
prop := "id"
|
|
if len(params) > 0 {
|
|
prop = params[0]
|
|
}
|
|
return terraform.LiteralProperty("aws_lb", *e.Name, prop)
|
|
}
|
|
|
|
type cloudformationNetworkLoadBalancer struct {
|
|
Name string `json:"Name"`
|
|
Scheme string `json:"Scheme"`
|
|
Subnets []*cloudformation.Literal `json:"Subnets"`
|
|
Type string `json:"Type"`
|
|
Tags []cloudformationTag `json:"Tags"`
|
|
}
|
|
|
|
type cloudformationNetworkLoadBalancerListener struct {
|
|
Certificates []cloudformationNetworkLoadBalancerListenerCertificate `json:"Certificates,omitempty"`
|
|
DefaultActions []cloudformationNetworkLoadBalancerListenerAction `json:"DefaultActions"`
|
|
LoadBalancerARN *cloudformation.Literal `json:"LoadBalancerArn"`
|
|
Port int64 `json:"Port"`
|
|
Protocol string `json:"Protocol"`
|
|
SSLPolicy *string `json:"SslPolicy,omitempty"`
|
|
}
|
|
|
|
type cloudformationNetworkLoadBalancerListenerCertificate struct {
|
|
CertificateArn string `json:"CertificateArn"`
|
|
}
|
|
|
|
type cloudformationNetworkLoadBalancerListenerAction struct {
|
|
Type string `json:"Type"`
|
|
TargetGroupARN *cloudformation.Literal `json:"TargetGroupArn"`
|
|
}
|
|
|
|
func (_ *NetworkLoadBalancer) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *NetworkLoadBalancer) error {
|
|
nlbCF := &cloudformationNetworkLoadBalancer{
|
|
Name: *e.LoadBalancerName,
|
|
Subnets: make([]*cloudformation.Literal, 0),
|
|
Type: elbv2.LoadBalancerTypeEnumNetwork,
|
|
Tags: buildCloudformationTags(e.Tags),
|
|
}
|
|
for _, subnet := range e.Subnets {
|
|
nlbCF.Subnets = append(nlbCF.Subnets, subnet.CloudformationLink())
|
|
}
|
|
if e.Scheme != nil {
|
|
nlbCF.Scheme = *e.Scheme
|
|
} else {
|
|
nlbCF.Scheme = elbv2.LoadBalancerSchemeEnumInternetFacing
|
|
}
|
|
err := t.RenderResource("AWS::ElasticLoadBalancingV2::LoadBalancer", *e.Name, nlbCF)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, listener := range e.Listeners {
|
|
var listenerTG *TargetGroup
|
|
for _, tg := range e.TargetGroups {
|
|
if aws.StringValue(tg.Name) == listener.TargetGroupName {
|
|
listenerTG = tg
|
|
break
|
|
}
|
|
}
|
|
if listenerTG == nil {
|
|
return fmt.Errorf("target group not found for NLB listener %+v", e)
|
|
}
|
|
listenerCF := &cloudformationNetworkLoadBalancerListener{
|
|
LoadBalancerARN: e.CloudformationLink(),
|
|
Port: int64(listener.Port),
|
|
DefaultActions: []cloudformationNetworkLoadBalancerListenerAction{
|
|
{
|
|
Type: elbv2.ActionTypeEnumForward,
|
|
TargetGroupARN: listenerTG.CloudformationLink(),
|
|
},
|
|
},
|
|
}
|
|
if listener.SSLCertificateID != "" {
|
|
listenerCF.Certificates = []cloudformationNetworkLoadBalancerListenerCertificate{
|
|
{CertificateArn: listener.SSLCertificateID},
|
|
}
|
|
listenerCF.Protocol = elbv2.ProtocolEnumTls
|
|
if listener.SSLPolicy != "" {
|
|
listenerCF.SSLPolicy = &listener.SSLPolicy
|
|
}
|
|
} else {
|
|
listenerCF.Protocol = elbv2.ProtocolEnumTcp
|
|
}
|
|
|
|
err = t.RenderResource("AWS::ElasticLoadBalancingV2::Listener", fmt.Sprintf("%v-%v", *e.Name, listener.Port), listenerCF)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (e *NetworkLoadBalancer) CloudformationLink() *cloudformation.Literal {
|
|
return cloudformation.Ref("AWS::ElasticLoadBalancingV2::LoadBalancer", *e.Name)
|
|
}
|
|
|
|
func (e *NetworkLoadBalancer) CloudformationAttrCanonicalHostedZoneNameID() *cloudformation.Literal {
|
|
return cloudformation.GetAtt("AWS::ElasticLoadBalancingV2::LoadBalancer", *e.Name, "CanonicalHostedZoneID")
|
|
}
|
|
|
|
func (e *NetworkLoadBalancer) CloudformationAttrDNSName() *cloudformation.Literal {
|
|
return cloudformation.GetAtt("AWS::ElasticLoadBalancingV2::LoadBalancer", *e.Name, "DNSName")
|
|
}
|