ekristen/aws-nuke

View on GitHub
resources/elbv2-listenerrule.go

Summary

Maintainability
A
3 hrs
Test Coverage
package resources

import (
    "context"
    "fmt"

    "github.com/sirupsen/logrus"

    "github.com/aws/aws-sdk-go/service/elbv2"

    "github.com/ekristen/libnuke/pkg/registry"
    "github.com/ekristen/libnuke/pkg/resource"
    "github.com/ekristen/libnuke/pkg/slices"
    "github.com/ekristen/libnuke/pkg/types"

    "github.com/ekristen/aws-nuke/v3/pkg/nuke"
)

var elbv2ListenerRulePageSize int64 = 400 // AWS has a limit of 100 rules per listener

const ELBv2ListenerRuleResource = "ELBv2ListenerRule"

func init() {
    registry.Register(&registry.Registration{
        Name:     ELBv2ListenerRuleResource,
        Scope:    nuke.Account,
        Resource: &ELBv2ListenerRule{},
        Lister:   &ELBv2ListenerRuleLister{},
    })
}

type ELBv2ListenerRuleLister struct{}

func (l *ELBv2ListenerRuleLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) {
    opts := o.(*nuke.ListerOpts)

    svc := elbv2.New(opts.Session)

    // We need to retrieve ELBs then Listeners then Rules
    lbs := make([]*elbv2.LoadBalancer, 0)
    err := svc.DescribeLoadBalancersPages(
        nil,
        func(page *elbv2.DescribeLoadBalancersOutput, lastPage bool) bool {
            lbs = append(lbs, page.LoadBalancers...)

            return !lastPage
        },
    )
    if err != nil {
        return nil, err
    }

    // Required for batched tag retrieval later
    ruleArns := make([]*string, 0)
    ruleArnToResource := make(map[string]*ELBv2ListenerRule)

    resources := make([]resource.Resource, 0)
    for _, lb := range lbs {
        err := svc.DescribeListenersPages(
            &elbv2.DescribeListenersInput{
                LoadBalancerArn: lb.LoadBalancerArn,
            },
            func(page *elbv2.DescribeListenersOutput, lastPage bool) bool {
                for _, listener := range page.Listeners {
                    rules, err := svc.DescribeRules(&elbv2.DescribeRulesInput{
                        ListenerArn: listener.ListenerArn,
                        PageSize:    &elbv2ListenerRulePageSize,
                    })
                    if err == nil {
                        for _, rule := range rules.Rules {
                            // Skip default rules as they cannot be deleted
                            if rule.IsDefault != nil && *rule.IsDefault {
                                continue
                            }

                            listenerRule := &ELBv2ListenerRule{
                                svc:         svc,
                                ruleArn:     rule.RuleArn,
                                lbName:      lb.LoadBalancerName,
                                listenerArn: listener.ListenerArn,
                            }

                            ruleArns = append(ruleArns, rule.RuleArn)
                            resources = append(resources, listenerRule)
                            ruleArnToResource[*rule.RuleArn] = listenerRule
                        }
                    } else {
                        logrus.
                            WithError(err).
                            WithField("listenerArn", listener.ListenerArn).
                            Error("Failed to list listener rules for listener")
                    }
                }

                return !lastPage
            },
        )
        if err != nil {
            logrus.
                WithError(err).
                WithField("loadBalancerArn", lb.LoadBalancerArn).
                Error("Failed to list listeners for load balancer")
        }
    }

    // Tags for Rules need to be fetched separately. We can only specify up to 20 in a single call.
    // See: https://github.com/aws/aws-sdk-go/blob/0e8c61841163762f870f6976775800ded4a789b0/service/elbv2/api.go#L5398
    for _, ruleChunk := range slices.Chunk(ruleArns, 20) {
        tagResp, err := svc.DescribeTags(&elbv2.DescribeTagsInput{
            ResourceArns: ruleChunk,
        })
        if err != nil {
            return nil, err
        }
        for _, elbv2TagInfo := range tagResp.TagDescriptions {
            rule := ruleArnToResource[*elbv2TagInfo.ResourceArn]
            rule.tags = elbv2TagInfo.Tags
        }
    }

    return resources, nil
}

type ELBv2ListenerRule struct {
    svc         *elbv2.ELBV2
    ruleArn     *string
    lbName      *string
    listenerArn *string
    tags        []*elbv2.Tag
}

func (e *ELBv2ListenerRule) Remove(_ context.Context) error {
    _, err := e.svc.DeleteRule(&elbv2.DeleteRuleInput{
        RuleArn: e.ruleArn,
    })
    if err != nil {
        return err
    }

    return nil
}

func (e *ELBv2ListenerRule) Properties() types.Properties {
    properties := types.NewProperties()

    properties.Set("ARN", e.ruleArn)
    properties.Set("ListenerARN", e.listenerArn)
    properties.Set("LoadBalancerName", e.lbName)

    for _, tagValue := range e.tags {
        properties.SetTag(tagValue.Key, tagValue.Value)
    }

    return properties
}

func (e *ELBv2ListenerRule) String() string {
    return fmt.Sprintf("%s -> %s", *e.lbName, *e.ruleArn)
}