mirror of https://github.com/kubernetes/kops.git
807 lines
24 KiB
Go
807 lines
24 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 (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
|
elb "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing"
|
|
elbv2 "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2"
|
|
elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types"
|
|
route53types "github.com/aws/aws-sdk-go-v2/service/route53/types"
|
|
"k8s.io/klog/v2"
|
|
"k8s.io/kops/pkg/truncate"
|
|
"k8s.io/kops/pkg/wellknownservices"
|
|
"k8s.io/kops/upup/pkg/fi"
|
|
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
|
|
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
|
|
"k8s.io/kops/upup/pkg/fi/cloudup/terraformWriter"
|
|
)
|
|
|
|
// NetworkLoadBalancer manages an NLB. We find the existing NLB using the Name tag.
|
|
var _ DNSTarget = &NetworkLoadBalancer{}
|
|
|
|
// +kops:fitask
|
|
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
|
|
|
|
// LoadBalancerBaseName is the base name to use when naming load balancers in NLB.
|
|
// The full, stable name will be in the Name tag.
|
|
// (NLB is restricted as to names, so we have limited choices!)
|
|
LoadBalancerBaseName *string
|
|
|
|
// CLBName is the name of a ClassicLoadBalancer to delete, if found.
|
|
// This enables migration from CLB -> NLB
|
|
CLBName *string
|
|
|
|
DNSName *string
|
|
HostedZoneId *string
|
|
|
|
SubnetMappings []*SubnetMapping
|
|
SecurityGroups []*SecurityGroup
|
|
|
|
Scheme elbv2types.LoadBalancerSchemeEnum
|
|
|
|
CrossZoneLoadBalancing *bool
|
|
|
|
IpAddressType elbv2types.IpAddressType
|
|
|
|
Tags map[string]string
|
|
|
|
Type elbv2types.LoadBalancerTypeEnum
|
|
|
|
VPC *VPC
|
|
AccessLog *NetworkLoadBalancerAccessLog
|
|
|
|
// WellKnownServices indicates which services are supported by this resource.
|
|
// This field is internal and is not rendered to the cloud.
|
|
WellKnownServices []wellknownservices.WellKnownService
|
|
|
|
// waitForLoadBalancerReady controls whether we wait for the load balancer to be ready before completing the "Render" operation.
|
|
waitForLoadBalancerReady bool
|
|
|
|
// After this is found/created, we store the ARN
|
|
loadBalancerArn string
|
|
|
|
// After this is found/created, we store the revision
|
|
revision string
|
|
|
|
// deletions is a list of previous versions of this object, that we should delete when asked to clean up.
|
|
deletions []fi.CloudupDeletion
|
|
}
|
|
|
|
func (e *NetworkLoadBalancer) SetWaitForLoadBalancerReady(v bool) {
|
|
e.waitForLoadBalancerReady = v
|
|
}
|
|
|
|
var _ fi.CompareWithID = &NetworkLoadBalancer{}
|
|
var _ fi.CloudupTaskNormalize = &NetworkLoadBalancer{}
|
|
var _ fi.CloudupProducesDeletions = &NetworkLoadBalancer{}
|
|
|
|
func (e *NetworkLoadBalancer) CompareWithID() *string {
|
|
return e.Name
|
|
}
|
|
|
|
func findNetworkLoadBalancerByAlias(cloud awsup.AWSCloud, alias *route53types.AliasTarget) (*elbv2types.LoadBalancer, error) {
|
|
ctx := context.TODO()
|
|
|
|
// TODO: Any way to avoid listing all NLBs?
|
|
request := &elbv2.DescribeLoadBalancersInput{}
|
|
|
|
dnsName := aws.ToString(alias.DNSName)
|
|
matchDnsName := strings.TrimSuffix(dnsName, ".")
|
|
if matchDnsName == "" {
|
|
return nil, fmt.Errorf("DNSName not set on AliasTarget")
|
|
}
|
|
|
|
matchHostedZoneId := aws.ToString(alias.HostedZoneId)
|
|
|
|
found, err := describeNetworkLoadBalancers(ctx, cloud, request, func(lb elbv2types.LoadBalancer) bool {
|
|
if matchHostedZoneId != aws.ToString(lb.CanonicalHostedZoneId) {
|
|
return false
|
|
}
|
|
|
|
lbDnsName := aws.ToString(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 describeNetworkLoadBalancers(ctx context.Context, cloud awsup.AWSCloud, request *elbv2.DescribeLoadBalancersInput, filter func(elbv2types.LoadBalancer) bool) ([]elbv2types.LoadBalancer, error) {
|
|
var found []elbv2types.LoadBalancer
|
|
paginator := elbv2.NewDescribeLoadBalancersPaginator(cloud.ELBV2(), request)
|
|
for paginator.HasMorePages() {
|
|
page, err := paginator.NextPage(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("listing NLBs: %v", err)
|
|
}
|
|
for _, lb := range page.LoadBalancers {
|
|
if filter(lb) {
|
|
found = append(found, lb)
|
|
}
|
|
}
|
|
}
|
|
|
|
return found, nil
|
|
}
|
|
|
|
func (e *NetworkLoadBalancer) getDNSName() *string {
|
|
return e.DNSName
|
|
}
|
|
|
|
func (e *NetworkLoadBalancer) getHostedZoneId() *string {
|
|
return e.HostedZoneId
|
|
}
|
|
|
|
func (e *NetworkLoadBalancer) Find(c *fi.CloudupContext) (*NetworkLoadBalancer, error) {
|
|
ctx := c.Context()
|
|
cloud := awsup.GetCloud(c)
|
|
|
|
allLoadBalancers, err := awsup.ListELBV2LoadBalancers(ctx, cloud)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
latest := awsup.FindLatestELBV2ByNameTag(allLoadBalancers, fi.ValueOf(e.Name))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Stash deletions for later
|
|
for _, lb := range allLoadBalancers {
|
|
if lb.NameTag() != fi.ValueOf(e.Name) {
|
|
continue
|
|
}
|
|
if latest != nil && latest.ARN() == lb.ARN() {
|
|
continue
|
|
}
|
|
|
|
e.deletions = append(e.deletions, &deleteNLB{
|
|
obj: lb,
|
|
})
|
|
}
|
|
|
|
if latest == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
lb := latest.LoadBalancer
|
|
|
|
loadBalancerArn := latest.ARN()
|
|
|
|
actual := &NetworkLoadBalancer{}
|
|
actual.Name = e.Name
|
|
actual.CLBName = e.CLBName
|
|
actual.DNSName = lb.DNSName
|
|
actual.HostedZoneId = lb.CanonicalHostedZoneId // CanonicalHostedZoneNameID
|
|
actual.Scheme = lb.Scheme
|
|
actual.VPC = &VPC{ID: lb.VpcId}
|
|
actual.Type = lb.Type
|
|
actual.IpAddressType = lb.IpAddressType
|
|
|
|
actual.Tags = make(map[string]string)
|
|
for _, tag := range latest.Tags {
|
|
k := aws.ToString(tag.Key)
|
|
if strings.HasPrefix(k, "aws:cloudformation:") {
|
|
continue
|
|
}
|
|
if k == awsup.KopsResourceRevisionTag {
|
|
continue
|
|
}
|
|
actual.Tags[k] = aws.ToString(tag.Value)
|
|
}
|
|
|
|
for _, az := range lb.AvailabilityZones {
|
|
sm := &SubnetMapping{
|
|
Subnet: &Subnet{ID: az.SubnetId},
|
|
}
|
|
for _, a := range az.LoadBalancerAddresses {
|
|
if a.PrivateIPv4Address != nil {
|
|
if sm.PrivateIPv4Address != nil {
|
|
return nil, fmt.Errorf("NLB has more then one PrivateIPv4Address, which is unexpected. This is a bug in kOps, please open a GitHub issue.")
|
|
}
|
|
sm.PrivateIPv4Address = a.PrivateIPv4Address
|
|
}
|
|
if a.AllocationId != nil {
|
|
if sm.AllocationID != nil {
|
|
return nil, fmt.Errorf("NLB has more then one AllocationID per subnet, which is unexpected. This is a bug in kOps, please open a GitHub issue.")
|
|
}
|
|
sm.AllocationID = a.AllocationId
|
|
}
|
|
}
|
|
actual.SubnetMappings = append(actual.SubnetMappings, sm)
|
|
}
|
|
|
|
for _, sg := range lb.SecurityGroups {
|
|
actual.SecurityGroups = append(actual.SecurityGroups, &SecurityGroup{ID: aws.String(sg)})
|
|
}
|
|
|
|
{
|
|
lbAttributes, err := findNetworkLoadBalancerAttributes(ctx, cloud, 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.PtrTo(b)
|
|
case "access_logs.s3.enabled":
|
|
b, err := strconv.ParseBool(*value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if actual.AccessLog == nil {
|
|
actual.AccessLog = &NetworkLoadBalancerAccessLog{}
|
|
}
|
|
actual.AccessLog.Enabled = fi.PtrTo(b)
|
|
case "access_logs.s3.bucket":
|
|
if actual.AccessLog == nil {
|
|
actual.AccessLog = &NetworkLoadBalancerAccessLog{}
|
|
}
|
|
if fi.ValueOf(value) != "" {
|
|
actual.AccessLog.S3BucketName = value
|
|
}
|
|
case "access_logs.s3.prefix":
|
|
if actual.AccessLog == nil {
|
|
actual.AccessLog = &NetworkLoadBalancerAccessLog{}
|
|
}
|
|
if fi.ValueOf(value) != "" {
|
|
actual.AccessLog.S3BucketPrefix = value
|
|
}
|
|
default:
|
|
klog.V(2).Infof("unsupported key -- ignoring, %v.\n", key)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Avoid spurious mismatches
|
|
if subnetMappingSlicesEqualIgnoreOrder(actual.SubnetMappings, e.SubnetMappings) {
|
|
actual.SubnetMappings = e.SubnetMappings
|
|
}
|
|
if e.DNSName == nil {
|
|
e.DNSName = actual.DNSName
|
|
}
|
|
if e.HostedZoneId == nil {
|
|
e.HostedZoneId = actual.HostedZoneId
|
|
}
|
|
|
|
// An existing internal NLB can't be updated to dualstack.
|
|
if actual.Scheme == elbv2types.LoadBalancerSchemeEnumInternal && actual.IpAddressType == elbv2types.IpAddressTypeIpv4 {
|
|
e.IpAddressType = actual.IpAddressType
|
|
}
|
|
|
|
_ = actual.Normalize(c)
|
|
actual.WellKnownServices = e.WellKnownServices
|
|
actual.Lifecycle = e.Lifecycle
|
|
actual.LoadBalancerBaseName = e.LoadBalancerBaseName
|
|
|
|
// Store state for other tasks
|
|
e.loadBalancerArn = aws.ToString(lb.LoadBalancerArn)
|
|
actual.loadBalancerArn = e.loadBalancerArn
|
|
e.revision, _ = latest.GetTag(awsup.KopsResourceRevisionTag)
|
|
actual.revision = e.revision
|
|
|
|
klog.V(4).Infof("Found NLB %+v", actual)
|
|
|
|
// AWS does not allow us to add security groups to an ELB that was initially created without them.
|
|
// This forces a new revision (currently, the only operation that forces a new revision)
|
|
if len(actual.SecurityGroups) == 0 && len(e.SecurityGroups) > 0 {
|
|
klog.Warningf("setting securityGroups on an existing NLB created without securityGroups; will force a new NLB")
|
|
t := time.Now()
|
|
revision := strconv.FormatInt(t.Unix(), 10)
|
|
actual = nil
|
|
e.revision = revision
|
|
}
|
|
|
|
return actual, nil
|
|
}
|
|
|
|
var _ fi.HasAddress = &NetworkLoadBalancer{}
|
|
|
|
// GetWellKnownServices implements fi.HasAddress::GetWellKnownServices.
|
|
// It indicates which services we support with this load balancer.
|
|
func (e *NetworkLoadBalancer) GetWellKnownServices() []wellknownservices.WellKnownService {
|
|
return e.WellKnownServices
|
|
}
|
|
|
|
func (e *NetworkLoadBalancer) FindAddresses(c *fi.CloudupContext) ([]string, error) {
|
|
ctx := c.Context()
|
|
|
|
var addresses []string
|
|
|
|
cloud := awsup.GetCloud(c)
|
|
cluster := c.T.Cluster
|
|
|
|
{
|
|
allLoadBalancers, err := awsup.ListELBV2LoadBalancers(ctx, cloud)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
lb := awsup.FindLatestELBV2ByNameTag(allLoadBalancers, fi.ValueOf(e.Name))
|
|
|
|
if lb != nil {
|
|
if fi.ValueOf(lb.LoadBalancer.DNSName) != "" {
|
|
addresses = append(addresses, fi.ValueOf(lb.LoadBalancer.DNSName))
|
|
}
|
|
|
|
if cluster.UsesNoneDNS() {
|
|
nis, err := cloud.FindELBV2NetworkInterfacesByName(fi.ValueOf(e.VPC.ID), aws.ToString(lb.LoadBalancer.LoadBalancerName))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to find network interfaces matching %q: %w", aws.ToString(lb.LoadBalancer.LoadBalancerName), err)
|
|
}
|
|
for _, ni := range nis {
|
|
if fi.ValueOf(ni.PrivateIpAddress) != "" {
|
|
addresses = append(addresses, fi.ValueOf(ni.PrivateIpAddress))
|
|
}
|
|
for _, v6 := range ni.Ipv6Addresses {
|
|
addresses = append(addresses, fi.ValueOf(v6.Ipv6Address))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sort.Strings(addresses)
|
|
|
|
return addresses, nil
|
|
}
|
|
|
|
func (e *NetworkLoadBalancer) Run(c *fi.CloudupContext) error {
|
|
return fi.CloudupDefaultDeltaRunMethod(e, c)
|
|
}
|
|
|
|
func (e *NetworkLoadBalancer) Normalize(c *fi.CloudupContext) error {
|
|
// We need to sort our arrays consistently, so we don't get spurious changes
|
|
sort.Stable(OrderSubnetMappingsByName(e.SubnetMappings))
|
|
|
|
e.IpAddressType = elbv2types.IpAddressTypeDualstack
|
|
for _, subnet := range e.SubnetMappings {
|
|
for _, clusterSubnet := range c.T.Cluster.Spec.Networking.Subnets {
|
|
if clusterSubnet.Name == fi.ValueOf(subnet.Subnet.ShortName) && clusterSubnet.IPv6CIDR == "" {
|
|
e.IpAddressType = elbv2types.IpAddressTypeIpv4
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (*NetworkLoadBalancer) CheckChanges(a, e, changes *NetworkLoadBalancer) error {
|
|
if a == nil {
|
|
if fi.ValueOf(e.Name) == "" {
|
|
return fi.RequiredField("Name")
|
|
}
|
|
if len(e.SubnetMappings) == 0 {
|
|
return fi.RequiredField("SubnetMappings")
|
|
}
|
|
|
|
if e.CrossZoneLoadBalancing != nil {
|
|
if e.CrossZoneLoadBalancing == nil {
|
|
return fi.RequiredField("CrossZoneLoadBalancing")
|
|
}
|
|
}
|
|
|
|
if e.AccessLog != nil {
|
|
if e.AccessLog.Enabled == nil {
|
|
return fi.RequiredField("Accesslog.Enabled")
|
|
}
|
|
if *e.AccessLog.Enabled {
|
|
if e.AccessLog.S3BucketName == nil {
|
|
return fi.RequiredField("Accesslog.S3Bucket")
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if len(changes.SubnetMappings) > 0 {
|
|
expectedSubnets := make(map[string]*string)
|
|
for _, s := range e.SubnetMappings {
|
|
if s.AllocationID != nil {
|
|
expectedSubnets[*s.Subnet.ID] = s.AllocationID
|
|
} else if s.PrivateIPv4Address != nil {
|
|
expectedSubnets[*s.Subnet.ID] = s.PrivateIPv4Address
|
|
} else {
|
|
expectedSubnets[*s.Subnet.ID] = nil
|
|
}
|
|
}
|
|
|
|
for _, s := range a.SubnetMappings {
|
|
eIP, ok := expectedSubnets[*s.Subnet.ID]
|
|
if !ok {
|
|
return fmt.Errorf("network load balancers do not support detaching subnets")
|
|
}
|
|
if fi.ValueOf(eIP) != fi.ValueOf(s.PrivateIPv4Address) || fi.ValueOf(eIP) != fi.ValueOf(s.AllocationID) {
|
|
return fmt.Errorf("network load balancers do not support modifying address settings")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (_ *NetworkLoadBalancer) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *NetworkLoadBalancer) error {
|
|
ctx := context.TODO()
|
|
|
|
loadBalancerArn := ""
|
|
|
|
revision := e.revision
|
|
|
|
// TODO: Use maps.Clone when we are >= go1.21 on supported branches
|
|
tags := make(map[string]string)
|
|
for k, v := range e.Tags {
|
|
tags[k] = v
|
|
}
|
|
|
|
// We removed revision for the diff/plan, but we want to set it
|
|
if revision != "" {
|
|
tags[awsup.KopsResourceRevisionTag] = revision
|
|
}
|
|
|
|
if a == nil {
|
|
loadBalancerName := fi.ValueOf(e.LoadBalancerBaseName)
|
|
if revision != "" {
|
|
s := fi.ValueOf(e.LoadBalancerBaseName) + "-" + revision
|
|
|
|
// We always compute the hash and add it, lest we trick users into assuming that we never do this
|
|
opt := truncate.TruncateStringOptions{
|
|
MaxLength: 32,
|
|
AlwaysAddHash: true,
|
|
HashLength: 6,
|
|
}
|
|
loadBalancerName = truncate.TruncateString(s, opt)
|
|
}
|
|
|
|
{
|
|
request := &elbv2.CreateLoadBalancerInput{}
|
|
request.Name = &loadBalancerName
|
|
request.Scheme = e.Scheme
|
|
request.Type = e.Type
|
|
request.IpAddressType = e.IpAddressType
|
|
request.Tags = awsup.ELBv2Tags(tags)
|
|
|
|
for _, subnetMapping := range e.SubnetMappings {
|
|
request.SubnetMappings = append(request.SubnetMappings, elbv2types.SubnetMapping{
|
|
SubnetId: subnetMapping.Subnet.ID,
|
|
AllocationId: subnetMapping.AllocationID,
|
|
PrivateIPv4Address: subnetMapping.PrivateIPv4Address,
|
|
})
|
|
}
|
|
|
|
for _, sg := range e.SecurityGroups {
|
|
request.SecurityGroups = append(request.SecurityGroups, aws.ToString(sg.ID))
|
|
}
|
|
|
|
klog.V(2).Infof("Creating NLB %q", loadBalancerName)
|
|
|
|
response, err := t.Cloud.ELBV2().CreateLoadBalancer(ctx, request)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating NLB %q: %w", loadBalancerName, err)
|
|
}
|
|
if len(response.LoadBalancers) != 1 {
|
|
return fmt.Errorf("error creating NLB %q: found %d", loadBalancerName, len(response.LoadBalancers))
|
|
}
|
|
|
|
lb := response.LoadBalancers[0]
|
|
e.DNSName = lb.DNSName
|
|
e.HostedZoneId = lb.CanonicalHostedZoneId
|
|
e.VPC = &VPC{ID: lb.VpcId}
|
|
loadBalancerArn = aws.ToString(lb.LoadBalancerArn)
|
|
e.loadBalancerArn = loadBalancerArn
|
|
e.revision = revision
|
|
}
|
|
|
|
if e.waitForLoadBalancerReady {
|
|
klog.Infof("Waiting for load balancer %q to be created...", loadBalancerName)
|
|
request := &elbv2.DescribeLoadBalancersInput{
|
|
Names: []string{loadBalancerName},
|
|
}
|
|
|
|
err := elbv2.NewLoadBalancerAvailableWaiter(t.Cloud.ELBV2()).Wait(ctx, request, 15*time.Minute)
|
|
if err != nil {
|
|
return fmt.Errorf("error waiting for NLB %q: %w", loadBalancerName, err)
|
|
}
|
|
}
|
|
|
|
} else {
|
|
loadBalancerArn = a.loadBalancerArn
|
|
|
|
if len(changes.IpAddressType) > 0 {
|
|
request := &elbv2.SetIpAddressTypeInput{
|
|
IpAddressType: e.IpAddressType,
|
|
LoadBalancerArn: &loadBalancerArn,
|
|
}
|
|
if _, err := t.Cloud.ELBV2().SetIpAddressType(ctx, request); err != nil {
|
|
return fmt.Errorf("error setting the IP addresses type: %v", err)
|
|
}
|
|
}
|
|
|
|
if changes.SubnetMappings != nil {
|
|
actualSubnets := make(map[string]*string)
|
|
for _, s := range a.SubnetMappings {
|
|
// actualSubnets[*s.Subnet.ID] = s
|
|
if s.AllocationID != nil {
|
|
actualSubnets[*s.Subnet.ID] = s.AllocationID
|
|
}
|
|
if s.PrivateIPv4Address != nil {
|
|
actualSubnets[*s.Subnet.ID] = s.PrivateIPv4Address
|
|
}
|
|
}
|
|
|
|
var awsSubnetMappings []elbv2types.SubnetMapping
|
|
hasChanges := false
|
|
for _, s := range e.SubnetMappings {
|
|
aIP, ok := actualSubnets[*s.Subnet.ID]
|
|
if !ok || (fi.ValueOf(s.PrivateIPv4Address) != fi.ValueOf(aIP) && fi.ValueOf(s.AllocationID) != fi.ValueOf(aIP)) {
|
|
hasChanges = true
|
|
}
|
|
awsSubnetMappings = append(awsSubnetMappings, elbv2types.SubnetMapping{
|
|
SubnetId: s.Subnet.ID,
|
|
AllocationId: s.AllocationID,
|
|
PrivateIPv4Address: s.PrivateIPv4Address,
|
|
})
|
|
}
|
|
|
|
if hasChanges {
|
|
request := &elbv2.SetSubnetsInput{}
|
|
request.LoadBalancerArn = aws.String(loadBalancerArn)
|
|
request.SubnetMappings = awsSubnetMappings
|
|
|
|
klog.V(2).Infof("Attaching Load Balancer to new subnets")
|
|
if _, err := t.Cloud.ELBV2().SetSubnets(ctx, request); err != nil {
|
|
return fmt.Errorf("error attaching load balancer to new subnets: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if changes.SecurityGroups != nil {
|
|
request := &elbv2.SetSecurityGroupsInput{
|
|
LoadBalancerArn: &loadBalancerArn,
|
|
}
|
|
for _, sg := range e.SecurityGroups {
|
|
request.SecurityGroups = append(request.SecurityGroups, aws.ToString(sg.ID))
|
|
}
|
|
|
|
klog.V(2).Infof("Updating Load Balancer Security Groups")
|
|
if _, err := t.Cloud.ELBV2().SetSecurityGroups(ctx, request); err != nil {
|
|
return fmt.Errorf("Error updating security groups on Load Balancer: %v", err)
|
|
}
|
|
}
|
|
|
|
if err := t.AddELBV2Tags(loadBalancerArn, tags); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := t.RemoveELBV2Tags(loadBalancerArn, 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 `cty:"name"`
|
|
Internal bool `cty:"internal"`
|
|
Type elbv2types.LoadBalancerTypeEnum `cty:"load_balancer_type"`
|
|
IPAddressType *elbv2types.IpAddressType `cty:"ip_address_type"`
|
|
SecurityGroups []*terraformWriter.Literal `cty:"security_groups"`
|
|
SubnetMappings []terraformNetworkLoadBalancerSubnetMapping `cty:"subnet_mapping"`
|
|
CrossZoneLoadBalancing bool `cty:"enable_cross_zone_load_balancing"`
|
|
AccessLog *terraformNetworkLoadBalancerAccessLog `cty:"access_logs"`
|
|
|
|
Tags map[string]string `cty:"tags"`
|
|
}
|
|
|
|
type terraformNetworkLoadBalancerSubnetMapping struct {
|
|
Subnet *terraformWriter.Literal `cty:"subnet_id"`
|
|
AllocationID *string `cty:"allocation_id"`
|
|
PrivateIPv4Address *string `cty:"private_ipv4_address"`
|
|
}
|
|
|
|
func (_ *NetworkLoadBalancer) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *NetworkLoadBalancer) error {
|
|
nlbTF := &terraformNetworkLoadBalancer{
|
|
Name: *e.LoadBalancerBaseName,
|
|
Internal: e.Scheme == elbv2types.LoadBalancerSchemeEnumInternal,
|
|
Type: elbv2types.LoadBalancerTypeEnumNetwork,
|
|
Tags: e.Tags,
|
|
CrossZoneLoadBalancing: fi.ValueOf(e.CrossZoneLoadBalancing),
|
|
}
|
|
if e.IpAddressType == elbv2types.IpAddressTypeDualstack {
|
|
nlbTF.IPAddressType = &e.IpAddressType
|
|
}
|
|
|
|
for _, subnetMapping := range e.SubnetMappings {
|
|
nlbTF.SubnetMappings = append(nlbTF.SubnetMappings, terraformNetworkLoadBalancerSubnetMapping{
|
|
Subnet: subnetMapping.Subnet.TerraformLink(),
|
|
AllocationID: subnetMapping.AllocationID,
|
|
PrivateIPv4Address: subnetMapping.PrivateIPv4Address,
|
|
})
|
|
}
|
|
|
|
for _, sg := range e.SecurityGroups {
|
|
nlbTF.SecurityGroups = append(nlbTF.SecurityGroups, sg.TerraformLink())
|
|
}
|
|
terraformWriter.SortLiterals(nlbTF.SecurityGroups)
|
|
|
|
if e.AccessLog != nil && fi.ValueOf(e.AccessLog.Enabled) {
|
|
nlbTF.AccessLog = &terraformNetworkLoadBalancerAccessLog{
|
|
Enabled: e.AccessLog.Enabled,
|
|
S3BucketName: e.AccessLog.S3BucketName,
|
|
S3BucketPrefix: e.AccessLog.S3BucketPrefix,
|
|
}
|
|
}
|
|
|
|
err := t.RenderResource("aws_lb", e.TerraformName(), nlbTF)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (e *NetworkLoadBalancer) TerraformName() string {
|
|
tfName := strings.ReplaceAll(fi.ValueOf(e.Name), ".", "-")
|
|
return tfName
|
|
}
|
|
|
|
func (e *NetworkLoadBalancer) TerraformLink(params ...string) *terraformWriter.Literal {
|
|
prop := "id"
|
|
if len(params) > 0 {
|
|
prop = params[0]
|
|
}
|
|
return terraformWriter.LiteralProperty("aws_lb", e.TerraformName(), prop)
|
|
}
|
|
|
|
// FindDeletions schedules deletion of the corresponding legacy classic load balancer when it no longer has targets.
|
|
func (e *NetworkLoadBalancer) FindDeletions(context *fi.CloudupContext) ([]fi.CloudupDeletion, error) {
|
|
var deletions []fi.CloudupDeletion
|
|
|
|
deletions = append(deletions, e.deletions...)
|
|
|
|
if e.CLBName != nil {
|
|
cloud := context.T.Cloud.(awsup.AWSCloud)
|
|
|
|
lb, err := cloud.FindELBByNameTag(fi.ValueOf(e.CLBName))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if lb != nil {
|
|
klog.V(4).Infof("Found CLB %v", aws.ToString(lb.LoadBalancerName))
|
|
deletions = append(deletions, &deleteClassicLoadBalancer{LoadBalancerName: e.CLBName})
|
|
}
|
|
}
|
|
|
|
return deletions, nil
|
|
}
|
|
|
|
type deleteClassicLoadBalancer struct {
|
|
// LoadBalancerName is the name in ELB, possibly different from our name
|
|
// (ELB is restricted as to names, so we have limited choices!)
|
|
LoadBalancerName *string
|
|
}
|
|
|
|
func (d deleteClassicLoadBalancer) TaskName() string {
|
|
return "ClassicLoadBalancer"
|
|
}
|
|
|
|
func (d deleteClassicLoadBalancer) Item() string {
|
|
return *d.LoadBalancerName
|
|
}
|
|
|
|
func (d deleteClassicLoadBalancer) DeferDeletion() bool {
|
|
return true
|
|
}
|
|
|
|
func (d deleteClassicLoadBalancer) Delete(t fi.CloudupTarget) error {
|
|
ctx := context.TODO()
|
|
awsTarget, ok := t.(*awsup.AWSAPITarget)
|
|
if !ok {
|
|
return fmt.Errorf("unexpected target type for deletion: %T", t)
|
|
}
|
|
|
|
_, err := awsTarget.Cloud.ELB().DeleteLoadBalancer(ctx, &elb.DeleteLoadBalancerInput{
|
|
LoadBalancerName: d.LoadBalancerName,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("deleting classic LoadBalancer: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// deleteNLB tracks a NLB that we're going to delete
|
|
// It implements fi.CloudupDeletion
|
|
type deleteNLB struct {
|
|
obj *awsup.LoadBalancerInfo
|
|
}
|
|
|
|
var _ fi.CloudupDeletion = &deleteNLB{}
|
|
|
|
func (d *deleteNLB) Delete(t fi.CloudupTarget) error {
|
|
ctx := context.TODO()
|
|
|
|
awsTarget, ok := t.(*awsup.AWSAPITarget)
|
|
if !ok {
|
|
return fmt.Errorf("unexpected target type for deletion: %T", t)
|
|
}
|
|
|
|
arn := d.obj.ARN()
|
|
klog.V(2).Infof("deleting load balancer %q", arn)
|
|
if _, err := awsTarget.Cloud.ELBV2().DeleteLoadBalancer(ctx, &elbv2.DeleteLoadBalancerInput{
|
|
LoadBalancerArn: &arn,
|
|
}); err != nil {
|
|
return fmt.Errorf("error deleting ELB LoadBalancer %q: %w", arn, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// String returns a string representation of the task
|
|
func (d *deleteNLB) String() string {
|
|
return d.TaskName() + "-" + d.Item()
|
|
}
|
|
|
|
// TaskName returns the task name
|
|
func (d *deleteNLB) TaskName() string {
|
|
return "network-load-balancer"
|
|
}
|
|
|
|
// Item returns the launch template name
|
|
func (d *deleteNLB) Item() string {
|
|
return d.obj.ARN()
|
|
}
|
|
|
|
func (d *deleteNLB) DeferDeletion() bool {
|
|
return true
|
|
}
|