kops/upup/pkg/fi/cloudup/awstasks/network_load_balancer.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
}