diff --git a/pkg/deploy/ec2/security_group_manager.go b/pkg/deploy/ec2/security_group_manager.go deleted file mode 100644 index 497e39014..000000000 --- a/pkg/deploy/ec2/security_group_manager.go +++ /dev/null @@ -1,180 +0,0 @@ -package ec2 - -import ( - "context" - awssdk "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - ec2sdk "github.com/aws/aws-sdk-go/service/ec2" - "github.com/go-logr/logr" - "github.com/pkg/errors" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/tracking" - ec2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/ec2" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/networking" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/runtime" - "time" -) - -const ( - defaultWaitSGDeletionPollInterval = 2 * time.Second - defaultWaitSGDeletionTimeout = 2 * time.Minute -) - -// SecurityGroupManager is responsible for create/update/delete SecurityGroup resources. -type SecurityGroupManager interface { - Create(ctx context.Context, resSG *ec2model.SecurityGroup) (ec2model.SecurityGroupStatus, error) - - Update(ctx context.Context, resSG *ec2model.SecurityGroup, sdkSG networking.SecurityGroupInfo) (ec2model.SecurityGroupStatus, error) - - Delete(ctx context.Context, sdkSG networking.SecurityGroupInfo) error -} - -// NewDefaultSecurityGroupManager constructs new defaultSecurityGroupManager. -func NewDefaultSecurityGroupManager(ec2Client services.EC2, trackingProvider tracking.Provider, taggingManager TaggingManager, - networkingSGReconciler networking.SecurityGroupReconciler, vpcID string, externalManagedTags []string, logger logr.Logger) *defaultSecurityGroupManager { - return &defaultSecurityGroupManager{ - ec2Client: ec2Client, - trackingProvider: trackingProvider, - taggingManager: taggingManager, - networkingSGReconciler: networkingSGReconciler, - vpcID: vpcID, - externalManagedTags: externalManagedTags, - logger: logger, - - waitSGDeletionPollInterval: defaultWaitSGDeletionPollInterval, - waitSGDeletionTimeout: defaultWaitSGDeletionTimeout, - } -} - -// default implementation for SecurityGroupManager. -type defaultSecurityGroupManager struct { - ec2Client services.EC2 - trackingProvider tracking.Provider - taggingManager TaggingManager - networkingSGReconciler networking.SecurityGroupReconciler - vpcID string - externalManagedTags []string - logger logr.Logger - - waitSGDeletionPollInterval time.Duration - waitSGDeletionTimeout time.Duration -} - -func (m *defaultSecurityGroupManager) Create(ctx context.Context, resSG *ec2model.SecurityGroup) (ec2model.SecurityGroupStatus, error) { - sgTags := m.trackingProvider.ResourceTags(resSG.Stack(), resSG, resSG.Spec.Tags) - sdkTags := convertTagsToSDKTags(sgTags) - permissionInfos, err := buildIPPermissionInfos(resSG.Spec.Ingress) - if err != nil { - return ec2model.SecurityGroupStatus{}, err - } - - req := &ec2sdk.CreateSecurityGroupInput{ - VpcId: awssdk.String(m.vpcID), - GroupName: awssdk.String(resSG.Spec.GroupName), - Description: awssdk.String(resSG.Spec.Description), - TagSpecifications: []*ec2sdk.TagSpecification{ - { - ResourceType: awssdk.String("security-group"), - Tags: sdkTags, - }, - }, - } - m.logger.Info("creating securityGroup", - "resourceID", resSG.ID()) - resp, err := m.ec2Client.CreateSecurityGroupWithContext(ctx, req) - if err != nil { - return ec2model.SecurityGroupStatus{}, err - } - sgID := awssdk.StringValue(resp.GroupId) - m.logger.Info("created securityGroup", - "resourceID", resSG.ID(), - "securityGroupID", sgID) - - if err := m.networkingSGReconciler.ReconcileIngress(ctx, sgID, permissionInfos); err != nil { - return ec2model.SecurityGroupStatus{}, err - } - - return ec2model.SecurityGroupStatus{ - GroupID: sgID, - }, nil -} - -func (m *defaultSecurityGroupManager) Update(ctx context.Context, resSG *ec2model.SecurityGroup, sdkSG networking.SecurityGroupInfo) (ec2model.SecurityGroupStatus, error) { - permissionInfos, err := buildIPPermissionInfos(resSG.Spec.Ingress) - if err != nil { - return ec2model.SecurityGroupStatus{}, err - } - if err := m.updateSDKSecurityGroupGroupWithTags(ctx, resSG, sdkSG); err != nil { - return ec2model.SecurityGroupStatus{}, err - } - if err := m.networkingSGReconciler.ReconcileIngress(ctx, sdkSG.SecurityGroupID, permissionInfos); err != nil { - return ec2model.SecurityGroupStatus{}, err - } - return ec2model.SecurityGroupStatus{ - GroupID: sdkSG.SecurityGroupID, - }, nil -} - -func (m *defaultSecurityGroupManager) Delete(ctx context.Context, sdkSG networking.SecurityGroupInfo) error { - req := &ec2sdk.DeleteSecurityGroupInput{ - GroupId: awssdk.String(sdkSG.SecurityGroupID), - } - - m.logger.Info("deleting securityGroup", - "securityGroupID", sdkSG.SecurityGroupID) - if err := runtime.RetryImmediateOnError(m.waitSGDeletionPollInterval, m.waitSGDeletionTimeout, isSecurityGroupDependencyViolationError, func() error { - _, err := m.ec2Client.DeleteSecurityGroupWithContext(ctx, req) - return err - }); err != nil { - return errors.Wrap(err, "failed to delete securityGroup") - } - m.logger.Info("deleted securityGroup", - "securityGroupID", sdkSG.SecurityGroupID) - - return nil -} - -func (m *defaultSecurityGroupManager) updateSDKSecurityGroupGroupWithTags(ctx context.Context, resSG *ec2model.SecurityGroup, sdkSG networking.SecurityGroupInfo) error { - desiredSGTags := m.trackingProvider.ResourceTags(resSG.Stack(), resSG, resSG.Spec.Tags) - return m.taggingManager.ReconcileTags(ctx, sdkSG.SecurityGroupID, desiredSGTags, - WithCurrentTags(sdkSG.Tags), - WithIgnoredTagKeys(m.trackingProvider.LegacyTagKeys()), - WithIgnoredTagKeys(m.externalManagedTags)) -} - -func buildIPPermissionInfos(permissions []ec2model.IPPermission) ([]networking.IPPermissionInfo, error) { - permissionInfos := make([]networking.IPPermissionInfo, 0, len(permissions)) - for _, permission := range permissions { - permissionInfo, err := buildIPPermissionInfo(permission) - if err != nil { - return nil, err - } - permissionInfos = append(permissionInfos, permissionInfo) - } - return permissionInfos, nil -} - -func buildIPPermissionInfo(permission ec2model.IPPermission) (networking.IPPermissionInfo, error) { - protocol := permission.IPProtocol - if len(permission.IPRanges) == 1 { - labels := networking.NewIPPermissionLabelsForRawDescription(permission.IPRanges[0].Description) - return networking.NewCIDRIPPermission(protocol, permission.FromPort, permission.ToPort, permission.IPRanges[0].CIDRIP, labels), nil - } - if len(permission.IPv6Range) == 1 { - labels := networking.NewIPPermissionLabelsForRawDescription(permission.IPv6Range[0].Description) - return networking.NewCIDRv6IPPermission(protocol, permission.FromPort, permission.ToPort, permission.IPv6Range[0].CIDRIPv6, labels), nil - } - if len(permission.UserIDGroupPairs) == 1 { - labels := networking.NewIPPermissionLabelsForRawDescription(permission.UserIDGroupPairs[0].Description) - return networking.NewGroupIDIPPermission(protocol, permission.FromPort, permission.ToPort, permission.UserIDGroupPairs[0].GroupID, labels), nil - } - return networking.IPPermissionInfo{}, errors.New("invalid ipPermission") -} - -func isSecurityGroupDependencyViolationError(err error) bool { - var awsErr awserr.Error - if errors.As(err, &awsErr) { - return awsErr.Code() == "DependencyViolation" - } - return false -} diff --git a/pkg/deploy/ec2/security_group_manager_test.go b/pkg/deploy/ec2/security_group_manager_test.go deleted file mode 100644 index ccf42eade..000000000 --- a/pkg/deploy/ec2/security_group_manager_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package ec2 - -import ( - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "testing" -) - -func Test_isSecurityGroupDependencyViolationError(t *testing.T) { - type args struct { - err error - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "is DependencyViolation error", - args: args{ - err: awserr.New("DependencyViolation", "some message", nil), - }, - want: true, - }, - { - name: "wraps DependencyViolation error", - args: args{ - err: errors.Wrap(awserr.New("DependencyViolation", "some message", nil), "wrapped message"), - }, - want: true, - }, - { - name: "isn't DependencyViolation error", - args: args{ - err: errors.New("some other error"), - }, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := isSecurityGroupDependencyViolationError(tt.args.err) - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/deploy/ec2/security_group_synthesizer.go b/pkg/deploy/ec2/security_group_synthesizer.go deleted file mode 100644 index 7358f3d34..000000000 --- a/pkg/deploy/ec2/security_group_synthesizer.go +++ /dev/null @@ -1,150 +0,0 @@ -package ec2 - -import ( - "context" - "github.com/go-logr/logr" - "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/util/sets" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/tracking" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" - ec2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/ec2" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/networking" -) - -// NewSecurityGroupSynthesizer constructs new securityGroupSynthesizer. -func NewSecurityGroupSynthesizer(ec2Client services.EC2, trackingProvider tracking.Provider, taggingManager TaggingManager, - sgManager SecurityGroupManager, vpcID string, logger logr.Logger, stack core.Stack) *securityGroupSynthesizer { - return &securityGroupSynthesizer{ - ec2Client: ec2Client, - trackingProvider: trackingProvider, - taggingManager: taggingManager, - sgManager: sgManager, - vpcID: vpcID, - logger: logger, - stack: stack, - unmatchedSDKSGs: nil, - } -} - -type securityGroupSynthesizer struct { - ec2Client services.EC2 - trackingProvider tracking.Provider - taggingManager TaggingManager - sgManager SecurityGroupManager - vpcID string - logger logr.Logger - - stack core.Stack - unmatchedSDKSGs []networking.SecurityGroupInfo -} - -func (s *securityGroupSynthesizer) Synthesize(ctx context.Context) error { - var resSGs []*ec2model.SecurityGroup - s.stack.ListResources(&resSGs) - sdkSGs, err := s.findSDKSecurityGroups(ctx) - if err != nil { - return err - } - matchedResAndSDKSGs, unmatchedResSGs, unmatchedSDKSGs, err := matchResAndSDKSecurityGroups(resSGs, sdkSGs, s.trackingProvider.ResourceIDTagKey()) - if err != nil { - return err - } - - // For SecurityGroup, we delete unmatched ones during post synthesize. - s.unmatchedSDKSGs = unmatchedSDKSGs - - for _, resSG := range unmatchedResSGs { - sgStatus, err := s.sgManager.Create(ctx, resSG) - if err != nil { - return err - } - resSG.SetStatus(sgStatus) - } - for _, resAndSDKSG := range matchedResAndSDKSGs { - sgStatus, err := s.sgManager.Update(ctx, resAndSDKSG.resSG, resAndSDKSG.sdkSG) - if err != nil { - return err - } - resAndSDKSG.resSG.SetStatus(sgStatus) - } - return nil -} - -func (s *securityGroupSynthesizer) PostSynthesize(ctx context.Context) error { - for _, sdkSG := range s.unmatchedSDKSGs { - if err := s.sgManager.Delete(ctx, sdkSG); err != nil { - return err - } - } - return nil -} - -// findSDKSecurityGroups will find all AWS SecurityGroups created for stack. -func (s *securityGroupSynthesizer) findSDKSecurityGroups(ctx context.Context) ([]networking.SecurityGroupInfo, error) { - stackTags := s.trackingProvider.StackTags(s.stack) - stackTagsLegacy := s.trackingProvider.StackTagsLegacy(s.stack) - return s.taggingManager.ListSecurityGroups(ctx, - tracking.TagsAsTagFilter(stackTags), - tracking.TagsAsTagFilter(stackTagsLegacy)) -} - -type resAndSDKSecurityGroupPair struct { - resSG *ec2model.SecurityGroup - sdkSG networking.SecurityGroupInfo -} - -func matchResAndSDKSecurityGroups(resSGs []*ec2model.SecurityGroup, sdkSGs []networking.SecurityGroupInfo, - resourceIDTagKey string) ([]resAndSDKSecurityGroupPair, []*ec2model.SecurityGroup, []networking.SecurityGroupInfo, error) { - var matchedResAndSDKSGs []resAndSDKSecurityGroupPair - var unmatchedResSGs []*ec2model.SecurityGroup - var unmatchedSDKSGs []networking.SecurityGroupInfo - - resSGsByID := mapResSecurityGroupByResourceID(resSGs) - sdkSGsByID, err := mapSDKSecurityGroupByResourceID(sdkSGs, resourceIDTagKey) - if err != nil { - return nil, nil, nil, err - } - - resSGIDs := sets.StringKeySet(resSGsByID) - sdkSGIDs := sets.StringKeySet(sdkSGsByID) - for _, resID := range resSGIDs.Intersection(sdkSGIDs).List() { - resSG := resSGsByID[resID] - sdkSGs := sdkSGsByID[resID] - matchedResAndSDKSGs = append(matchedResAndSDKSGs, resAndSDKSecurityGroupPair{ - resSG: resSG, - sdkSG: sdkSGs[0], - }) - for _, sdkSG := range sdkSGs[1:] { - unmatchedSDKSGs = append(unmatchedSDKSGs, sdkSG) - } - } - for _, resID := range resSGIDs.Difference(sdkSGIDs).List() { - unmatchedResSGs = append(unmatchedResSGs, resSGsByID[resID]) - } - for _, resID := range sdkSGIDs.Difference(resSGIDs).List() { - unmatchedSDKSGs = append(unmatchedSDKSGs, sdkSGsByID[resID]...) - } - - return matchedResAndSDKSGs, unmatchedResSGs, unmatchedSDKSGs, nil -} - -func mapResSecurityGroupByResourceID(resSGs []*ec2model.SecurityGroup) map[string]*ec2model.SecurityGroup { - resSGsByID := make(map[string]*ec2model.SecurityGroup, len(resSGs)) - for _, resSG := range resSGs { - resSGsByID[resSG.ID()] = resSG - } - return resSGsByID -} - -func mapSDKSecurityGroupByResourceID(sdkSGs []networking.SecurityGroupInfo, resourceIDTagKey string) (map[string][]networking.SecurityGroupInfo, error) { - sdkSGsByID := make(map[string][]networking.SecurityGroupInfo, len(sdkSGs)) - for _, sdkSG := range sdkSGs { - resourceID, ok := sdkSG.Tags[resourceIDTagKey] - if !ok { - return nil, errors.Errorf("unexpected securityGroup with no resourceID: %v", sdkSG.SecurityGroupID) - } - sdkSGsByID[resourceID] = append(sdkSGsByID[resourceID], sdkSG) - } - return sdkSGsByID, nil -} diff --git a/pkg/deploy/ec2/tagging_manager.go b/pkg/deploy/ec2/tagging_manager.go deleted file mode 100644 index 508a685e7..000000000 --- a/pkg/deploy/ec2/tagging_manager.go +++ /dev/null @@ -1,192 +0,0 @@ -package ec2 - -import ( - "context" - "errors" - "fmt" - awssdk "github.com/aws/aws-sdk-go/aws" - ec2sdk "github.com/aws/aws-sdk-go/service/ec2" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/util/sets" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/algorithm" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/tracking" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/networking" -) - -// options for ReconcileTags API. -type ReconcileTagsOptions struct { - // CurrentTags on resources. - // when it's nil, the TaggingManager will try to get the CurrentTags from AWS - CurrentTags map[string]string - - // IgnoredTagKeys defines the tag keys that should be ignored. - // these tags shouldn't be altered or deleted. - IgnoredTagKeys []string -} - -func (opts *ReconcileTagsOptions) ApplyOptions(options []ReconcileTagsOption) { - for _, option := range options { - option(opts) - } -} - -type ReconcileTagsOption func(opts *ReconcileTagsOptions) - -// WithCurrentTags is a reconcile option that supplies current tags. -func WithCurrentTags(tags map[string]string) ReconcileTagsOption { - return func(opts *ReconcileTagsOptions) { - opts.CurrentTags = tags - } -} - -// WithIgnoredTagKeys is a reconcile option that configures IgnoredTagKeys. -func WithIgnoredTagKeys(ignoredTagKeys []string) ReconcileTagsOption { - return func(opts *ReconcileTagsOptions) { - opts.IgnoredTagKeys = append(opts.IgnoredTagKeys, ignoredTagKeys...) - } -} - -// abstraction around tagging operations for EC2. -type TaggingManager interface { - // ReconcileTags will reconcile tags on resources. - ReconcileTags(ctx context.Context, resID string, desiredTags map[string]string, opts ...ReconcileTagsOption) error - - // ListSecurityGroups returns SecurityGroups that matches any of the tagging requirements. - ListSecurityGroups(ctx context.Context, tagFilters ...tracking.TagFilter) ([]networking.SecurityGroupInfo, error) -} - -// NewDefaultTaggingManager constructs new defaultTaggingManager. -func NewDefaultTaggingManager(ec2Client services.EC2, networkingSGManager networking.SecurityGroupManager, vpcID string, logger logr.Logger) *defaultTaggingManager { - return &defaultTaggingManager{ - ec2Client: ec2Client, - networkingSGManager: networkingSGManager, - vpcID: vpcID, - logger: logger, - } -} - -var _ TaggingManager = &defaultTaggingManager{} - -// default implementation for TaggingManager. -type defaultTaggingManager struct { - ec2Client services.EC2 - networkingSGManager networking.SecurityGroupManager - vpcID string - logger logr.Logger -} - -func (m *defaultTaggingManager) ReconcileTags(ctx context.Context, resID string, desiredTags map[string]string, opts ...ReconcileTagsOption) error { - reconcileOpts := ReconcileTagsOptions{ - CurrentTags: nil, - IgnoredTagKeys: nil, - } - reconcileOpts.ApplyOptions(opts) - currentTags := reconcileOpts.CurrentTags - if currentTags == nil { - // TODO: support read currentTags from AWS when we need to support more resources other than securityGroup. - return errors.New("currentTags must be specified") - } - - tagsToUpdate, tagsToRemove := algorithm.DiffStringMap(desiredTags, currentTags) - for _, ignoredTagKey := range reconcileOpts.IgnoredTagKeys { - delete(tagsToUpdate, ignoredTagKey) - delete(tagsToRemove, ignoredTagKey) - } - - if len(tagsToUpdate) > 0 { - req := &ec2sdk.CreateTagsInput{ - Resources: []*string{awssdk.String(resID)}, - Tags: convertTagsToSDKTags(tagsToUpdate), - } - - m.logger.Info("adding resource tags", - "resourceID", resID, - "change", tagsToUpdate) - if _, err := m.ec2Client.CreateTagsWithContext(ctx, req); err != nil { - return err - } - m.logger.Info("added resource tags", - "resourceID", resID) - } - - if len(tagsToRemove) > 0 { - req := &ec2sdk.DeleteTagsInput{ - Resources: []*string{awssdk.String(resID)}, - Tags: convertTagsToSDKTags(tagsToRemove), - } - - m.logger.Info("removing resource tags", - "resourceID", resID, - "change", tagsToRemove) - if _, err := m.ec2Client.DeleteTagsWithContext(ctx, req); err != nil { - return err - } - m.logger.Info("removed resource tags", - "resourceID", resID) - } - return nil -} - -func (m *defaultTaggingManager) ListSecurityGroups(ctx context.Context, tagFilters ...tracking.TagFilter) ([]networking.SecurityGroupInfo, error) { - sgInfoByID := make(map[string]networking.SecurityGroupInfo) - for _, tagFilter := range tagFilters { - sgInfoByIDForTagFilter, err := m.listSecurityGroupsWithTagFilter(ctx, tagFilter) - if err != nil { - return nil, err - } - for sgID, sgInfo := range sgInfoByIDForTagFilter { - sgInfoByID[sgID] = sgInfo - } - } - - sgInfos := make([]networking.SecurityGroupInfo, 0, len(sgInfoByID)) - for _, sgInfo := range sgInfoByID { - sgInfos = append(sgInfos, sgInfo) - } - return sgInfos, nil -} - -func (m *defaultTaggingManager) listSecurityGroupsWithTagFilter(ctx context.Context, tagFilter tracking.TagFilter) (map[string]networking.SecurityGroupInfo, error) { - req := &ec2sdk.DescribeSecurityGroupsInput{ - Filters: []*ec2sdk.Filter{ - { - Name: awssdk.String("vpc-id"), - Values: awssdk.StringSlice([]string{m.vpcID}), - }, - }, - } - - for _, tagKey := range sets.StringKeySet(tagFilter).List() { - tagValues := tagFilter[tagKey] - var filter ec2sdk.Filter - if len(tagValues) == 0 { - tagFilterName := "tag-key" - filter.Name = awssdk.String(tagFilterName) - filter.Values = awssdk.StringSlice([]string{tagKey}) - } else { - tagFilterName := fmt.Sprintf("tag:%v", tagKey) - filter.Name = awssdk.String(tagFilterName) - filter.Values = awssdk.StringSlice(tagValues) - } - req.Filters = append(req.Filters, &filter) - } - - return m.networkingSGManager.FetchSGInfosByRequest(ctx, req) -} - -// convert tags into AWS SDK tag presentation. -func convertTagsToSDKTags(tags map[string]string) []*ec2sdk.Tag { - if len(tags) == 0 { - return nil - } - sdkTags := make([]*ec2sdk.Tag, 0, len(tags)) - - for _, key := range sets.StringKeySet(tags).List() { - sdkTags = append(sdkTags, &ec2sdk.Tag{ - Key: awssdk.String(key), - Value: awssdk.String(tags[key]), - }) - } - return sdkTags -} diff --git a/pkg/deploy/ec2/tagging_manager_test.go b/pkg/deploy/ec2/tagging_manager_test.go deleted file mode 100644 index cf59bf7b4..000000000 --- a/pkg/deploy/ec2/tagging_manager_test.go +++ /dev/null @@ -1,486 +0,0 @@ -package ec2 - -import ( - "context" - "testing" - - awssdk "github.com/aws/aws-sdk-go/aws" - ec2sdk "github.com/aws/aws-sdk-go/service/ec2" - "github.com/go-logr/logr" - "github.com/golang/mock/gomock" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/stretchr/testify/assert" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/tracking" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/networking" - "sigs.k8s.io/controller-runtime/pkg/log" -) - -func Test_defaultTaggingManager_ReconcileTags(t *testing.T) { - type createTagsWithContextCall struct { - req *ec2sdk.CreateTagsInput - resp *ec2sdk.CreateTagsOutput - err error - } - type deleteTagsWithContextCall struct { - req *ec2sdk.DeleteTagsInput - resp *ec2sdk.DeleteTagsOutput - err error - } - - type fields struct { - createTagsWithContextCalls []createTagsWithContextCall - deleteTagsWithContextCalls []deleteTagsWithContextCall - } - type args struct { - resID string - desiredTags map[string]string - opts []ReconcileTagsOption - } - tests := []struct { - name string - fields fields - args args - wantErr error - }{ - { - name: "standard case - add/update/remove tags", - fields: fields{ - createTagsWithContextCalls: []createTagsWithContextCall{ - { - req: &ec2sdk.CreateTagsInput{ - Resources: awssdk.StringSlice([]string{"sg-a"}), - Tags: []*ec2sdk.Tag{ - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB2"), - }, - { - Key: awssdk.String("keyD"), - Value: awssdk.String("valueD"), - }, - }, - }, - }, - }, - deleteTagsWithContextCalls: []deleteTagsWithContextCall{ - { - req: &ec2sdk.DeleteTagsInput{ - Resources: awssdk.StringSlice([]string{"sg-a"}), - Tags: []*ec2sdk.Tag{ - { - Key: awssdk.String("keyC"), - Value: awssdk.String("valueC"), - }, - }, - }, - }, - }, - }, - args: args{ - resID: "sg-a", - desiredTags: map[string]string{ - "keyA": "valueA", - "keyB": "valueB2", - "keyD": "valueD", - }, - opts: []ReconcileTagsOption{ - WithCurrentTags(map[string]string{ - "keyA": "valueA", - "keyB": "valueB", - "keyC": "valueC", - }), - }, - }, - wantErr: nil, - }, - { - name: "ignore specific tag updates and deletes", - fields: fields{ - createTagsWithContextCalls: []createTagsWithContextCall{ - { - req: &ec2sdk.CreateTagsInput{ - Resources: awssdk.StringSlice([]string{"sg-a"}), - Tags: []*ec2sdk.Tag{ - { - Key: awssdk.String("keyC"), - Value: awssdk.String("valueC2"), - }, - { - Key: awssdk.String("keyD"), - Value: awssdk.String("valueD"), - }, - }, - }, - }, - }, - deleteTagsWithContextCalls: []deleteTagsWithContextCall{ - { - req: &ec2sdk.DeleteTagsInput{ - Resources: awssdk.StringSlice([]string{"sg-a"}), - Tags: []*ec2sdk.Tag{ - { - Key: awssdk.String("keyF"), - Value: awssdk.String("valueF"), - }, - }, - }, - }, - }, - }, - args: args{ - resID: "sg-a", - desiredTags: map[string]string{ - "keyA": "valueA", - "keyB": "valueB2", - "keyC": "valueC2", - "keyD": "valueD", - }, - opts: []ReconcileTagsOption{ - WithCurrentTags(map[string]string{ - "keyA": "valueA", - "keyB": "valueB", - "keyC": "valueC", - "keyE": "valueE", - "keyF": "valueF", - }), - WithIgnoredTagKeys([]string{"keyB", "keyE"}), - }, - }, - wantErr: nil, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ec2Client := services.NewMockEC2(ctrl) - for _, call := range tt.fields.createTagsWithContextCalls { - ec2Client.EXPECT().CreateTagsWithContext(gomock.Any(), call.req).Return(call.resp, call.err) - } - for _, call := range tt.fields.deleteTagsWithContextCalls { - ec2Client.EXPECT().DeleteTagsWithContext(gomock.Any(), call.req).Return(call.resp, call.err) - } - - m := &defaultTaggingManager{ - ec2Client: ec2Client, - logger: logr.New(&log.NullLogSink{}), - } - err := m.ReconcileTags(context.Background(), tt.args.resID, tt.args.desiredTags, tt.args.opts...) - if tt.wantErr != nil { - assert.EqualError(t, err, tt.wantErr.Error()) - } - }) - } -} - -func Test_defaultTaggingManager_ListSecurityGroups(t *testing.T) { - type fetchSGInfosByRequestCall struct { - req *ec2sdk.DescribeSecurityGroupsInput - resp map[string]networking.SecurityGroupInfo - err error - } - type fields struct { - fetchSGInfosByRequestCalls []fetchSGInfosByRequestCall - } - type args struct { - tagFilters []tracking.TagFilter - } - tests := []struct { - name string - fields fields - args args - want []networking.SecurityGroupInfo - wantErr error - }{ - { - name: "with a single tagFilter", - fields: fields{ - fetchSGInfosByRequestCalls: []fetchSGInfosByRequestCall{ - { - req: &ec2sdk.DescribeSecurityGroupsInput{ - Filters: []*ec2sdk.Filter{ - { - Name: awssdk.String("vpc-id"), - Values: awssdk.StringSlice([]string{"vpc-xxxxxxx"}), - }, - { - Name: awssdk.String("tag:keyA"), - Values: awssdk.StringSlice([]string{"valueA"}), - }, - { - Name: awssdk.String("tag:keyB"), - Values: awssdk.StringSlice([]string{"valueB1", "valueB2"}), - }, - { - Name: awssdk.String("tag-key"), - Values: awssdk.StringSlice([]string{"keyC"}), - }, - }, - }, - resp: map[string]networking.SecurityGroupInfo{ - "sg-a": { - SecurityGroupID: "sg-a", - Tags: map[string]string{ - "keyA": "valueA", - "keyB": "valueB1", - "keyC": "valueC", - "keyD": "valueD", - }, - }, - "sg-b": { - SecurityGroupID: "sg-b", - Tags: map[string]string{ - "keyA": "valueA", - "keyB": "valueB2", - "keyC": "valueC", - "keyD": "valueD", - }, - }, - }, - }, - }, - }, - args: args{ - tagFilters: []tracking.TagFilter{ - { - "keyA": []string{"valueA"}, - "keyB": []string{"valueB1", "valueB2"}, - "keyC": nil, - }, - }, - }, - want: []networking.SecurityGroupInfo{ - { - SecurityGroupID: "sg-a", - Tags: map[string]string{ - "keyA": "valueA", - "keyB": "valueB1", - "keyC": "valueC", - "keyD": "valueD", - }, - }, - { - SecurityGroupID: "sg-b", - Tags: map[string]string{ - "keyA": "valueA", - "keyB": "valueB2", - "keyC": "valueC", - "keyD": "valueD", - }, - }, - }, - }, - { - name: "with two tagFilter", - fields: fields{ - fetchSGInfosByRequestCalls: []fetchSGInfosByRequestCall{ - { - req: &ec2sdk.DescribeSecurityGroupsInput{ - Filters: []*ec2sdk.Filter{ - { - Name: awssdk.String("vpc-id"), - Values: awssdk.StringSlice([]string{"vpc-xxxxxxx"}), - }, - { - Name: awssdk.String("tag:keyA"), - Values: awssdk.StringSlice([]string{"valueA"}), - }, - { - Name: awssdk.String("tag:keyB"), - Values: awssdk.StringSlice([]string{"valueB1", "valueB2"}), - }, - { - Name: awssdk.String("tag-key"), - Values: awssdk.StringSlice([]string{"keyC"}), - }, - }, - }, - resp: map[string]networking.SecurityGroupInfo{ - "sg-a": { - SecurityGroupID: "sg-a", - Tags: map[string]string{ - "keyA": "valueA", - "keyB": "valueB1", - "keyC": "valueC", - "keyD": "valueD", - }, - }, - "sg-b": { - SecurityGroupID: "sg-b", - Tags: map[string]string{ - "keyA": "valueA", - "keyB": "valueB2", - "keyC": "valueC", - "keyD": "valueD", - }, - }, - }, - }, - { - req: &ec2sdk.DescribeSecurityGroupsInput{ - Filters: []*ec2sdk.Filter{ - { - Name: awssdk.String("vpc-id"), - Values: awssdk.StringSlice([]string{"vpc-xxxxxxx"}), - }, - { - Name: awssdk.String("tag:keyA"), - Values: awssdk.StringSlice([]string{"valueA"}), - }, - { - Name: awssdk.String("tag:keyB"), - Values: awssdk.StringSlice([]string{"valueB2", "valueB3"}), - }, - { - Name: awssdk.String("tag-key"), - Values: awssdk.StringSlice([]string{"keyC"}), - }, - }, - }, - resp: map[string]networking.SecurityGroupInfo{ - "sg-b": { - SecurityGroupID: "sg-b", - Tags: map[string]string{ - "keyA": "valueA", - "keyB": "valueB2", - "keyC": "valueC", - "keyD": "valueD", - }, - }, - "sg-c": { - SecurityGroupID: "sg-c", - Tags: map[string]string{ - "keyA": "valueA", - "keyB": "valueB3", - "keyC": "valueC", - "keyD": "valueD", - }, - }, - }, - }, - }, - }, - args: args{ - tagFilters: []tracking.TagFilter{ - { - "keyA": []string{"valueA"}, - "keyB": []string{"valueB1", "valueB2"}, - "keyC": nil, - }, - { - "keyA": []string{"valueA"}, - "keyB": []string{"valueB2", "valueB3"}, - "keyC": nil, - }, - }, - }, - want: []networking.SecurityGroupInfo{ - { - SecurityGroupID: "sg-a", - Tags: map[string]string{ - "keyA": "valueA", - "keyB": "valueB1", - "keyC": "valueC", - "keyD": "valueD", - }, - }, - { - SecurityGroupID: "sg-b", - Tags: map[string]string{ - "keyA": "valueA", - "keyB": "valueB2", - "keyC": "valueC", - "keyD": "valueD", - }, - }, - { - SecurityGroupID: "sg-c", - Tags: map[string]string{ - "keyA": "valueA", - "keyB": "valueB3", - "keyC": "valueC", - "keyD": "valueD", - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - networkingSGManager := networking.NewMockSecurityGroupManager(ctrl) - for _, call := range tt.fields.fetchSGInfosByRequestCalls { - networkingSGManager.EXPECT().FetchSGInfosByRequest(gomock.Any(), call.req).Return(call.resp, call.err) - } - m := &defaultTaggingManager{ - networkingSGManager: networkingSGManager, - vpcID: "vpc-xxxxxxx", - } - got, err := m.ListSecurityGroups(context.Background(), tt.args.tagFilters...) - if tt.wantErr != nil { - assert.EqualError(t, err, tt.wantErr.Error()) - } else { - assert.NoError(t, err) - opts := cmpopts.SortSlices(func(lhs networking.SecurityGroupInfo, rhs networking.SecurityGroupInfo) bool { - return lhs.SecurityGroupID < rhs.SecurityGroupID - }) - assert.True(t, cmp.Equal(tt.want, got, opts), "diff", cmp.Diff(tt.want, got, opts)) - } - }) - } -} - -func Test_convertTagsToSDKTags(t *testing.T) { - type args struct { - tags map[string]string - } - tests := []struct { - name string - args args - want []*ec2sdk.Tag - }{ - { - name: "non-empty tags", - args: args{ - tags: map[string]string{ - "keyA": "valueA", - "keyB": "valueB", - }, - }, - want: []*ec2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB"), - }, - }, - }, - { - name: "nil tags", - args: args{ - tags: nil, - }, - want: nil, - }, - { - name: "empty tags", - args: args{ - tags: map[string]string{}, - }, - want: nil, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := convertTagsToSDKTags(tt.args.tags) - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/deploy/elbv2/listener_manager.go b/pkg/deploy/elbv2/listener_manager.go deleted file mode 100644 index 3e1aed2c5..000000000 --- a/pkg/deploy/elbv2/listener_manager.go +++ /dev/null @@ -1,334 +0,0 @@ -package elbv2 - -import ( - "context" - "time" - - awssdk "github.com/aws/aws-sdk-go/aws" - elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/go-logr/logr" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/util/sets" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/config" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/tracking" - elbv2equality "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/equality/elbv2" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/runtime" -) - -// ListenerManager is responsible for create/update/delete Listener resources. -type ListenerManager interface { - Create(ctx context.Context, resLS *elbv2model.Listener) (elbv2model.ListenerStatus, error) - - Update(ctx context.Context, resLS *elbv2model.Listener, sdkLS ListenerWithTags) (elbv2model.ListenerStatus, error) - - Delete(ctx context.Context, sdkLS ListenerWithTags) error -} - -func NewDefaultListenerManager(elbv2Client services.ELBV2, trackingProvider tracking.Provider, - taggingManager TaggingManager, externalManagedTags []string, featureGates config.FeatureGates, logger logr.Logger) *defaultListenerManager { - return &defaultListenerManager{ - elbv2Client: elbv2Client, - trackingProvider: trackingProvider, - taggingManager: taggingManager, - externalManagedTags: externalManagedTags, - featureGates: featureGates, - logger: logger, - waitLSExistencePollInterval: defaultWaitLSExistencePollInterval, - waitLSExistenceTimeout: defaultWaitLSExistenceTimeout, - } -} - -var _ ListenerManager = &defaultListenerManager{} - -// default implementation for ListenerManager -type defaultListenerManager struct { - elbv2Client services.ELBV2 - trackingProvider tracking.Provider - taggingManager TaggingManager - externalManagedTags []string - featureGates config.FeatureGates - logger logr.Logger - - waitLSExistencePollInterval time.Duration - waitLSExistenceTimeout time.Duration -} - -func (m *defaultListenerManager) Create(ctx context.Context, resLS *elbv2model.Listener) (elbv2model.ListenerStatus, error) { - req, err := buildSDKCreateListenerInput(resLS.Spec, m.featureGates) - if err != nil { - return elbv2model.ListenerStatus{}, err - } - var lsTags map[string]string - if m.featureGates.Enabled(config.ListenerRulesTagging) { - lsTags = m.trackingProvider.ResourceTags(resLS.Stack(), resLS, resLS.Spec.Tags) - } - req.Tags = convertTagsToSDKTags(lsTags) - - m.logger.Info("creating listener", - "stackID", resLS.Stack().StackID(), - "resourceID", resLS.ID()) - resp, err := m.elbv2Client.CreateListenerWithContext(ctx, req) - if err != nil { - return elbv2model.ListenerStatus{}, err - } - sdkLS := ListenerWithTags{ - Listener: resp.Listeners[0], - Tags: lsTags, - } - m.logger.Info("created listener", - "stackID", resLS.Stack().StackID(), - "resourceID", resLS.ID(), - "arn", awssdk.StringValue(sdkLS.Listener.ListenerArn)) - - if err := runtime.RetryImmediateOnError(m.waitLSExistencePollInterval, m.waitLSExistenceTimeout, isListenerNotFoundError, func() error { - return m.updateSDKListenerWithExtraCertificates(ctx, resLS, sdkLS, true) - }); err != nil { - return elbv2model.ListenerStatus{}, errors.Wrap(err, "failed to update extra certificates on listener") - } - return buildResListenerStatus(sdkLS), nil -} - -func (m *defaultListenerManager) Update(ctx context.Context, resLS *elbv2model.Listener, sdkLS ListenerWithTags) (elbv2model.ListenerStatus, error) { - if m.featureGates.Enabled(config.ListenerRulesTagging) { - if err := m.updateSDKListenerWithTags(ctx, resLS, sdkLS); err != nil { - return elbv2model.ListenerStatus{}, err - } - } - if err := m.updateSDKListenerWithSettings(ctx, resLS, sdkLS); err != nil { - return elbv2model.ListenerStatus{}, err - } - if err := m.updateSDKListenerWithExtraCertificates(ctx, resLS, sdkLS, false); err != nil { - return elbv2model.ListenerStatus{}, err - } - return buildResListenerStatus(sdkLS), nil -} - -func (m *defaultListenerManager) Delete(ctx context.Context, sdkLS ListenerWithTags) error { - req := &elbv2sdk.DeleteListenerInput{ - ListenerArn: sdkLS.Listener.ListenerArn, - } - m.logger.Info("deleting listener", - "arn", awssdk.StringValue(req.ListenerArn)) - if _, err := m.elbv2Client.DeleteListenerWithContext(ctx, req); err != nil { - return err - } - m.logger.Info("deleted listener", - "arn", awssdk.StringValue(req.ListenerArn)) - return nil -} - -func (m *defaultListenerManager) updateSDKListenerWithTags(ctx context.Context, resLS *elbv2model.Listener, sdkLS ListenerWithTags) error { - desiredLSTags := m.trackingProvider.ResourceTags(resLS.Stack(), resLS, resLS.Spec.Tags) - return m.taggingManager.ReconcileTags(ctx, awssdk.StringValue(sdkLS.Listener.ListenerArn), desiredLSTags, - WithCurrentTags(sdkLS.Tags), - WithIgnoredTagKeys(m.externalManagedTags)) -} - -func (m *defaultListenerManager) updateSDKListenerWithSettings(ctx context.Context, resLS *elbv2model.Listener, sdkLS ListenerWithTags) error { - desiredDefaultActions, err := buildSDKActions(resLS.Spec.DefaultActions, m.featureGates) - if err != nil { - return err - } - desiredDefaultCerts, _ := buildSDKCertificates(resLS.Spec.Certificates) - if !isSDKListenerSettingsDrifted(resLS.Spec, sdkLS, desiredDefaultActions, desiredDefaultCerts) { - return nil - } - req := buildSDKModifyListenerInput(resLS.Spec, desiredDefaultActions, desiredDefaultCerts) - req.ListenerArn = sdkLS.Listener.ListenerArn - m.logger.Info("modifying listener", - "stackID", resLS.Stack().StackID(), - "resourceID", resLS.ID(), - "arn", awssdk.StringValue(sdkLS.Listener.ListenerArn)) - if _, err := m.elbv2Client.ModifyListenerWithContext(ctx, req); err != nil { - return err - } - m.logger.Info("modified listener", - "stackID", resLS.Stack().StackID(), - "resourceID", resLS.ID(), - "arn", awssdk.StringValue(sdkLS.Listener.ListenerArn)) - return nil -} - -// updateSDKListenerWithExtraCertificates will update the extra certificates on listener. -// currentExtraCertificates is the current extra certificates, if it's nil, the current extra certificates will be fetched from AWS. -func (m *defaultListenerManager) updateSDKListenerWithExtraCertificates(ctx context.Context, resLS *elbv2model.Listener, - sdkLS ListenerWithTags, isNewSDKListener bool) error { - // if TLS is not supported, we shouldn't update - if resLS.Spec.SSLPolicy == nil && sdkLS.Listener.SslPolicy == nil { - m.logger.V(1).Info("Res and Sdk Listener don't have SSL Policy set, we skip updating extra certs for non-TLS listener.") - return nil - } - - desiredExtraCertARNs := sets.NewString() - _, desiredExtraCerts := buildSDKCertificates(resLS.Spec.Certificates) - for _, cert := range desiredExtraCerts { - desiredExtraCertARNs.Insert(awssdk.StringValue(cert.CertificateArn)) - } - currentExtraCertARNs := sets.NewString() - if !isNewSDKListener { - certARNs, err := m.fetchSDKListenerExtraCertificateARNs(ctx, sdkLS) - if err != nil { - return err - } - currentExtraCertARNs.Insert(certARNs...) - } - - for _, certARN := range currentExtraCertARNs.Difference(desiredExtraCertARNs).List() { - req := &elbv2sdk.RemoveListenerCertificatesInput{ - ListenerArn: sdkLS.Listener.ListenerArn, - Certificates: []*elbv2sdk.Certificate{ - { - CertificateArn: awssdk.String(certARN), - }, - }, - } - m.logger.Info("removing certificate from listener", - "stackID", resLS.Stack().StackID(), - "resourceID", resLS.ID(), - "arn", awssdk.StringValue(sdkLS.Listener.ListenerArn), - "certificateARN", certARN) - if _, err := m.elbv2Client.RemoveListenerCertificatesWithContext(ctx, req); err != nil { - return err - } - m.logger.Info("removed certificate from listener", - "stackID", resLS.Stack().StackID(), - "resourceID", resLS.ID(), - "arn", awssdk.StringValue(sdkLS.Listener.ListenerArn), - "certificateARN", certARN) - } - - for _, certARN := range desiredExtraCertARNs.Difference(currentExtraCertARNs).List() { - req := &elbv2sdk.AddListenerCertificatesInput{ - ListenerArn: sdkLS.Listener.ListenerArn, - Certificates: []*elbv2sdk.Certificate{ - { - CertificateArn: awssdk.String(certARN), - }, - }, - } - m.logger.Info("adding certificate to listener", - "stackID", resLS.Stack().StackID(), - "resourceID", resLS.ID(), - "arn", awssdk.StringValue(sdkLS.Listener.ListenerArn), - "certificateARN", certARN) - if _, err := m.elbv2Client.AddListenerCertificatesWithContext(ctx, req); err != nil { - return err - } - m.logger.Info("added certificate to listener", - "stackID", resLS.Stack().StackID(), - "resourceID", resLS.ID(), - "arn", awssdk.StringValue(sdkLS.Listener.ListenerArn), - "certificateARN", certARN) - } - - return nil -} - -func (m *defaultListenerManager) fetchSDKListenerExtraCertificateARNs(ctx context.Context, sdkLS ListenerWithTags) ([]string, error) { - req := &elbv2sdk.DescribeListenerCertificatesInput{ - ListenerArn: sdkLS.Listener.ListenerArn, - } - sdkCerts, err := m.elbv2Client.DescribeListenerCertificatesAsList(ctx, req) - if err != nil { - return nil, err - } - extraCertARNs := make([]string, 0, len(sdkCerts)) - for _, cert := range sdkCerts { - if !awssdk.BoolValue(cert.IsDefault) { - extraCertARNs = append(extraCertARNs, awssdk.StringValue(cert.CertificateArn)) - } - } - return extraCertARNs, nil -} - -func isSDKListenerSettingsDrifted(lsSpec elbv2model.ListenerSpec, sdkLS ListenerWithTags, - desiredDefaultActions []*elbv2sdk.Action, desiredDefaultCerts []*elbv2sdk.Certificate) bool { - if lsSpec.Port != awssdk.Int64Value(sdkLS.Listener.Port) { - return true - } - if string(lsSpec.Protocol) != awssdk.StringValue(sdkLS.Listener.Protocol) { - return true - } - if !cmp.Equal(desiredDefaultActions, sdkLS.Listener.DefaultActions, elbv2equality.CompareOptionForActions()) { - return true - } - if !cmp.Equal(desiredDefaultCerts, sdkLS.Listener.Certificates, elbv2equality.CompareOptionForCertificates()) { - return true - } - if lsSpec.SSLPolicy != nil && awssdk.StringValue(lsSpec.SSLPolicy) != awssdk.StringValue(sdkLS.Listener.SslPolicy) { - return true - } - if len(lsSpec.ALPNPolicy) != 0 && !cmp.Equal(lsSpec.ALPNPolicy, awssdk.StringValueSlice(sdkLS.Listener.AlpnPolicy), cmpopts.EquateEmpty()) { - return true - } - - return false -} - -func buildSDKCreateListenerInput(lsSpec elbv2model.ListenerSpec, featureGates config.FeatureGates) (*elbv2sdk.CreateListenerInput, error) { - ctx := context.Background() - lbARN, err := lsSpec.LoadBalancerARN.Resolve(ctx) - if err != nil { - return nil, err - } - sdkObj := &elbv2sdk.CreateListenerInput{} - sdkObj.LoadBalancerArn = awssdk.String(lbARN) - sdkObj.Port = awssdk.Int64(lsSpec.Port) - sdkObj.Protocol = awssdk.String(string(lsSpec.Protocol)) - defaultActions, err := buildSDKActions(lsSpec.DefaultActions, featureGates) - if err != nil { - return nil, err - } - sdkObj.DefaultActions = defaultActions - sdkObj.Certificates, _ = buildSDKCertificates(lsSpec.Certificates) - sdkObj.SslPolicy = lsSpec.SSLPolicy - if len(lsSpec.ALPNPolicy) != 0 { - sdkObj.AlpnPolicy = awssdk.StringSlice(lsSpec.ALPNPolicy) - } - return sdkObj, nil -} - -func buildSDKModifyListenerInput(lsSpec elbv2model.ListenerSpec, desiredDefaultActions []*elbv2sdk.Action, desiredDefaultCerts []*elbv2sdk.Certificate) *elbv2sdk.ModifyListenerInput { - sdkObj := &elbv2sdk.ModifyListenerInput{} - sdkObj.Port = awssdk.Int64(lsSpec.Port) - sdkObj.Protocol = awssdk.String(string(lsSpec.Protocol)) - sdkObj.DefaultActions = desiredDefaultActions - sdkObj.Certificates = desiredDefaultCerts - sdkObj.SslPolicy = lsSpec.SSLPolicy - if len(lsSpec.ALPNPolicy) != 0 { - sdkObj.AlpnPolicy = awssdk.StringSlice(lsSpec.ALPNPolicy) - } - return sdkObj -} - -// buildSDKCertificates builds the certificate list for listener. -// returns the default certificates and extra certificates. -func buildSDKCertificates(modelCerts []elbv2model.Certificate) ([]*elbv2sdk.Certificate, []*elbv2sdk.Certificate) { - if len(modelCerts) == 0 { - return nil, nil - } - - var defaultSDKCerts []*elbv2sdk.Certificate - var extraSDKCerts []*elbv2sdk.Certificate - defaultSDKCerts = append(defaultSDKCerts, buildSDKCertificate(modelCerts[0])) - for _, cert := range modelCerts[1:] { - extraSDKCerts = append(extraSDKCerts, buildSDKCertificate(cert)) - } - return defaultSDKCerts, extraSDKCerts -} - -func buildSDKCertificate(modelCert elbv2model.Certificate) *elbv2sdk.Certificate { - return &elbv2sdk.Certificate{ - CertificateArn: modelCert.CertificateARN, - } -} - -func buildResListenerStatus(sdkLS ListenerWithTags) elbv2model.ListenerStatus { - return elbv2model.ListenerStatus{ - ListenerARN: awssdk.StringValue(sdkLS.Listener.ListenerArn), - } -} diff --git a/pkg/deploy/elbv2/listener_manager_test.go b/pkg/deploy/elbv2/listener_manager_test.go deleted file mode 100644 index a6af95c7c..000000000 --- a/pkg/deploy/elbv2/listener_manager_test.go +++ /dev/null @@ -1,186 +0,0 @@ -package elbv2 - -import ( - awssdk "github.com/aws/aws-sdk-go/aws" - elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/stretchr/testify/assert" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" - "testing" -) - -func Test_isSDKListenerSettingsDrifted(t *testing.T) { - type args struct { - lsSpec elbv2model.ListenerSpec - sdkLS ListenerWithTags - desiredDefaultActions []*elbv2sdk.Action - desiredDefaultCerts []*elbv2sdk.Certificate - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "listener hasn't drifted", - args: args{ - lsSpec: elbv2model.ListenerSpec{ - Port: 80, - Protocol: elbv2model.ProtocolHTTPS, - SSLPolicy: awssdk.String("ELBSecurityPolicy-FS-1-2-Res-2019-08"), - ALPNPolicy: []string{"HTTP2Preferred"}, - }, - sdkLS: ListenerWithTags{ - Listener: &elbv2sdk.Listener{ - Port: awssdk.Int64(80), - Protocol: awssdk.String("HTTPS"), - Certificates: []*elbv2sdk.Certificate{ - { - CertificateArn: awssdk.String("cert-arn1"), - IsDefault: awssdk.Bool(true), - }, - }, - DefaultActions: []*elbv2sdk.Action{ - { - Type: awssdk.String("fixed-response"), - FixedResponseConfig: &elbv2sdk.FixedResponseActionConfig{ - StatusCode: awssdk.String("404"), - }, - }, - }, - SslPolicy: awssdk.String("ELBSecurityPolicy-FS-1-2-Res-2019-08"), - AlpnPolicy: awssdk.StringSlice([]string{"HTTP2Preferred"}), - }, - }, - desiredDefaultCerts: []*elbv2sdk.Certificate{ - { - CertificateArn: awssdk.String("cert-arn1"), - IsDefault: awssdk.Bool(true), - }, - }, - desiredDefaultActions: []*elbv2sdk.Action{ - { - Type: awssdk.String("fixed-response"), - FixedResponseConfig: &elbv2sdk.FixedResponseActionConfig{ - StatusCode: awssdk.String("404"), - }, - }, - }, - }, - }, - { - name: "listener hasn't drifted if multiple acm specified", - args: args{ - lsSpec: elbv2model.ListenerSpec{ - Port: 80, - Protocol: elbv2model.ProtocolHTTPS, - SSLPolicy: awssdk.String("ELBSecurityPolicy-FS-1-2-Res-2019-08"), - ALPNPolicy: []string{"HTTP2Preferred"}, - Certificates: []elbv2model.Certificate{ - { - CertificateARN: awssdk.String("cert-arn1"), - }, - { - CertificateARN: awssdk.String("cert-arn2"), - }, - }, - }, - sdkLS: ListenerWithTags{ - Listener: &elbv2sdk.Listener{ - Port: awssdk.Int64(80), - Protocol: awssdk.String("HTTPS"), - Certificates: []*elbv2sdk.Certificate{ - { - CertificateArn: awssdk.String("cert-arn1"), - IsDefault: awssdk.Bool(true), - }, - }, - DefaultActions: []*elbv2sdk.Action{ - { - Type: awssdk.String("fixed-response"), - FixedResponseConfig: &elbv2sdk.FixedResponseActionConfig{ - StatusCode: awssdk.String("404"), - }, - }, - }, - SslPolicy: awssdk.String("ELBSecurityPolicy-FS-1-2-Res-2019-08"), - AlpnPolicy: awssdk.StringSlice([]string{"HTTP2Preferred"}), - }, - }, - desiredDefaultCerts: []*elbv2sdk.Certificate{ - { - CertificateArn: awssdk.String("cert-arn1"), - IsDefault: awssdk.Bool(true), - }, - }, - desiredDefaultActions: []*elbv2sdk.Action{ - { - Type: awssdk.String("fixed-response"), - FixedResponseConfig: &elbv2sdk.FixedResponseActionConfig{ - StatusCode: awssdk.String("404"), - }, - }, - }, - }, - }, - { - name: "Ignore ALPN configuration if not specified in model", - args: args{ - lsSpec: elbv2model.ListenerSpec{ - Port: 80, - Protocol: elbv2model.ProtocolHTTPS, - SSLPolicy: awssdk.String("ELBSecurityPolicy-FS-1-2-Res-2019-08"), - }, - sdkLS: ListenerWithTags{ - Listener: &elbv2sdk.Listener{ - Port: awssdk.Int64(80), - Protocol: awssdk.String("HTTPS"), - Certificates: []*elbv2sdk.Certificate{ - { - CertificateArn: awssdk.String("cert-arn1"), - IsDefault: awssdk.Bool(true), - }, - }, - DefaultActions: []*elbv2sdk.Action{ - { - Type: awssdk.String("forward-config"), - ForwardConfig: &elbv2sdk.ForwardActionConfig{ - TargetGroups: []*elbv2sdk.TargetGroupTuple{ - { - TargetGroupArn: awssdk.String("target-group"), - }, - }, - }, - }, - }, - SslPolicy: awssdk.String("ELBSecurityPolicy-FS-1-2-Res-2019-08"), - AlpnPolicy: awssdk.StringSlice([]string{"HTTP2Preferred"}), - }, - }, - desiredDefaultCerts: []*elbv2sdk.Certificate{ - { - CertificateArn: awssdk.String("cert-arn1"), - IsDefault: awssdk.Bool(true), - }, - }, - desiredDefaultActions: []*elbv2sdk.Action{ - { - Type: awssdk.String("forward-config"), - ForwardConfig: &elbv2sdk.ForwardActionConfig{ - TargetGroups: []*elbv2sdk.TargetGroupTuple{ - { - TargetGroupArn: awssdk.String("target-group"), - }, - }, - }, - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := isSDKListenerSettingsDrifted(tt.args.lsSpec, tt.args.sdkLS, tt.args.desiredDefaultActions, tt.args.desiredDefaultCerts) - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/deploy/elbv2/listener_rule_manager.go b/pkg/deploy/elbv2/listener_rule_manager.go deleted file mode 100644 index d820285a2..000000000 --- a/pkg/deploy/elbv2/listener_rule_manager.go +++ /dev/null @@ -1,193 +0,0 @@ -package elbv2 - -import ( - "context" - awssdk "github.com/aws/aws-sdk-go/aws" - elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/go-logr/logr" - "github.com/google/go-cmp/cmp" - "github.com/pkg/errors" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/config" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/tracking" - elbv2equality "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/equality/elbv2" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/runtime" - "time" -) - -// ListenerRuleManager is responsible for create/update/delete ListenerRule resources. -type ListenerRuleManager interface { - Create(ctx context.Context, resLR *elbv2model.ListenerRule) (elbv2model.ListenerRuleStatus, error) - - Update(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags) (elbv2model.ListenerRuleStatus, error) - - Delete(ctx context.Context, sdkLR ListenerRuleWithTags) error -} - -// NewDefaultListenerRuleManager constructs new defaultListenerRuleManager. -func NewDefaultListenerRuleManager(elbv2Client services.ELBV2, trackingProvider tracking.Provider, - taggingManager TaggingManager, externalManagedTags []string, featureGates config.FeatureGates, logger logr.Logger) *defaultListenerRuleManager { - return &defaultListenerRuleManager{ - elbv2Client: elbv2Client, - trackingProvider: trackingProvider, - taggingManager: taggingManager, - externalManagedTags: externalManagedTags, - featureGates: featureGates, - logger: logger, - waitLSExistencePollInterval: defaultWaitLSExistencePollInterval, - waitLSExistenceTimeout: defaultWaitLSExistenceTimeout, - } -} - -// default implementation for ListenerRuleManager. -type defaultListenerRuleManager struct { - elbv2Client services.ELBV2 - trackingProvider tracking.Provider - taggingManager TaggingManager - externalManagedTags []string - featureGates config.FeatureGates - logger logr.Logger - - waitLSExistencePollInterval time.Duration - waitLSExistenceTimeout time.Duration -} - -func (m *defaultListenerRuleManager) Create(ctx context.Context, resLR *elbv2model.ListenerRule) (elbv2model.ListenerRuleStatus, error) { - req, err := buildSDKCreateListenerRuleInput(resLR.Spec, m.featureGates) - if err != nil { - return elbv2model.ListenerRuleStatus{}, err - } - var ruleTags map[string]string - if m.featureGates.Enabled(config.ListenerRulesTagging) { - ruleTags = m.trackingProvider.ResourceTags(resLR.Stack(), resLR, resLR.Spec.Tags) - } - req.Tags = convertTagsToSDKTags(ruleTags) - - m.logger.Info("creating listener rule", - "stackID", resLR.Stack().StackID(), - "resourceID", resLR.ID()) - var sdkLR ListenerRuleWithTags - if err := runtime.RetryImmediateOnError(m.waitLSExistencePollInterval, m.waitLSExistenceTimeout, isListenerNotFoundError, func() error { - resp, err := m.elbv2Client.CreateRuleWithContext(ctx, req) - if err != nil { - return err - } - sdkLR = ListenerRuleWithTags{ - ListenerRule: resp.Rules[0], - Tags: ruleTags, - } - return nil - }); err != nil { - return elbv2model.ListenerRuleStatus{}, errors.Wrap(err, "failed to create listener rule") - } - m.logger.Info("created listener rule", - "stackID", resLR.Stack().StackID(), - "resourceID", resLR.ID(), - "arn", awssdk.StringValue(sdkLR.ListenerRule.RuleArn)) - - return buildResListenerRuleStatus(sdkLR), nil -} - -func (m *defaultListenerRuleManager) Update(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags) (elbv2model.ListenerRuleStatus, error) { - if m.featureGates.Enabled(config.ListenerRulesTagging) { - if err := m.updateSDKListenerRuleWithTags(ctx, resLR, sdkLR); err != nil { - return elbv2model.ListenerRuleStatus{}, err - } - } - if err := m.updateSDKListenerRuleWithSettings(ctx, resLR, sdkLR); err != nil { - return elbv2model.ListenerRuleStatus{}, err - } - return buildResListenerRuleStatus(sdkLR), nil -} - -func (m *defaultListenerRuleManager) Delete(ctx context.Context, sdkLR ListenerRuleWithTags) error { - req := &elbv2sdk.DeleteRuleInput{ - RuleArn: sdkLR.ListenerRule.RuleArn, - } - m.logger.Info("deleting listener rule", - "arn", awssdk.StringValue(req.RuleArn)) - if _, err := m.elbv2Client.DeleteRuleWithContext(ctx, req); err != nil { - return err - } - m.logger.Info("deleted listener rule", - "arn", awssdk.StringValue(req.RuleArn)) - return nil -} - -func (m *defaultListenerRuleManager) updateSDKListenerRuleWithSettings(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags) error { - desiredActions, err := buildSDKActions(resLR.Spec.Actions, m.featureGates) - if err != nil { - return err - } - desiredConditions := buildSDKRuleConditions(resLR.Spec.Conditions) - if !isSDKListenerRuleSettingsDrifted(resLR.Spec, sdkLR, desiredActions, desiredConditions) { - return nil - } - - req := buildSDKModifyListenerRuleInput(resLR.Spec, desiredActions, desiredConditions) - req.RuleArn = sdkLR.ListenerRule.RuleArn - m.logger.Info("modifying listener rule", - "stackID", resLR.Stack().StackID(), - "resourceID", resLR.ID(), - "arn", awssdk.StringValue(sdkLR.ListenerRule.RuleArn)) - if _, err := m.elbv2Client.ModifyRuleWithContext(ctx, req); err != nil { - return err - } - m.logger.Info("modified listener rule", - "stackID", resLR.Stack().StackID(), - "resourceID", resLR.ID(), - "arn", awssdk.StringValue(sdkLR.ListenerRule.RuleArn)) - return nil -} - -func (m *defaultListenerRuleManager) updateSDKListenerRuleWithTags(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags) error { - desiredTags := m.trackingProvider.ResourceTags(resLR.Stack(), resLR, resLR.Spec.Tags) - return m.taggingManager.ReconcileTags(ctx, awssdk.StringValue(sdkLR.ListenerRule.RuleArn), desiredTags, - WithCurrentTags(sdkLR.Tags), - WithIgnoredTagKeys(m.externalManagedTags)) -} - -func isSDKListenerRuleSettingsDrifted(lrSpec elbv2model.ListenerRuleSpec, sdkLR ListenerRuleWithTags, - desiredActions []*elbv2sdk.Action, desiredConditions []*elbv2sdk.RuleCondition) bool { - - if !cmp.Equal(desiredActions, sdkLR.ListenerRule.Actions, elbv2equality.CompareOptionForActions()) { - return true - } - if !cmp.Equal(desiredConditions, sdkLR.ListenerRule.Conditions, elbv2equality.CompareOptionForRuleConditions()) { - return true - } - - return false -} - -func buildSDKCreateListenerRuleInput(lrSpec elbv2model.ListenerRuleSpec, featureGates config.FeatureGates) (*elbv2sdk.CreateRuleInput, error) { - ctx := context.Background() - lsARN, err := lrSpec.ListenerARN.Resolve(ctx) - if err != nil { - return nil, err - } - sdkObj := &elbv2sdk.CreateRuleInput{} - sdkObj.ListenerArn = awssdk.String(lsARN) - sdkObj.Priority = awssdk.Int64(lrSpec.Priority) - actions, err := buildSDKActions(lrSpec.Actions, featureGates) - if err != nil { - return nil, err - } - sdkObj.Actions = actions - sdkObj.Conditions = buildSDKRuleConditions(lrSpec.Conditions) - return sdkObj, nil -} - -func buildSDKModifyListenerRuleInput(_ elbv2model.ListenerRuleSpec, desiredActions []*elbv2sdk.Action, desiredConditions []*elbv2sdk.RuleCondition) *elbv2sdk.ModifyRuleInput { - sdkObj := &elbv2sdk.ModifyRuleInput{} - sdkObj.Actions = desiredActions - sdkObj.Conditions = desiredConditions - return sdkObj -} - -func buildResListenerRuleStatus(sdkLR ListenerRuleWithTags) elbv2model.ListenerRuleStatus { - return elbv2model.ListenerRuleStatus{ - RuleARN: awssdk.StringValue(sdkLR.ListenerRule.RuleArn), - } -} diff --git a/pkg/deploy/elbv2/listener_rule_synthesizer.go b/pkg/deploy/elbv2/listener_rule_synthesizer.go deleted file mode 100644 index 9d28d3561..000000000 --- a/pkg/deploy/elbv2/listener_rule_synthesizer.go +++ /dev/null @@ -1,168 +0,0 @@ -package elbv2 - -import ( - "context" - awssdk "github.com/aws/aws-sdk-go/aws" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/util/sets" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" - "strconv" -) - -// NewListenerRuleSynthesizer constructs new listenerRuleSynthesizer. -func NewListenerRuleSynthesizer(elbv2Client services.ELBV2, taggingManager TaggingManager, - lrManager ListenerRuleManager, logger logr.Logger, stack core.Stack) *listenerRuleSynthesizer { - return &listenerRuleSynthesizer{ - elbv2Client: elbv2Client, - lrManager: lrManager, - logger: logger, - taggingManager: taggingManager, - stack: stack, - } -} - -type listenerRuleSynthesizer struct { - elbv2Client services.ELBV2 - lrManager ListenerRuleManager - logger logr.Logger - taggingManager TaggingManager - - stack core.Stack -} - -func (s *listenerRuleSynthesizer) Synthesize(ctx context.Context) error { - var resLRs []*elbv2model.ListenerRule - s.stack.ListResources(&resLRs) - resLRsByLSARN, err := mapResListenerRuleByListenerARN(resLRs) - if err != nil { - return err - } - - var resLSs []*elbv2model.Listener - s.stack.ListResources(&resLSs) - for _, resLS := range resLSs { - lsARN, err := resLS.ListenerARN().Resolve(ctx) - if err != nil { - return err - } - resLRs := resLRsByLSARN[lsARN] - if err := s.synthesizeListenerRulesOnListener(ctx, lsARN, resLRs); err != nil { - return err - } - } - return nil -} - -func (s *listenerRuleSynthesizer) PostSynthesize(ctx context.Context) error { - // nothing to do here. - return nil -} - -func (s *listenerRuleSynthesizer) synthesizeListenerRulesOnListener(ctx context.Context, lsARN string, resLRs []*elbv2model.ListenerRule) error { - sdkLRs, err := s.findSDKListenersRulesOnLS(ctx, lsARN) - if err != nil { - return err - } - - matchedResAndSDKLRs, unmatchedResLRs, unmatchedSDKLRs := matchResAndSDKListenerRules(resLRs, sdkLRs) - for _, sdkLR := range unmatchedSDKLRs { - if err := s.lrManager.Delete(ctx, sdkLR); err != nil { - return err - } - } - for _, resLR := range unmatchedResLRs { - lrStatus, err := s.lrManager.Create(ctx, resLR) - if err != nil { - return err - } - resLR.SetStatus(lrStatus) - } - for _, resAndSDKLR := range matchedResAndSDKLRs { - lsStatus, err := s.lrManager.Update(ctx, resAndSDKLR.resLR, resAndSDKLR.sdkLR) - if err != nil { - return err - } - resAndSDKLR.resLR.SetStatus(lsStatus) - } - return nil -} - -// findSDKListenersRulesOnLS returns the listenerRules configured on Listener. -func (s *listenerRuleSynthesizer) findSDKListenersRulesOnLS(ctx context.Context, lsARN string) ([]ListenerRuleWithTags, error) { - sdkLRs, err := s.taggingManager.ListListenerRules(ctx, lsARN) - if err != nil { - return nil, err - } - nonDefaultRules := make([]ListenerRuleWithTags, 0, len(sdkLRs)) - for _, rule := range sdkLRs { - if awssdk.BoolValue(rule.ListenerRule.IsDefault) { - continue - } - nonDefaultRules = append(nonDefaultRules, rule) - } - return nonDefaultRules, nil -} - -type resAndSDKListenerRulePair struct { - resLR *elbv2model.ListenerRule - sdkLR ListenerRuleWithTags -} - -func matchResAndSDKListenerRules(resLRs []*elbv2model.ListenerRule, sdkLRs []ListenerRuleWithTags) ([]resAndSDKListenerRulePair, []*elbv2model.ListenerRule, []ListenerRuleWithTags) { - var matchedResAndSDKLRs []resAndSDKListenerRulePair - var unmatchedResLRs []*elbv2model.ListenerRule - var unmatchedSDKLRs []ListenerRuleWithTags - - resLRByPriority := mapResListenerRuleByPriority(resLRs) - sdkLRByPriority := mapSDKListenerRuleByPriority(sdkLRs) - resLRPriorities := sets.Int64KeySet(resLRByPriority) - sdkLRPriorities := sets.Int64KeySet(sdkLRByPriority) - for _, priority := range resLRPriorities.Intersection(sdkLRPriorities).List() { - resLR := resLRByPriority[priority] - sdkLR := sdkLRByPriority[priority] - matchedResAndSDKLRs = append(matchedResAndSDKLRs, resAndSDKListenerRulePair{ - resLR: resLR, - sdkLR: sdkLR, - }) - } - for _, priority := range resLRPriorities.Difference(sdkLRPriorities).List() { - unmatchedResLRs = append(unmatchedResLRs, resLRByPriority[priority]) - } - for _, priority := range sdkLRPriorities.Difference(resLRPriorities).List() { - unmatchedSDKLRs = append(unmatchedSDKLRs, sdkLRByPriority[priority]) - } - - return matchedResAndSDKLRs, unmatchedResLRs, unmatchedSDKLRs -} - -func mapResListenerRuleByPriority(resLRs []*elbv2model.ListenerRule) map[int64]*elbv2model.ListenerRule { - resLRByPriority := make(map[int64]*elbv2model.ListenerRule, len(resLRs)) - for _, resLR := range resLRs { - resLRByPriority[resLR.Spec.Priority] = resLR - } - return resLRByPriority -} - -func mapSDKListenerRuleByPriority(sdkLRs []ListenerRuleWithTags) map[int64]ListenerRuleWithTags { - sdkLRByPriority := make(map[int64]ListenerRuleWithTags, len(sdkLRs)) - for _, sdkLR := range sdkLRs { - priority, _ := strconv.ParseInt(awssdk.StringValue(sdkLR.ListenerRule.Priority), 10, 64) - sdkLRByPriority[priority] = sdkLR - } - return sdkLRByPriority -} - -func mapResListenerRuleByListenerARN(resLRs []*elbv2model.ListenerRule) (map[string][]*elbv2model.ListenerRule, error) { - resLRsByLSARN := make(map[string][]*elbv2model.ListenerRule, len(resLRs)) - ctx := context.Background() - for _, lr := range resLRs { - lsARN, err := lr.Spec.ListenerARN.Resolve(ctx) - if err != nil { - return nil, err - } - resLRsByLSARN[lsARN] = append(resLRsByLSARN[lsARN], lr) - } - return resLRsByLSARN, nil -} diff --git a/pkg/deploy/elbv2/listener_synthesizer.go b/pkg/deploy/elbv2/listener_synthesizer.go deleted file mode 100644 index 99239b4de..000000000 --- a/pkg/deploy/elbv2/listener_synthesizer.go +++ /dev/null @@ -1,145 +0,0 @@ -package elbv2 - -import ( - "context" - awssdk "github.com/aws/aws-sdk-go/aws" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/util/sets" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" -) - -func NewListenerSynthesizer(elbv2Client services.ELBV2, taggingManager TaggingManager, - lsManager ListenerManager, logger logr.Logger, stack core.Stack) *listenerSynthesizer { - return &listenerSynthesizer{ - elbv2Client: elbv2Client, - lsManager: lsManager, - logger: logger, - taggingManager: taggingManager, - stack: stack, - } -} - -type listenerSynthesizer struct { - elbv2Client services.ELBV2 - lsManager ListenerManager - logger logr.Logger - taggingManager TaggingManager - - stack core.Stack -} - -func (s *listenerSynthesizer) Synthesize(ctx context.Context) error { - var resLSs []*elbv2model.Listener - s.stack.ListResources(&resLSs) - resLSsByLBARN, err := mapResListenerByLoadBalancerARN(resLSs) - if err != nil { - return err - } - - for lbARN, resLSs := range resLSsByLBARN { - if err := s.synthesizeListenersOnLB(ctx, lbARN, resLSs); err != nil { - return err - } - } - return nil -} - -func (s *listenerSynthesizer) PostSynthesize(ctx context.Context) error { - // nothing to do here. - return nil -} - -func (s *listenerSynthesizer) synthesizeListenersOnLB(ctx context.Context, lbARN string, resLSs []*elbv2model.Listener) error { - sdkLSs, err := s.findSDKListenersOnLB(ctx, lbARN) - if err != nil { - return err - } - matchedResAndSDKLSs, unmatchedResLSs, unmatchedSDKLSs := matchResAndSDKListeners(resLSs, sdkLSs) - for _, sdkLS := range unmatchedSDKLSs { - if err := s.lsManager.Delete(ctx, sdkLS); err != nil { - return err - } - } - for _, resLS := range unmatchedResLSs { - lsStatus, err := s.lsManager.Create(ctx, resLS) - if err != nil { - return err - } - resLS.SetStatus(lsStatus) - } - for _, resAndSDKLS := range matchedResAndSDKLSs { - lsStatus, err := s.lsManager.Update(ctx, resAndSDKLS.resLS, resAndSDKLS.sdkLS) - if err != nil { - return err - } - resAndSDKLS.resLS.SetStatus(lsStatus) - } - return nil -} - -// findSDKListenersOnLB returns the listeners configured on LoadBalancer. -func (s *listenerSynthesizer) findSDKListenersOnLB(ctx context.Context, lbARN string) ([]ListenerWithTags, error) { - return s.taggingManager.ListListeners(ctx, lbARN) -} - -type resAndSDKListenerPair struct { - resLS *elbv2model.Listener - sdkLS ListenerWithTags -} - -func matchResAndSDKListeners(resLSs []*elbv2model.Listener, sdkLSs []ListenerWithTags) ([]resAndSDKListenerPair, []*elbv2model.Listener, []ListenerWithTags) { - var matchedResAndSDKLSs []resAndSDKListenerPair - var unmatchedResLSs []*elbv2model.Listener - var unmatchedSDKLSs []ListenerWithTags - - resLSByPort := mapResListenerByPort(resLSs) - sdkLSByPort := mapSDKListenerByPort(sdkLSs) - resLSPorts := sets.Int64KeySet(resLSByPort) - sdkLSPorts := sets.Int64KeySet(sdkLSByPort) - for _, port := range resLSPorts.Intersection(sdkLSPorts).List() { - resLS := resLSByPort[port] - sdkLS := sdkLSByPort[port] - matchedResAndSDKLSs = append(matchedResAndSDKLSs, resAndSDKListenerPair{ - resLS: resLS, - sdkLS: sdkLS, - }) - } - for _, port := range resLSPorts.Difference(sdkLSPorts).List() { - unmatchedResLSs = append(unmatchedResLSs, resLSByPort[port]) - } - for _, port := range sdkLSPorts.Difference(resLSPorts).List() { - unmatchedSDKLSs = append(unmatchedSDKLSs, sdkLSByPort[port]) - } - return matchedResAndSDKLSs, unmatchedResLSs, unmatchedSDKLSs -} - -func mapResListenerByPort(resLSs []*elbv2model.Listener) map[int64]*elbv2model.Listener { - resLSByPort := make(map[int64]*elbv2model.Listener, len(resLSs)) - for _, ls := range resLSs { - resLSByPort[ls.Spec.Port] = ls - } - return resLSByPort -} - -func mapSDKListenerByPort(sdkLSs []ListenerWithTags) map[int64]ListenerWithTags { - sdkLSByPort := make(map[int64]ListenerWithTags, len(sdkLSs)) - for _, ls := range sdkLSs { - sdkLSByPort[awssdk.Int64Value(ls.Listener.Port)] = ls - } - return sdkLSByPort -} - -func mapResListenerByLoadBalancerARN(resLSs []*elbv2model.Listener) (map[string][]*elbv2model.Listener, error) { - resLSsByLBARN := make(map[string][]*elbv2model.Listener, len(resLSs)) - ctx := context.Background() - for _, ls := range resLSs { - lbARN, err := ls.Spec.LoadBalancerARN.Resolve(ctx) - if err != nil { - return nil, err - } - resLSsByLBARN[lbARN] = append(resLSsByLBARN[lbARN], ls) - } - return resLSsByLBARN, nil -} diff --git a/pkg/deploy/elbv2/listener_utils.go b/pkg/deploy/elbv2/listener_utils.go deleted file mode 100644 index 8cec7bad9..000000000 --- a/pkg/deploy/elbv2/listener_utils.go +++ /dev/null @@ -1,227 +0,0 @@ -package elbv2 - -import ( - "context" - awssdk "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/pkg/errors" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/config" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" - "time" -) - -const ( - defaultWaitLSExistencePollInterval = 2 * time.Second - defaultWaitLSExistenceTimeout = 20 * time.Second -) - -func buildSDKActions(modelActions []elbv2model.Action, featureGates config.FeatureGates) ([]*elbv2sdk.Action, error) { - var sdkActions []*elbv2sdk.Action - if len(modelActions) != 0 { - sdkActions = make([]*elbv2sdk.Action, 0, len(modelActions)) - for index, modelAction := range modelActions { - sdkAction, err := buildSDKAction(modelAction, featureGates) - if err != nil { - return nil, err - } - sdkAction.Order = awssdk.Int64(int64(index) + 1) - sdkActions = append(sdkActions, sdkAction) - } - } - return sdkActions, nil -} - -func buildSDKAction(modelAction elbv2model.Action, featureGates config.FeatureGates) (*elbv2sdk.Action, error) { - sdkObj := &elbv2sdk.Action{} - sdkObj.Type = awssdk.String(string(modelAction.Type)) - if modelAction.AuthenticateCognitoConfig != nil { - sdkObj.AuthenticateCognitoConfig = buildSDKAuthenticateCognitoActionConfig(*modelAction.AuthenticateCognitoConfig) - } - if modelAction.AuthenticateOIDCConfig != nil { - sdkObj.AuthenticateOidcConfig = buildSDKAuthenticateOidcActionConfig(*modelAction.AuthenticateOIDCConfig) - } - if modelAction.FixedResponseConfig != nil { - sdkObj.FixedResponseConfig = buildSDKFixedResponseActionConfig(*modelAction.FixedResponseConfig) - } - if modelAction.RedirectConfig != nil { - sdkObj.RedirectConfig = buildSDKRedirectActionConfig(*modelAction.RedirectConfig) - } - if modelAction.ForwardConfig != nil { - forwardConfig, err := buildSDKForwardActionConfig(*modelAction.ForwardConfig) - if err != nil { - return nil, err - } - if !featureGates.Enabled(config.WeightedTargetGroups) { - if len(forwardConfig.TargetGroups) == 1 { - sdkObj.TargetGroupArn = forwardConfig.TargetGroups[0].TargetGroupArn - } else { - return nil, errors.New("weighted target groups feature is disabled") - } - } else { - sdkObj.ForwardConfig = forwardConfig - } - } - return sdkObj, nil -} - -func buildSDKAuthenticateCognitoActionConfig(modelCfg elbv2model.AuthenticateCognitoActionConfig) *elbv2sdk.AuthenticateCognitoActionConfig { - return &elbv2sdk.AuthenticateCognitoActionConfig{ - AuthenticationRequestExtraParams: awssdk.StringMap(modelCfg.AuthenticationRequestExtraParams), - OnUnauthenticatedRequest: (*string)(modelCfg.OnUnauthenticatedRequest), - Scope: modelCfg.Scope, - SessionCookieName: modelCfg.SessionCookieName, - SessionTimeout: modelCfg.SessionTimeout, - UserPoolArn: awssdk.String(modelCfg.UserPoolARN), - UserPoolClientId: awssdk.String(modelCfg.UserPoolClientID), - UserPoolDomain: awssdk.String(modelCfg.UserPoolDomain), - } -} - -func buildSDKAuthenticateOidcActionConfig(modelCfg elbv2model.AuthenticateOIDCActionConfig) *elbv2sdk.AuthenticateOidcActionConfig { - return &elbv2sdk.AuthenticateOidcActionConfig{ - AuthenticationRequestExtraParams: awssdk.StringMap(modelCfg.AuthenticationRequestExtraParams), - OnUnauthenticatedRequest: (*string)(modelCfg.OnUnauthenticatedRequest), - Scope: modelCfg.Scope, - SessionCookieName: modelCfg.SessionCookieName, - SessionTimeout: modelCfg.SessionTimeout, - ClientId: awssdk.String(modelCfg.ClientID), - ClientSecret: awssdk.String(modelCfg.ClientSecret), - Issuer: awssdk.String(modelCfg.Issuer), - AuthorizationEndpoint: awssdk.String(modelCfg.AuthorizationEndpoint), - TokenEndpoint: awssdk.String(modelCfg.TokenEndpoint), - UserInfoEndpoint: awssdk.String(modelCfg.UserInfoEndpoint), - } -} - -func buildSDKFixedResponseActionConfig(modelCfg elbv2model.FixedResponseActionConfig) *elbv2sdk.FixedResponseActionConfig { - return &elbv2sdk.FixedResponseActionConfig{ - ContentType: modelCfg.ContentType, - MessageBody: modelCfg.MessageBody, - StatusCode: awssdk.String(modelCfg.StatusCode), - } -} - -func buildSDKRedirectActionConfig(modelCfg elbv2model.RedirectActionConfig) *elbv2sdk.RedirectActionConfig { - return &elbv2sdk.RedirectActionConfig{ - Host: modelCfg.Host, - Path: modelCfg.Path, - Port: modelCfg.Port, - Protocol: modelCfg.Protocol, - Query: modelCfg.Query, - StatusCode: awssdk.String(modelCfg.StatusCode), - } -} - -func buildSDKForwardActionConfig(modelCfg elbv2model.ForwardActionConfig) (*elbv2sdk.ForwardActionConfig, error) { - ctx := context.Background() - sdkObj := &elbv2sdk.ForwardActionConfig{} - var tgTuples []*elbv2sdk.TargetGroupTuple - for _, tgt := range modelCfg.TargetGroups { - tgARN, err := tgt.TargetGroupARN.Resolve(ctx) - if err != nil { - return nil, err - } - tgTuples = append(tgTuples, &elbv2sdk.TargetGroupTuple{ - TargetGroupArn: awssdk.String(tgARN), - Weight: tgt.Weight, - }) - } - sdkObj.TargetGroups = tgTuples - if modelCfg.TargetGroupStickinessConfig != nil { - sdkObj.TargetGroupStickinessConfig = &elbv2sdk.TargetGroupStickinessConfig{ - DurationSeconds: modelCfg.TargetGroupStickinessConfig.DurationSeconds, - Enabled: modelCfg.TargetGroupStickinessConfig.Enabled, - } - } - - return sdkObj, nil -} - -func buildSDKRuleConditions(modelConditions []elbv2model.RuleCondition) []*elbv2sdk.RuleCondition { - var sdkConditions []*elbv2sdk.RuleCondition - if len(modelConditions) != 0 { - sdkConditions = make([]*elbv2sdk.RuleCondition, 0, len(modelConditions)) - for _, modelCondition := range modelConditions { - sdkCondition := buildSDKRuleCondition(modelCondition) - sdkConditions = append(sdkConditions, sdkCondition) - } - } - return sdkConditions -} - -func buildSDKRuleCondition(modelCondition elbv2model.RuleCondition) *elbv2sdk.RuleCondition { - sdkObj := &elbv2sdk.RuleCondition{} - sdkObj.Field = awssdk.String(string(modelCondition.Field)) - if modelCondition.HostHeaderConfig != nil { - sdkObj.HostHeaderConfig = buildSDKHostHeaderConditionConfig(*modelCondition.HostHeaderConfig) - } - if modelCondition.HTTPHeaderConfig != nil { - sdkObj.HttpHeaderConfig = buildSDKHTTPHeaderConditionConfig(*modelCondition.HTTPHeaderConfig) - } - if modelCondition.HTTPRequestMethodConfig != nil { - sdkObj.HttpRequestMethodConfig = buildSDKHTTPRequestMethodConditionConfig(*modelCondition.HTTPRequestMethodConfig) - } - if modelCondition.PathPatternConfig != nil { - sdkObj.PathPatternConfig = buildSDKPathPatternConditionConfig(*modelCondition.PathPatternConfig) - } - if modelCondition.QueryStringConfig != nil { - sdkObj.QueryStringConfig = buildSDKQueryStringConditionConfig(*modelCondition.QueryStringConfig) - } - if modelCondition.SourceIPConfig != nil { - sdkObj.SourceIpConfig = buildSDKSourceIpConditionConfig(*modelCondition.SourceIPConfig) - } - return sdkObj -} - -func buildSDKHostHeaderConditionConfig(modelCfg elbv2model.HostHeaderConditionConfig) *elbv2sdk.HostHeaderConditionConfig { - return &elbv2sdk.HostHeaderConditionConfig{ - Values: awssdk.StringSlice(modelCfg.Values), - } -} - -func buildSDKHTTPHeaderConditionConfig(modelCfg elbv2model.HTTPHeaderConditionConfig) *elbv2sdk.HttpHeaderConditionConfig { - return &elbv2sdk.HttpHeaderConditionConfig{ - HttpHeaderName: awssdk.String(modelCfg.HTTPHeaderName), - Values: awssdk.StringSlice(modelCfg.Values), - } -} - -func buildSDKHTTPRequestMethodConditionConfig(modelCfg elbv2model.HTTPRequestMethodConditionConfig) *elbv2sdk.HttpRequestMethodConditionConfig { - return &elbv2sdk.HttpRequestMethodConditionConfig{ - Values: awssdk.StringSlice(modelCfg.Values), - } -} - -func buildSDKPathPatternConditionConfig(modelCfg elbv2model.PathPatternConditionConfig) *elbv2sdk.PathPatternConditionConfig { - return &elbv2sdk.PathPatternConditionConfig{ - Values: awssdk.StringSlice(modelCfg.Values), - } -} - -func buildSDKQueryStringConditionConfig(modelCfg elbv2model.QueryStringConditionConfig) *elbv2sdk.QueryStringConditionConfig { - kvPairs := make([]*elbv2sdk.QueryStringKeyValuePair, 0, len(modelCfg.Values)) - for _, value := range modelCfg.Values { - kvPairs = append(kvPairs, &elbv2sdk.QueryStringKeyValuePair{ - Key: value.Key, - Value: awssdk.String(value.Value), - }) - } - return &elbv2sdk.QueryStringConditionConfig{ - Values: kvPairs, - } -} - -func buildSDKSourceIpConditionConfig(modelCfg elbv2model.SourceIPConditionConfig) *elbv2sdk.SourceIpConditionConfig { - return &elbv2sdk.SourceIpConditionConfig{ - Values: awssdk.StringSlice(modelCfg.Values), - } -} - -func isListenerNotFoundError(err error) bool { - var awsErr awserr.Error - if errors.As(err, &awsErr) { - return awsErr.Code() == "ListenerNotFound" - } - return false -} diff --git a/pkg/deploy/elbv2/listener_utils_test.go b/pkg/deploy/elbv2/listener_utils_test.go deleted file mode 100644 index f6a8d4ce7..000000000 --- a/pkg/deploy/elbv2/listener_utils_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package elbv2 - -import ( - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "testing" -) - -func Test_isListenerNotFoundError(t *testing.T) { - type args struct { - err error - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "is ListenerNotFound error", - args: args{ - err: awserr.New("ListenerNotFound", "some message", nil), - }, - want: true, - }, - { - name: "wraps ListenerNotFound error", - args: args{ - err: errors.Wrap(awserr.New("ListenerNotFound", "some message", nil), "wrapped message"), - }, - want: true, - }, - { - name: "isn't ListenerNotFound error", - args: args{ - err: errors.New("some other error"), - }, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := isListenerNotFoundError(tt.args.err) - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/deploy/elbv2/load_balancer_attributes_reconciler.go b/pkg/deploy/elbv2/load_balancer_attributes_reconciler.go deleted file mode 100644 index e8180de90..000000000 --- a/pkg/deploy/elbv2/load_balancer_attributes_reconciler.go +++ /dev/null @@ -1,94 +0,0 @@ -package elbv2 - -import ( - "context" - awssdk "github.com/aws/aws-sdk-go/aws" - elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/util/sets" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/algorithm" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" -) - -// reconciler for LoadBalancer attributes -type LoadBalancerAttributeReconciler interface { - // Reconcile loadBalancer attributes - Reconcile(ctx context.Context, resLB *elbv2model.LoadBalancer, sdkLB LoadBalancerWithTags) error -} - -// NewDefaultLoadBalancerAttributeReconciler constructs new defaultLoadBalancerAttributeReconciler. -func NewDefaultLoadBalancerAttributeReconciler(elbv2Client services.ELBV2, logger logr.Logger) *defaultLoadBalancerAttributeReconciler { - return &defaultLoadBalancerAttributeReconciler{ - elbv2Client: elbv2Client, - logger: logger, - } -} - -var _ LoadBalancerAttributeReconciler = &defaultLoadBalancerAttributeReconciler{} - -// default implementation for LoadBalancerAttributeReconciler -type defaultLoadBalancerAttributeReconciler struct { - elbv2Client services.ELBV2 - logger logr.Logger -} - -func (r *defaultLoadBalancerAttributeReconciler) Reconcile(ctx context.Context, resLB *elbv2model.LoadBalancer, sdkLB LoadBalancerWithTags) error { - desiredAttrs := r.getDesiredLoadBalancerAttributes(ctx, resLB) - currentAttrs, err := r.getCurrentLoadBalancerAttributes(ctx, sdkLB) - if err != nil { - return err - } - - attributesToUpdate, _ := algorithm.DiffStringMap(desiredAttrs, currentAttrs) - if len(attributesToUpdate) > 0 { - req := &elbv2sdk.ModifyLoadBalancerAttributesInput{ - LoadBalancerArn: sdkLB.LoadBalancer.LoadBalancerArn, - Attributes: nil, - } - for _, attrKey := range sets.StringKeySet(attributesToUpdate).List() { - req.Attributes = append(req.Attributes, &elbv2sdk.LoadBalancerAttribute{ - Key: awssdk.String(attrKey), - Value: awssdk.String(attributesToUpdate[attrKey]), - }) - } - - r.logger.Info("modifying loadBalancer attributes", - "stackID", resLB.Stack().StackID(), - "resourceID", resLB.ID(), - "arn", awssdk.StringValue(sdkLB.LoadBalancer.LoadBalancerArn), - "change", attributesToUpdate) - if _, err := r.elbv2Client.ModifyLoadBalancerAttributesWithContext(ctx, req); err != nil { - return err - } - r.logger.Info("modified loadBalancer attributes", - "stackID", resLB.Stack().StackID(), - "resourceID", resLB.ID(), - "arn", awssdk.StringValue(sdkLB.LoadBalancer.LoadBalancerArn)) - } - return nil -} - -func (r *defaultLoadBalancerAttributeReconciler) getDesiredLoadBalancerAttributes(ctx context.Context, resLB *elbv2model.LoadBalancer) map[string]string { - lbAttributes := make(map[string]string, len(resLB.Spec.LoadBalancerAttributes)) - for _, attr := range resLB.Spec.LoadBalancerAttributes { - lbAttributes[attr.Key] = attr.Value - } - return lbAttributes -} - -func (r *defaultLoadBalancerAttributeReconciler) getCurrentLoadBalancerAttributes(ctx context.Context, sdkLB LoadBalancerWithTags) (map[string]string, error) { - req := &elbv2sdk.DescribeLoadBalancerAttributesInput{ - LoadBalancerArn: sdkLB.LoadBalancer.LoadBalancerArn, - } - resp, err := r.elbv2Client.DescribeLoadBalancerAttributesWithContext(ctx, req) - if err != nil { - return nil, err - } - - lbAttributes := make(map[string]string, len(resp.Attributes)) - for _, attr := range resp.Attributes { - lbAttributes[awssdk.StringValue(attr.Key)] = awssdk.StringValue(attr.Value) - } - return lbAttributes, nil -} diff --git a/pkg/deploy/elbv2/load_balancer_attributes_reconciler_test.go b/pkg/deploy/elbv2/load_balancer_attributes_reconciler_test.go deleted file mode 100644 index 55976f0cc..000000000 --- a/pkg/deploy/elbv2/load_balancer_attributes_reconciler_test.go +++ /dev/null @@ -1,332 +0,0 @@ -package elbv2 - -import ( - "context" - "testing" - - awssdk "github.com/aws/aws-sdk-go/aws" - elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/go-logr/logr" - "github.com/golang/mock/gomock" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - coremodel "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" - "sigs.k8s.io/controller-runtime/pkg/log" -) - -func Test_defaultLoadBalancerAttributeReconciler_updateSDKLoadBalancerWithAttributes(t *testing.T) { - type describeLoadBalancerAttributesWithContextCall struct { - req *elbv2sdk.DescribeLoadBalancerAttributesInput - resp *elbv2sdk.DescribeLoadBalancerAttributesOutput - err error - } - - type modifyLoadBalancerAttributesWithContextCall struct { - req *elbv2sdk.ModifyLoadBalancerAttributesInput - resp *elbv2sdk.ModifyLoadBalancerAttributesOutput - err error - } - - type fields struct { - describeLoadBalancerAttributesWithContextCalls []describeLoadBalancerAttributesWithContextCall - modifyLoadBalancerAttributesWithContextCalls []modifyLoadBalancerAttributesWithContextCall - } - type args struct { - sdkLB LoadBalancerWithTags - resLB *elbv2model.LoadBalancer - } - - stack := coremodel.NewDefaultStack(coremodel.StackID{Namespace: "namespace", Name: "name"}) - tests := []struct { - name string - fields fields - args args - wantErr error - }{ - { - name: "multiple attributes should be updated", - fields: fields{ - describeLoadBalancerAttributesWithContextCalls: []describeLoadBalancerAttributesWithContextCall{ - { - req: &elbv2sdk.DescribeLoadBalancerAttributesInput{ - LoadBalancerArn: awssdk.String("my-arn"), - }, - resp: &elbv2sdk.DescribeLoadBalancerAttributesOutput{ - Attributes: []*elbv2sdk.LoadBalancerAttribute{ - { - Key: awssdk.String("idle_timeout.timeout_seconds"), - Value: awssdk.String("50"), - }, - { - Key: awssdk.String("load_balancing.cross_zone.enabled"), - Value: awssdk.String("false"), - }, - }, - }, - }, - }, - modifyLoadBalancerAttributesWithContextCalls: []modifyLoadBalancerAttributesWithContextCall{ - { - req: &elbv2sdk.ModifyLoadBalancerAttributesInput{ - LoadBalancerArn: awssdk.String("my-arn"), - Attributes: []*elbv2sdk.LoadBalancerAttribute{ - { - Key: awssdk.String("idle_timeout.timeout_seconds"), - Value: awssdk.String("100"), - }, - { - Key: awssdk.String("load_balancing.cross_zone.enabled"), - Value: awssdk.String("true"), - }, - }, - }, - }, - }, - }, - args: args{ - sdkLB: LoadBalancerWithTags{ - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("my-arn"), - }, - }, - resLB: &elbv2model.LoadBalancer{ - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::LoadBalancer", "id-1"), - Spec: elbv2model.LoadBalancerSpec{ - LoadBalancerAttributes: []elbv2model.LoadBalancerAttribute{ - { - Key: "idle_timeout.timeout_seconds", - Value: "100", - }, - { - Key: "load_balancing.cross_zone.enabled", - Value: "true", - }, - }, - }, - }, - }, - }, - { - name: "no attributes should be updated", - fields: fields{ - describeLoadBalancerAttributesWithContextCalls: []describeLoadBalancerAttributesWithContextCall{ - { - req: &elbv2sdk.DescribeLoadBalancerAttributesInput{ - LoadBalancerArn: awssdk.String("my-arn"), - }, - resp: &elbv2sdk.DescribeLoadBalancerAttributesOutput{ - Attributes: []*elbv2sdk.LoadBalancerAttribute{ - { - Key: awssdk.String("idle_timeout.timeout_seconds"), - Value: awssdk.String("50"), - }, - { - Key: awssdk.String("load_balancing.cross_zone.enabled"), - Value: awssdk.String("false"), - }, - }, - }, - }, - }, - modifyLoadBalancerAttributesWithContextCalls: nil, - }, - args: args{ - sdkLB: LoadBalancerWithTags{ - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("my-arn"), - }, - }, - resLB: &elbv2model.LoadBalancer{ - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::LoadBalancer", "id-1"), - Spec: elbv2model.LoadBalancerSpec{ - LoadBalancerAttributes: []elbv2model.LoadBalancerAttribute{ - { - Key: "idle_timeout.timeout_seconds", - Value: "50", - }, - }, - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - elbv2Client := services.NewMockELBV2(ctrl) - for _, call := range tt.fields.describeLoadBalancerAttributesWithContextCalls { - elbv2Client.EXPECT().DescribeLoadBalancerAttributesWithContext(gomock.Any(), call.req).Return(call.resp, call.err) - } - for _, call := range tt.fields.modifyLoadBalancerAttributesWithContextCalls { - elbv2Client.EXPECT().ModifyLoadBalancerAttributesWithContext(gomock.Any(), call.req).Return(call.resp, call.err) - } - r := &defaultLoadBalancerAttributeReconciler{ - elbv2Client: elbv2Client, - logger: logr.New(&log.NullLogSink{}), - } - err := r.Reconcile(context.Background(), tt.args.resLB, tt.args.sdkLB) - if tt.wantErr != nil { - assert.EqualError(t, err, tt.wantErr.Error()) - } else { - assert.NoError(t, err) - } - }) - } -} - -func Test_defaultLoadBalancerAttributeReconciler_getDesiredLoadBalancerAttributes(t *testing.T) { - type args struct { - resLB *elbv2model.LoadBalancer - } - tests := []struct { - name string - args args - want map[string]string - }{ - { - name: "standard case", - args: args{ - resLB: &elbv2model.LoadBalancer{ - Spec: elbv2model.LoadBalancerSpec{ - LoadBalancerAttributes: []elbv2model.LoadBalancerAttribute{ - { - Key: "keyA", - Value: "valueA", - }, - { - Key: "keyB", - Value: "valueB", - }, - }, - }, - }, - }, - want: map[string]string{ - "keyA": "valueA", - "keyB": "valueB", - }, - }, - { - name: "nil attributes case", - args: args{ - resLB: &elbv2model.LoadBalancer{ - Spec: elbv2model.LoadBalancerSpec{ - LoadBalancerAttributes: nil, - }, - }, - }, - want: map[string]string{}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := &defaultLoadBalancerAttributeReconciler{} - got := r.getDesiredLoadBalancerAttributes(context.Background(), tt.args.resLB) - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_defaultLoadBalancerAttributeReconciler_getCurrentLoadBalancerAttributes(t *testing.T) { - type describeLoadBalancerAttributesWithContextCall struct { - req *elbv2sdk.DescribeLoadBalancerAttributesInput - resp *elbv2sdk.DescribeLoadBalancerAttributesOutput - err error - } - type fields struct { - describeLoadBalancerAttributesWithContextCalls []describeLoadBalancerAttributesWithContextCall - } - type args struct { - sdkLB LoadBalancerWithTags - } - tests := []struct { - name string - fields fields - args args - want map[string]string - wantErr error - }{ - { - name: "standard case", - fields: fields{ - describeLoadBalancerAttributesWithContextCalls: []describeLoadBalancerAttributesWithContextCall{ - { - req: &elbv2sdk.DescribeLoadBalancerAttributesInput{ - LoadBalancerArn: awssdk.String("my-arn"), - }, - resp: &elbv2sdk.DescribeLoadBalancerAttributesOutput{ - Attributes: []*elbv2sdk.LoadBalancerAttribute{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB"), - }, - }, - }, - }, - }, - }, - args: args{ - sdkLB: LoadBalancerWithTags{ - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("my-arn"), - }, - Tags: nil, - }, - }, - want: map[string]string{ - "keyA": "valueA", - "keyB": "valueB", - }, - }, - { - name: "error case", - fields: fields{ - describeLoadBalancerAttributesWithContextCalls: []describeLoadBalancerAttributesWithContextCall{ - { - req: &elbv2sdk.DescribeLoadBalancerAttributesInput{ - LoadBalancerArn: awssdk.String("my-arn"), - }, - err: errors.New("some error"), - }, - }, - }, - args: args{ - sdkLB: LoadBalancerWithTags{ - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("my-arn"), - }, - Tags: nil, - }, - }, - wantErr: errors.New("some error"), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - elbv2Client := services.NewMockELBV2(ctrl) - for _, call := range tt.fields.describeLoadBalancerAttributesWithContextCalls { - elbv2Client.EXPECT().DescribeLoadBalancerAttributesWithContext(gomock.Any(), call.req).Return(call.resp, call.err) - } - - r := &defaultLoadBalancerAttributeReconciler{ - elbv2Client: elbv2Client, - } - got, err := r.getCurrentLoadBalancerAttributes(context.Background(), tt.args.sdkLB) - if tt.wantErr != nil { - assert.EqualError(t, err, tt.wantErr.Error()) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.want, got) - } - }) - } -} diff --git a/pkg/deploy/elbv2/load_balancer_manager.go b/pkg/deploy/elbv2/load_balancer_manager.go deleted file mode 100644 index 515334793..000000000 --- a/pkg/deploy/elbv2/load_balancer_manager.go +++ /dev/null @@ -1,300 +0,0 @@ -package elbv2 - -import ( - "context" - "fmt" - awssdk "github.com/aws/aws-sdk-go/aws" - elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/util/sets" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/tracking" - coremodel "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" -) - -// LoadBalancerManager is responsible for create/update/delete LoadBalancer resources. -type LoadBalancerManager interface { - Create(ctx context.Context, resLB *elbv2model.LoadBalancer) (elbv2model.LoadBalancerStatus, error) - - Update(ctx context.Context, resLB *elbv2model.LoadBalancer, sdkLB LoadBalancerWithTags) (elbv2model.LoadBalancerStatus, error) - - Delete(ctx context.Context, sdkLB LoadBalancerWithTags) error -} - -// NewDefaultLoadBalancerManager constructs new defaultLoadBalancerManager. -func NewDefaultLoadBalancerManager(elbv2Client services.ELBV2, trackingProvider tracking.Provider, - taggingManager TaggingManager, externalManagedTags []string, logger logr.Logger) *defaultLoadBalancerManager { - return &defaultLoadBalancerManager{ - elbv2Client: elbv2Client, - trackingProvider: trackingProvider, - taggingManager: taggingManager, - attributesReconciler: NewDefaultLoadBalancerAttributeReconciler(elbv2Client, logger), - externalManagedTags: externalManagedTags, - logger: logger, - } -} - -var _ LoadBalancerManager = &defaultLoadBalancerManager{} - -// defaultLoadBalancerManager implement LoadBalancerManager -type defaultLoadBalancerManager struct { - elbv2Client services.ELBV2 - trackingProvider tracking.Provider - taggingManager TaggingManager - attributesReconciler LoadBalancerAttributeReconciler - externalManagedTags []string - - logger logr.Logger -} - -func (m *defaultLoadBalancerManager) Create(ctx context.Context, resLB *elbv2model.LoadBalancer) (elbv2model.LoadBalancerStatus, error) { - req, err := buildSDKCreateLoadBalancerInput(resLB.Spec) - if err != nil { - return elbv2model.LoadBalancerStatus{}, err - } - lbTags := m.trackingProvider.ResourceTags(resLB.Stack(), resLB, resLB.Spec.Tags) - req.Tags = convertTagsToSDKTags(lbTags) - - m.logger.Info("creating loadBalancer", - "stackID", resLB.Stack().StackID(), - "resourceID", resLB.ID()) - resp, err := m.elbv2Client.CreateLoadBalancerWithContext(ctx, req) - if err != nil { - return elbv2model.LoadBalancerStatus{}, err - } - sdkLB := LoadBalancerWithTags{ - LoadBalancer: resp.LoadBalancers[0], - Tags: lbTags, - } - m.logger.Info("created loadBalancer", - "stackID", resLB.Stack().StackID(), - "resourceID", resLB.ID(), - "arn", awssdk.StringValue(sdkLB.LoadBalancer.LoadBalancerArn)) - if err := m.attributesReconciler.Reconcile(ctx, resLB, sdkLB); err != nil { - return elbv2model.LoadBalancerStatus{}, err - } - - return buildResLoadBalancerStatus(sdkLB), nil -} - -func (m *defaultLoadBalancerManager) Update(ctx context.Context, resLB *elbv2model.LoadBalancer, sdkLB LoadBalancerWithTags) (elbv2model.LoadBalancerStatus, error) { - if err := m.updateSDKLoadBalancerWithTags(ctx, resLB, sdkLB); err != nil { - return elbv2model.LoadBalancerStatus{}, err - } - if err := m.updateSDKLoadBalancerWithSecurityGroups(ctx, resLB, sdkLB); err != nil { - return elbv2model.LoadBalancerStatus{}, err - } - if err := m.updateSDKLoadBalancerWithSubnetMappings(ctx, resLB, sdkLB); err != nil { - return elbv2model.LoadBalancerStatus{}, err - } - if err := m.updateSDKLoadBalancerWithIPAddressType(ctx, resLB, sdkLB); err != nil { - return elbv2model.LoadBalancerStatus{}, err - } - if err := m.attributesReconciler.Reconcile(ctx, resLB, sdkLB); err != nil { - return elbv2model.LoadBalancerStatus{}, err - } - if err := m.checkSDKLoadBalancerWithCOIPv4Pool(ctx, resLB, sdkLB); err != nil { - return elbv2model.LoadBalancerStatus{}, err - } - return buildResLoadBalancerStatus(sdkLB), nil -} - -func (m *defaultLoadBalancerManager) Delete(ctx context.Context, sdkLB LoadBalancerWithTags) error { - req := &elbv2sdk.DeleteLoadBalancerInput{ - LoadBalancerArn: sdkLB.LoadBalancer.LoadBalancerArn, - } - m.logger.Info("deleting loadBalancer", - "arn", awssdk.StringValue(req.LoadBalancerArn)) - if _, err := m.elbv2Client.DeleteLoadBalancerWithContext(ctx, req); err != nil { - return err - } - m.logger.Info("deleted loadBalancer", - "arn", awssdk.StringValue(req.LoadBalancerArn)) - return nil -} - -func (m *defaultLoadBalancerManager) updateSDKLoadBalancerWithIPAddressType(ctx context.Context, resLB *elbv2model.LoadBalancer, sdkLB LoadBalancerWithTags) error { - if resLB.Spec.IPAddressType == nil { - return nil - } - desiredIPAddressType := string(*resLB.Spec.IPAddressType) - currentIPAddressType := awssdk.StringValue(sdkLB.LoadBalancer.IpAddressType) - if desiredIPAddressType == currentIPAddressType { - return nil - } - - req := &elbv2sdk.SetIpAddressTypeInput{ - LoadBalancerArn: sdkLB.LoadBalancer.LoadBalancerArn, - IpAddressType: awssdk.String(desiredIPAddressType), - } - changeDesc := fmt.Sprintf("%v => %v", currentIPAddressType, desiredIPAddressType) - m.logger.Info("modifying loadBalancer ipAddressType", - "stackID", resLB.Stack().StackID(), - "resourceID", resLB.ID(), - "arn", awssdk.StringValue(sdkLB.LoadBalancer.LoadBalancerArn), - "change", changeDesc) - if _, err := m.elbv2Client.SetIpAddressTypeWithContext(ctx, req); err != nil { - return err - } - m.logger.Info("modified loadBalancer ipAddressType", - "stackID", resLB.Stack().StackID(), - "resourceID", resLB.ID(), - "arn", awssdk.StringValue(sdkLB.LoadBalancer.LoadBalancerArn)) - - return nil -} - -func (m *defaultLoadBalancerManager) updateSDKLoadBalancerWithSubnetMappings(ctx context.Context, resLB *elbv2model.LoadBalancer, sdkLB LoadBalancerWithTags) error { - desiredSubnets := sets.NewString() - for _, mapping := range resLB.Spec.SubnetMappings { - desiredSubnets.Insert(mapping.SubnetID) - } - currentSubnets := sets.NewString() - for _, az := range sdkLB.LoadBalancer.AvailabilityZones { - currentSubnets.Insert(awssdk.StringValue(az.SubnetId)) - } - if desiredSubnets.Equal(currentSubnets) { - return nil - } - - req := &elbv2sdk.SetSubnetsInput{ - LoadBalancerArn: sdkLB.LoadBalancer.LoadBalancerArn, - SubnetMappings: buildSDKSubnetMappings(resLB.Spec.SubnetMappings), - } - changeDesc := fmt.Sprintf("%v => %v", currentSubnets.List(), desiredSubnets.List()) - m.logger.Info("modifying loadBalancer subnetMappings", - "stackID", resLB.Stack().StackID(), - "resourceID", resLB.ID(), - "arn", awssdk.StringValue(sdkLB.LoadBalancer.LoadBalancerArn), - "change", changeDesc) - if _, err := m.elbv2Client.SetSubnetsWithContext(ctx, req); err != nil { - return err - } - m.logger.Info("modified loadBalancer subnetMappings", - "stackID", resLB.Stack().StackID(), - "resourceID", resLB.ID(), - "arn", awssdk.StringValue(sdkLB.LoadBalancer.LoadBalancerArn)) - - return nil -} - -func (m *defaultLoadBalancerManager) updateSDKLoadBalancerWithSecurityGroups(ctx context.Context, resLB *elbv2model.LoadBalancer, sdkLB LoadBalancerWithTags) error { - securityGroups, err := buildSDKSecurityGroups(resLB.Spec.SecurityGroups) - if err != nil { - return err - } - desiredSecurityGroups := sets.NewString(awssdk.StringValueSlice(securityGroups)...) - currentSecurityGroups := sets.NewString(awssdk.StringValueSlice(sdkLB.LoadBalancer.SecurityGroups)...) - if desiredSecurityGroups.Equal(currentSecurityGroups) { - return nil - } - - req := &elbv2sdk.SetSecurityGroupsInput{ - LoadBalancerArn: sdkLB.LoadBalancer.LoadBalancerArn, - SecurityGroups: securityGroups, - } - changeDesc := fmt.Sprintf("%v => %v", currentSecurityGroups.List(), desiredSecurityGroups.List()) - m.logger.Info("modifying loadBalancer securityGroups", - "stackID", resLB.Stack().StackID(), - "resourceID", resLB.ID(), - "arn", awssdk.StringValue(sdkLB.LoadBalancer.LoadBalancerArn), - "change", changeDesc) - if _, err := m.elbv2Client.SetSecurityGroupsWithContext(ctx, req); err != nil { - return err - } - m.logger.Info("modified loadBalancer securityGroups", - "stackID", resLB.Stack().StackID(), - "resourceID", resLB.ID(), - "arn", awssdk.StringValue(sdkLB.LoadBalancer.LoadBalancerArn)) - - return nil -} - -func (m *defaultLoadBalancerManager) checkSDKLoadBalancerWithCOIPv4Pool(_ context.Context, resLB *elbv2model.LoadBalancer, sdkLB LoadBalancerWithTags) error { - if awssdk.StringValue(resLB.Spec.CustomerOwnedIPv4Pool) != awssdk.StringValue(sdkLB.LoadBalancer.CustomerOwnedIpv4Pool) { - m.logger.Info("loadBalancer has drifted CustomerOwnedIPv4Pool setting", - "desired", awssdk.StringValue(resLB.Spec.CustomerOwnedIPv4Pool), - "current", awssdk.StringValue(sdkLB.LoadBalancer.CustomerOwnedIpv4Pool)) - } - return nil -} - -func (m *defaultLoadBalancerManager) updateSDKLoadBalancerWithTags(ctx context.Context, resLB *elbv2model.LoadBalancer, sdkLB LoadBalancerWithTags) error { - desiredLBTags := m.trackingProvider.ResourceTags(resLB.Stack(), resLB, resLB.Spec.Tags) - return m.taggingManager.ReconcileTags(ctx, awssdk.StringValue(sdkLB.LoadBalancer.LoadBalancerArn), desiredLBTags, - WithCurrentTags(sdkLB.Tags), - WithIgnoredTagKeys(m.trackingProvider.LegacyTagKeys()), - WithIgnoredTagKeys(m.externalManagedTags)) -} - -func buildSDKCreateLoadBalancerInput(lbSpec elbv2model.LoadBalancerSpec) (*elbv2sdk.CreateLoadBalancerInput, error) { - sdkObj := &elbv2sdk.CreateLoadBalancerInput{} - sdkObj.Name = awssdk.String(lbSpec.Name) - sdkObj.Type = awssdk.String(string(lbSpec.Type)) - - if lbSpec.Scheme != nil { - sdkObj.Scheme = (*string)(lbSpec.Scheme) - } else { - sdkObj.Scheme = nil - } - - if lbSpec.IPAddressType != nil { - sdkObj.IpAddressType = (*string)(lbSpec.IPAddressType) - } else { - sdkObj.IpAddressType = nil - } - - sdkObj.SubnetMappings = buildSDKSubnetMappings(lbSpec.SubnetMappings) - if sdkSecurityGroups, err := buildSDKSecurityGroups(lbSpec.SecurityGroups); err != nil { - return nil, err - } else { - sdkObj.SecurityGroups = sdkSecurityGroups - } - - sdkObj.CustomerOwnedIpv4Pool = lbSpec.CustomerOwnedIPv4Pool - return sdkObj, nil -} - -func buildSDKSubnetMappings(modelSubnetMappings []elbv2model.SubnetMapping) []*elbv2sdk.SubnetMapping { - var sdkSubnetMappings []*elbv2sdk.SubnetMapping - if len(modelSubnetMappings) != 0 { - sdkSubnetMappings = make([]*elbv2sdk.SubnetMapping, 0, len(modelSubnetMappings)) - for _, modelSubnetMapping := range modelSubnetMappings { - sdkSubnetMappings = append(sdkSubnetMappings, buildSDKSubnetMapping(modelSubnetMapping)) - } - } - return sdkSubnetMappings -} - -func buildSDKSecurityGroups(modelSecurityGroups []coremodel.StringToken) ([]*string, error) { - ctx := context.Background() - var sdkSecurityGroups []*string - if len(modelSecurityGroups) != 0 { - sdkSecurityGroups = make([]*string, 0, len(modelSecurityGroups)) - for _, modelSecurityGroup := range modelSecurityGroups { - token, err := modelSecurityGroup.Resolve(ctx) - if err != nil { - return nil, err - } - sdkSecurityGroups = append(sdkSecurityGroups, awssdk.String(token)) - } - } - return sdkSecurityGroups, nil -} - -func buildSDKSubnetMapping(modelSubnetMapping elbv2model.SubnetMapping) *elbv2sdk.SubnetMapping { - return &elbv2sdk.SubnetMapping{ - AllocationId: modelSubnetMapping.AllocationID, - PrivateIPv4Address: modelSubnetMapping.PrivateIPv4Address, - IPv6Address: modelSubnetMapping.IPv6Address, - SubnetId: awssdk.String(modelSubnetMapping.SubnetID), - } -} - -func buildResLoadBalancerStatus(sdkLB LoadBalancerWithTags) elbv2model.LoadBalancerStatus { - return elbv2model.LoadBalancerStatus{ - LoadBalancerARN: awssdk.StringValue(sdkLB.LoadBalancer.LoadBalancerArn), - DNSName: awssdk.StringValue(sdkLB.LoadBalancer.DNSName), - } -} diff --git a/pkg/deploy/elbv2/load_balancer_manager_test.go b/pkg/deploy/elbv2/load_balancer_manager_test.go deleted file mode 100644 index a381c50f3..000000000 --- a/pkg/deploy/elbv2/load_balancer_manager_test.go +++ /dev/null @@ -1,416 +0,0 @@ -package elbv2 - -import ( - "context" - "testing" - - awssdk "github.com/aws/aws-sdk-go/aws" - elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/go-logr/logr" - "github.com/stretchr/testify/assert" - coremodel "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" - "sigs.k8s.io/controller-runtime/pkg/log" -) - -func Test_buildSDKCreateLoadBalancerInput(t *testing.T) { - schemeInternetFacing := elbv2model.LoadBalancerSchemeInternetFacing - addressTypeDualStack := elbv2model.IPAddressTypeDualStack - type args struct { - lbSpec elbv2model.LoadBalancerSpec - } - tests := []struct { - name string - args args - want *elbv2sdk.CreateLoadBalancerInput - wantErr error - }{ - { - name: "application loadBalancer - standard case", - args: args{ - lbSpec: elbv2model.LoadBalancerSpec{ - Name: "my-alb", - Type: elbv2model.LoadBalancerTypeApplication, - Scheme: &schemeInternetFacing, - IPAddressType: &addressTypeDualStack, - SubnetMappings: []elbv2model.SubnetMapping{ - { - SubnetID: "subnet-A", - }, - { - SubnetID: "subnet-B", - }, - }, - SecurityGroups: []coremodel.StringToken{ - coremodel.LiteralStringToken("sg-A"), - coremodel.LiteralStringToken("sg-B"), - }, - }, - }, - want: &elbv2sdk.CreateLoadBalancerInput{ - Name: awssdk.String("my-alb"), - Type: awssdk.String("application"), - IpAddressType: awssdk.String("dualstack"), - Scheme: awssdk.String("internet-facing"), - SubnetMappings: []*elbv2sdk.SubnetMapping{ - { - SubnetId: awssdk.String("subnet-A"), - }, - { - SubnetId: awssdk.String("subnet-B"), - }, - }, - SecurityGroups: awssdk.StringSlice([]string{"sg-A", "sg-B"}), - }, - }, - { - name: "network loadBalancer - standard case", - args: args{ - lbSpec: elbv2model.LoadBalancerSpec{ - Name: "my-nlb", - Type: elbv2model.LoadBalancerTypeNetwork, - Scheme: &schemeInternetFacing, - IPAddressType: &addressTypeDualStack, - SubnetMappings: []elbv2model.SubnetMapping{ - { - SubnetID: "subnet-A", - }, - { - SubnetID: "subnet-B", - }, - }, - }, - }, - want: &elbv2sdk.CreateLoadBalancerInput{ - Name: awssdk.String("my-nlb"), - Type: awssdk.String("network"), - IpAddressType: awssdk.String("dualstack"), - Scheme: awssdk.String("internet-facing"), - SubnetMappings: []*elbv2sdk.SubnetMapping{ - { - SubnetId: awssdk.String("subnet-A"), - }, - { - SubnetId: awssdk.String("subnet-B"), - }, - }, - }, - }, - { - name: "application loadBalancer - with CoIP pool", - args: args{ - lbSpec: elbv2model.LoadBalancerSpec{ - Name: "my-alb", - Type: elbv2model.LoadBalancerTypeApplication, - Scheme: &schemeInternetFacing, - IPAddressType: &addressTypeDualStack, - SubnetMappings: []elbv2model.SubnetMapping{ - { - SubnetID: "subnet-A", - }, - { - SubnetID: "subnet-B", - }, - }, - SecurityGroups: []coremodel.StringToken{ - coremodel.LiteralStringToken("sg-A"), - coremodel.LiteralStringToken("sg-B"), - }, - CustomerOwnedIPv4Pool: awssdk.String("coIP-pool-x"), - }, - }, - want: &elbv2sdk.CreateLoadBalancerInput{ - Name: awssdk.String("my-alb"), - Type: awssdk.String("application"), - IpAddressType: awssdk.String("dualstack"), - Scheme: awssdk.String("internet-facing"), - SubnetMappings: []*elbv2sdk.SubnetMapping{ - { - SubnetId: awssdk.String("subnet-A"), - }, - { - SubnetId: awssdk.String("subnet-B"), - }, - }, - SecurityGroups: awssdk.StringSlice([]string{"sg-A", "sg-B"}), - CustomerOwnedIpv4Pool: awssdk.String("coIP-pool-x"), - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := buildSDKCreateLoadBalancerInput(tt.args.lbSpec) - if tt.wantErr != nil { - assert.EqualError(t, err, tt.wantErr.Error()) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.want, got) - } - }) - } -} - -func Test_buildSDKSubnetMappings(t *testing.T) { - type args struct { - modelSubnetMappings []elbv2model.SubnetMapping - } - tests := []struct { - name string - args args - want []*elbv2sdk.SubnetMapping - }{ - { - name: "standard case", - args: args{ - modelSubnetMappings: []elbv2model.SubnetMapping{ - { - SubnetID: "subnet-a", - }, - { - SubnetID: "subnet-b", - }, - }, - }, - want: []*elbv2sdk.SubnetMapping{ - { - SubnetId: awssdk.String("subnet-a"), - }, - { - SubnetId: awssdk.String("subnet-b"), - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := buildSDKSubnetMappings(tt.args.modelSubnetMappings) - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_buildSDKSecurityGroups(t *testing.T) { - type args struct { - modelSecurityGroups []coremodel.StringToken - } - tests := []struct { - name string - args args - want []*string - wantErr error - }{ - { - name: "one securityGroup", - args: args{ - modelSecurityGroups: []coremodel.StringToken{ - coremodel.LiteralStringToken("sg-a"), - }, - }, - want: awssdk.StringSlice([]string{"sg-a"}), - }, - { - name: "multiple securityGroups", - args: args{ - modelSecurityGroups: []coremodel.StringToken{ - coremodel.LiteralStringToken("sg-a"), - coremodel.LiteralStringToken("sg-b"), - }, - }, - want: awssdk.StringSlice([]string{"sg-a", "sg-b"}), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := buildSDKSecurityGroups(tt.args.modelSecurityGroups) - if tt.wantErr != nil { - assert.EqualError(t, err, tt.wantErr.Error()) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.want, got) - } - }) - } -} - -func Test_buildSDKSubnetMapping(t *testing.T) { - type args struct { - modelSubnetMapping elbv2model.SubnetMapping - } - tests := []struct { - name string - args args - want *elbv2sdk.SubnetMapping - }{ - { - name: "stand case", - args: args{ - modelSubnetMapping: elbv2model.SubnetMapping{ - AllocationID: awssdk.String("some-id"), - PrivateIPv4Address: awssdk.String("192.168.100.0"), - SubnetID: "subnet-abc", - }, - }, - want: &elbv2sdk.SubnetMapping{ - AllocationId: awssdk.String("some-id"), - PrivateIPv4Address: awssdk.String("192.168.100.0"), - SubnetId: awssdk.String("subnet-abc"), - }, - }, - { - name: "only-subnet specified", - args: args{ - modelSubnetMapping: elbv2model.SubnetMapping{ - SubnetID: "subnet-abc", - }, - }, - want: &elbv2sdk.SubnetMapping{ - SubnetId: awssdk.String("subnet-abc"), - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := buildSDKSubnetMapping(tt.args.modelSubnetMapping) - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_buildResLoadBalancerStatus(t *testing.T) { - type args struct { - sdkLB LoadBalancerWithTags - } - tests := []struct { - name string - args args - want elbv2model.LoadBalancerStatus - }{ - { - name: "standard case", - args: args{ - sdkLB: LoadBalancerWithTags{ - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("my-arn"), - DNSName: awssdk.String("www.example.com"), - }, - }, - }, - want: elbv2model.LoadBalancerStatus{ - LoadBalancerARN: "my-arn", - DNSName: "www.example.com", - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := buildResLoadBalancerStatus(tt.args.sdkLB) - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_defaultLoadBalancerManager_checkSDKLoadBalancerWithCOIPv4Pool(t *testing.T) { - type args struct { - resLB *elbv2model.LoadBalancer - sdkLB LoadBalancerWithTags - } - tests := []struct { - name string - args args - wantErr error - }{ - { - name: "both resLB and sdkLB don't have CustomerOwnedIPv4Pool setting", - args: args{ - resLB: &elbv2model.LoadBalancer{ - Spec: elbv2model.LoadBalancerSpec{ - CustomerOwnedIPv4Pool: nil, - }, - }, - sdkLB: LoadBalancerWithTags{ - LoadBalancer: &elbv2sdk.LoadBalancer{ - CustomerOwnedIpv4Pool: nil, - }, - }, - }, - wantErr: nil, - }, - { - name: "both resLB and sdkLB have same CustomerOwnedIPv4Pool setting", - args: args{ - resLB: &elbv2model.LoadBalancer{ - Spec: elbv2model.LoadBalancerSpec{ - CustomerOwnedIPv4Pool: awssdk.String("ipv4pool-coip-abc"), - }, - }, - sdkLB: LoadBalancerWithTags{ - LoadBalancer: &elbv2sdk.LoadBalancer{ - CustomerOwnedIpv4Pool: awssdk.String("ipv4pool-coip-abc"), - }, - }, - }, - wantErr: nil, - }, - { - name: "both resLB and sdkLB have different CustomerOwnedIPv4Pool setting", - args: args{ - resLB: &elbv2model.LoadBalancer{ - Spec: elbv2model.LoadBalancerSpec{ - CustomerOwnedIPv4Pool: awssdk.String("ipv4pool-coip-abc"), - }, - }, - sdkLB: LoadBalancerWithTags{ - LoadBalancer: &elbv2sdk.LoadBalancer{ - CustomerOwnedIpv4Pool: awssdk.String("ipv4pool-coip-def"), - }, - }, - }, - wantErr: nil, - }, - { - name: "only resLB have CustomerOwnedIPv4Pool setting", - args: args{ - resLB: &elbv2model.LoadBalancer{ - Spec: elbv2model.LoadBalancerSpec{ - CustomerOwnedIPv4Pool: awssdk.String("ipv4pool-coip-abc"), - }, - }, - sdkLB: LoadBalancerWithTags{ - LoadBalancer: &elbv2sdk.LoadBalancer{ - CustomerOwnedIpv4Pool: nil, - }, - }, - }, - wantErr: nil, - }, - { - name: "only sdkLB have CustomerOwnedIPv4Pool setting", - args: args{ - resLB: &elbv2model.LoadBalancer{ - Spec: elbv2model.LoadBalancerSpec{ - CustomerOwnedIPv4Pool: nil, - }, - }, - sdkLB: LoadBalancerWithTags{ - LoadBalancer: &elbv2sdk.LoadBalancer{ - CustomerOwnedIpv4Pool: awssdk.String("ipv4pool-coip-abc"), - }, - }, - }, - wantErr: nil, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m := &defaultLoadBalancerManager{ - logger: logr.New(&log.NullLogSink{}), - } - err := m.checkSDKLoadBalancerWithCOIPv4Pool(context.Background(), tt.args.resLB, tt.args.sdkLB) - if tt.wantErr != nil { - assert.EqualError(t, err, tt.wantErr.Error()) - } else { - assert.NoError(t, err) - } - }) - } -} diff --git a/pkg/deploy/elbv2/load_balancer_synthesizer.go b/pkg/deploy/elbv2/load_balancer_synthesizer.go deleted file mode 100644 index 5e81ad9bc..000000000 --- a/pkg/deploy/elbv2/load_balancer_synthesizer.go +++ /dev/null @@ -1,198 +0,0 @@ -package elbv2 - -import ( - "context" - "strings" - - awssdk "github.com/aws/aws-sdk-go/aws" - elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/go-logr/logr" - "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/util/sets" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/tracking" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" -) - -const ( - lbAttrsDeletionProtectionEnabled = "deletion_protection.enabled" -) - -// NewLoadBalancerSynthesizer constructs loadBalancerSynthesizer -func NewLoadBalancerSynthesizer(elbv2Client services.ELBV2, trackingProvider tracking.Provider, taggingManager TaggingManager, - lbManager LoadBalancerManager, logger logr.Logger, stack core.Stack) *loadBalancerSynthesizer { - return &loadBalancerSynthesizer{ - elbv2Client: elbv2Client, - trackingProvider: trackingProvider, - taggingManager: taggingManager, - lbManager: lbManager, - logger: logger, - stack: stack, - } -} - -// loadBalancerSynthesizer is responsible for synthesize LoadBalancer resources types for certain stack. -type loadBalancerSynthesizer struct { - elbv2Client services.ELBV2 - trackingProvider tracking.Provider - taggingManager TaggingManager - lbManager LoadBalancerManager - logger logr.Logger - - stack core.Stack -} - -func (s *loadBalancerSynthesizer) Synthesize(ctx context.Context) error { - var resLBs []*elbv2model.LoadBalancer - s.stack.ListResources(&resLBs) - sdkLBs, err := s.findSDKLoadBalancers(ctx) - if err != nil { - return err - } - - matchedResAndSDKLBs, unmatchedResLBs, unmatchedSDKLBs, err := matchResAndSDKLoadBalancers(resLBs, sdkLBs, s.trackingProvider.ResourceIDTagKey()) - if err != nil { - return err - } - - // For LoadBalancers, we delete unmatched ones first given below facts: - // * LoadBalancer delete will automatically delete listeners attached to it. - // * we can avoid the operation to detach a targetGroup from unmatched LBs. (a targetGroup can only attach to one LB). - // I don't like this, but it's the easiest solution to meet our requirement :D. - for _, sdkLB := range unmatchedSDKLBs { - if err := s.lbManager.Delete(ctx, sdkLB); err != nil { - errMessage := err.Error() - if strings.Contains(errMessage, "OperationNotPermitted") && strings.Contains(errMessage, "deletion protection") { - s.disableDeletionProtection(sdkLB.LoadBalancer) - if err = s.lbManager.Delete(ctx, sdkLB); err != nil { - return err - } - } else { - return err - } - } - } - for _, resLB := range unmatchedResLBs { - lbStatus, err := s.lbManager.Create(ctx, resLB) - if err != nil { - return err - } - resLB.SetStatus(lbStatus) - } - for _, resAndSDKLB := range matchedResAndSDKLBs { - lbStatus, err := s.lbManager.Update(ctx, resAndSDKLB.resLB, resAndSDKLB.sdkLB) - if err != nil { - return err - } - resAndSDKLB.resLB.SetStatus(lbStatus) - } - return nil -} - -func (s *loadBalancerSynthesizer) disableDeletionProtection(lb *elbv2sdk.LoadBalancer) error { - input := &elbv2sdk.ModifyLoadBalancerAttributesInput{ - Attributes: []*elbv2sdk.LoadBalancerAttribute{ - { - Key: awssdk.String(lbAttrsDeletionProtectionEnabled), - Value: awssdk.String("false"), - }, - }, - LoadBalancerArn: lb.LoadBalancerArn, - } - _, err := s.elbv2Client.ModifyLoadBalancerAttributes(input) - return err -} - -func (s *loadBalancerSynthesizer) PostSynthesize(ctx context.Context) error { - // nothing to do here. - return nil -} - -// findSDKLoadBalancers will find all AWS LoadBalancer created for stack. -func (s *loadBalancerSynthesizer) findSDKLoadBalancers(ctx context.Context) ([]LoadBalancerWithTags, error) { - stackTags := s.trackingProvider.StackTags(s.stack) - stackTagsLegacy := s.trackingProvider.StackTagsLegacy(s.stack) - return s.taggingManager.ListLoadBalancers(ctx, - tracking.TagsAsTagFilter(stackTags), - tracking.TagsAsTagFilter(stackTagsLegacy)) -} - -type resAndSDKLoadBalancerPair struct { - resLB *elbv2model.LoadBalancer - sdkLB LoadBalancerWithTags -} - -func matchResAndSDKLoadBalancers(resLBs []*elbv2model.LoadBalancer, sdkLBs []LoadBalancerWithTags, - resourceIDTagKey string) ([]resAndSDKLoadBalancerPair, []*elbv2model.LoadBalancer, []LoadBalancerWithTags, error) { - var matchedResAndSDKLBs []resAndSDKLoadBalancerPair - var unmatchedResLBs []*elbv2model.LoadBalancer - var unmatchedSDKLBs []LoadBalancerWithTags - - resLBsByID := mapResLoadBalancerByResourceID(resLBs) - sdkLBsByID, err := mapSDKLoadBalancerByResourceID(sdkLBs, resourceIDTagKey) - if err != nil { - return nil, nil, nil, err - } - - resLBIDs := sets.StringKeySet(resLBsByID) - sdkLBIDs := sets.StringKeySet(sdkLBsByID) - for _, resID := range resLBIDs.Intersection(sdkLBIDs).List() { - resLB := resLBsByID[resID] - sdkLBs := sdkLBsByID[resID] - foundMatch := false - for _, sdkLB := range sdkLBs { - if isSDKLoadBalancerRequiresReplacement(sdkLB, resLB) { - unmatchedSDKLBs = append(unmatchedSDKLBs, sdkLB) - continue - } - matchedResAndSDKLBs = append(matchedResAndSDKLBs, resAndSDKLoadBalancerPair{ - resLB: resLB, - sdkLB: sdkLB, - }) - foundMatch = true - } - if !foundMatch { - unmatchedResLBs = append(unmatchedResLBs, resLB) - } - } - for _, resID := range resLBIDs.Difference(sdkLBIDs).List() { - unmatchedResLBs = append(unmatchedResLBs, resLBsByID[resID]) - } - for _, resID := range sdkLBIDs.Difference(resLBIDs).List() { - unmatchedSDKLBs = append(unmatchedSDKLBs, sdkLBsByID[resID]...) - } - - return matchedResAndSDKLBs, unmatchedResLBs, unmatchedSDKLBs, nil -} - -func mapResLoadBalancerByResourceID(resLBs []*elbv2model.LoadBalancer) map[string]*elbv2model.LoadBalancer { - resLBsByID := make(map[string]*elbv2model.LoadBalancer, len(resLBs)) - for _, resLB := range resLBs { - resLBsByID[resLB.ID()] = resLB - } - return resLBsByID -} - -func mapSDKLoadBalancerByResourceID(sdkLBs []LoadBalancerWithTags, resourceIDTagKey string) (map[string][]LoadBalancerWithTags, error) { - sdkLBsByID := make(map[string][]LoadBalancerWithTags, len(sdkLBs)) - for _, sdkLB := range sdkLBs { - resourceID, ok := sdkLB.Tags[resourceIDTagKey] - if !ok { - return nil, errors.Errorf("unexpected loadBalancer with no resourceID: %v", awssdk.StringValue(sdkLB.LoadBalancer.LoadBalancerArn)) - } - sdkLBsByID[resourceID] = append(sdkLBsByID[resourceID], sdkLB) - } - return sdkLBsByID, nil -} - -// isSDKLoadBalancerRequiresReplacement checks whether a sdk LoadBalancer requires replacement to fulfill a LoadBalancer resource. -func isSDKLoadBalancerRequiresReplacement(sdkLB LoadBalancerWithTags, resLB *elbv2model.LoadBalancer) bool { - if string(resLB.Spec.Type) != awssdk.StringValue(sdkLB.LoadBalancer.Type) { - return true - } - if resLB.Spec.Scheme != nil && string(*resLB.Spec.Scheme) != awssdk.StringValue(sdkLB.LoadBalancer.Scheme) { - return true - } - return false -} diff --git a/pkg/deploy/elbv2/load_balancer_synthesizer_test.go b/pkg/deploy/elbv2/load_balancer_synthesizer_test.go deleted file mode 100644 index bbf20b327..000000000 --- a/pkg/deploy/elbv2/load_balancer_synthesizer_test.go +++ /dev/null @@ -1,597 +0,0 @@ -package elbv2 - -import ( - "testing" - - awssdk "github.com/aws/aws-sdk-go/aws" - elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - coremodel "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" -) - -func Test_matchResAndSDKLoadBalancers(t *testing.T) { - stack := coremodel.NewDefaultStack(coremodel.StackID{Namespace: "namespace", Name: "name"}) - type args struct { - resLBs []*elbv2model.LoadBalancer - sdkLBs []LoadBalancerWithTags - resourceIDTagKey string - } - tests := []struct { - name string - args args - want []resAndSDKLoadBalancerPair - want1 []*elbv2model.LoadBalancer - want2 []LoadBalancerWithTags - wantErr error - }{ - { - name: "all loadBalancer has match", - args: args{ - resLBs: []*elbv2model.LoadBalancer{ - { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::LoadBalancer", "id-1"), - Spec: elbv2model.LoadBalancerSpec{ - Name: "id-1", - }, - }, - { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::LoadBalancer", "id-2"), - Spec: elbv2model.LoadBalancerSpec{ - Name: "id-2", - }, - }, - }, - sdkLBs: []LoadBalancerWithTags{ - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-2"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-2", - }, - }, - }, - resourceIDTagKey: "ingress.k8s.aws/resource", - }, - want: []resAndSDKLoadBalancerPair{ - { - resLB: &elbv2model.LoadBalancer{ - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::LoadBalancer", "id-1"), - Spec: elbv2model.LoadBalancerSpec{ - Name: "id-1", - }, - }, - sdkLB: LoadBalancerWithTags{ - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - }, - { - resLB: &elbv2model.LoadBalancer{ - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::LoadBalancer", "id-2"), - Spec: elbv2model.LoadBalancerSpec{ - Name: "id-2", - }, - }, - sdkLB: LoadBalancerWithTags{ - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-2"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-2", - }, - }, - }, - }, - }, - { - name: "some res LoadBalancer don't have match", - args: args{ - resLBs: []*elbv2model.LoadBalancer{ - { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::LoadBalancer", "id-1"), - Spec: elbv2model.LoadBalancerSpec{ - Name: "id-1", - }, - }, - { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::LoadBalancer", "id-2"), - Spec: elbv2model.LoadBalancerSpec{ - Name: "id-2", - }, - }, - }, - sdkLBs: []LoadBalancerWithTags{ - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - }, - resourceIDTagKey: "ingress.k8s.aws/resource", - }, - want: []resAndSDKLoadBalancerPair{ - { - resLB: &elbv2model.LoadBalancer{ - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::LoadBalancer", "id-1"), - Spec: elbv2model.LoadBalancerSpec{ - Name: "id-1", - }, - }, - sdkLB: LoadBalancerWithTags{ - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - }, - }, - want1: []*elbv2model.LoadBalancer{ - { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::LoadBalancer", "id-2"), - Spec: elbv2model.LoadBalancerSpec{ - Name: "id-2", - }, - }, - }, - }, - { - name: "some sdk LoadBalancer don't have match", - args: args{ - resLBs: []*elbv2model.LoadBalancer{ - { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::LoadBalancer", "id-1"), - Spec: elbv2model.LoadBalancerSpec{ - Name: "id-1", - }, - }, - }, - sdkLBs: []LoadBalancerWithTags{ - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-2"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-2", - }, - }, - }, - resourceIDTagKey: "ingress.k8s.aws/resource", - }, - want: []resAndSDKLoadBalancerPair{ - { - resLB: &elbv2model.LoadBalancer{ - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::LoadBalancer", "id-1"), - Spec: elbv2model.LoadBalancerSpec{ - Name: "id-1", - }, - }, - sdkLB: LoadBalancerWithTags{ - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - }, - }, - want2: []LoadBalancerWithTags{ - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-2"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-2", - }, - }, - }, - }, - { - name: "one loadBalancer need to be replaced", - args: args{ - resLBs: []*elbv2model.LoadBalancer{ - { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::LoadBalancer", "id-1"), - Spec: elbv2model.LoadBalancerSpec{ - Name: "my-name", - Type: elbv2model.LoadBalancerTypeNetwork, - }, - }, - }, - sdkLBs: []LoadBalancerWithTags{ - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-1"), - Type: awssdk.String("application"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-2"), - Type: awssdk.String("network"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - }, - resourceIDTagKey: "ingress.k8s.aws/resource", - }, - want: []resAndSDKLoadBalancerPair{ - { - resLB: &elbv2model.LoadBalancer{ - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::LoadBalancer", "id-1"), - Spec: elbv2model.LoadBalancerSpec{ - Name: "my-name", - Type: elbv2model.LoadBalancerTypeNetwork, - }, - }, - sdkLB: LoadBalancerWithTags{ - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-2"), - Type: awssdk.String("network"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - }, - }, - want2: []LoadBalancerWithTags{ - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-1"), - Type: awssdk.String("application"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, got1, got2, err := matchResAndSDKLoadBalancers(tt.args.resLBs, tt.args.sdkLBs, tt.args.resourceIDTagKey) - if tt.wantErr != nil { - assert.EqualError(t, err, tt.wantErr.Error()) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.want, got) - assert.Equal(t, tt.want1, got1) - assert.Equal(t, tt.want2, got2) - } - }) - } -} - -func Test_mapResLoadBalancerByResourceID(t *testing.T) { - stack := coremodel.NewDefaultStack(coremodel.StackID{Namespace: "namespace", Name: "name"}) - type args struct { - resLBs []*elbv2model.LoadBalancer - } - tests := []struct { - name string - args args - want map[string]*elbv2model.LoadBalancer - }{ - { - name: "standard case", - args: args{ - resLBs: []*elbv2model.LoadBalancer{ - { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::LoadBalancer", "id-1"), - Spec: elbv2model.LoadBalancerSpec{ - Name: "id-1", - }, - }, - { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::LoadBalancer", "id-2"), - Spec: elbv2model.LoadBalancerSpec{ - Name: "id-2", - }, - }, - }, - }, - want: map[string]*elbv2model.LoadBalancer{ - "id-1": { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::LoadBalancer", "id-1"), - Spec: elbv2model.LoadBalancerSpec{ - Name: "id-1", - }, - }, - "id-2": { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::LoadBalancer", "id-2"), - Spec: elbv2model.LoadBalancerSpec{ - Name: "id-2", - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := mapResLoadBalancerByResourceID(tt.args.resLBs) - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_mapSDKLoadBalancerByResourceID(t *testing.T) { - type args struct { - sdkLBs []LoadBalancerWithTags - resourceIDTagKey string - } - tests := []struct { - name string - args args - want map[string][]LoadBalancerWithTags - wantErr error - }{ - { - name: "standard case", - args: args{ - sdkLBs: []LoadBalancerWithTags{ - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-2"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-2", - }, - }, - }, - resourceIDTagKey: "ingress.k8s.aws/resource", - }, - want: map[string][]LoadBalancerWithTags{ - "id-1": { - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - }, - "id-2": { - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-2"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-2", - }, - }, - }, - }, - }, - { - name: "multiple LoadBalancers with same ID", - args: args{ - sdkLBs: []LoadBalancerWithTags{ - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-2A"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-2", - }, - }, - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-2B"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-2", - }, - }, - }, - resourceIDTagKey: "ingress.k8s.aws/resource", - }, - want: map[string][]LoadBalancerWithTags{ - "id-1": { - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - }, - "id-2": { - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-2A"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-2", - }, - }, - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-2B"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-2", - }, - }, - }, - }, - }, - { - name: "LoadBalancers without resourceID tag", - args: args{ - sdkLBs: []LoadBalancerWithTags{ - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{}, - }, - }, - resourceIDTagKey: "ingress.k8s.aws/resource", - }, - wantErr: errors.New("unexpected loadBalancer with no resourceID: arn-1"), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := mapSDKLoadBalancerByResourceID(tt.args.sdkLBs, tt.args.resourceIDTagKey) - if tt.wantErr != nil { - assert.EqualError(t, err, tt.wantErr.Error()) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.want, got) - } - }) - } -} - -func Test_isSDKLoadBalancerRequiresReplacement(t *testing.T) { - schemaInternetFacing := elbv2model.LoadBalancerSchemeInternetFacing - type args struct { - sdkLB LoadBalancerWithTags - resLB *elbv2model.LoadBalancer - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "don't need replacement", - args: args{ - sdkLB: LoadBalancerWithTags{ - LoadBalancer: &elbv2sdk.LoadBalancer{ - Type: awssdk.String("application"), - Scheme: awssdk.String("internet-facing"), - LoadBalancerName: awssdk.String("my-lb"), - }, - }, - resLB: &elbv2model.LoadBalancer{ - Spec: elbv2model.LoadBalancerSpec{ - Type: elbv2model.LoadBalancerTypeApplication, - Scheme: &schemaInternetFacing, - Name: "my-lb", - }, - }, - }, - want: false, - }, - { - name: "name-only change shouldn't need replacement", - args: args{ - sdkLB: LoadBalancerWithTags{ - LoadBalancer: &elbv2sdk.LoadBalancer{ - Type: awssdk.String("application"), - Scheme: awssdk.String("internet-facing"), - LoadBalancerName: awssdk.String("my-lb1"), - }, - }, - resLB: &elbv2model.LoadBalancer{ - Spec: elbv2model.LoadBalancerSpec{ - Type: elbv2model.LoadBalancerTypeApplication, - Scheme: &schemaInternetFacing, - Name: "my-lb", - }, - }, - }, - want: false, - }, - { - name: "type change need replacement", - args: args{ - sdkLB: LoadBalancerWithTags{ - LoadBalancer: &elbv2sdk.LoadBalancer{ - Type: awssdk.String("network"), - Scheme: awssdk.String("internet-facing"), - LoadBalancerName: awssdk.String("my-lb"), - }, - }, - resLB: &elbv2model.LoadBalancer{ - Spec: elbv2model.LoadBalancerSpec{ - Type: elbv2model.LoadBalancerTypeApplication, - Scheme: &schemaInternetFacing, - Name: "my-lb", - }, - }, - }, - want: true, - }, - { - name: "scheme change need replacement", - args: args{ - sdkLB: LoadBalancerWithTags{ - LoadBalancer: &elbv2sdk.LoadBalancer{ - Type: awssdk.String("application"), - Scheme: awssdk.String("internal"), - LoadBalancerName: awssdk.String("my-lb"), - }, - }, - resLB: &elbv2model.LoadBalancer{ - Spec: elbv2model.LoadBalancerSpec{ - Type: elbv2model.LoadBalancerTypeApplication, - Scheme: &schemaInternetFacing, - Name: "my-lb", - }, - }, - }, - want: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := isSDKLoadBalancerRequiresReplacement(tt.args.sdkLB, tt.args.resLB) - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/deploy/elbv2/tagging_manager.go b/pkg/deploy/elbv2/tagging_manager.go deleted file mode 100644 index a8039a6ec..000000000 --- a/pkg/deploy/elbv2/tagging_manager.go +++ /dev/null @@ -1,474 +0,0 @@ -package elbv2 - -import ( - "context" - "github.com/aws/aws-sdk-go-v2/aws" - awssdk "github.com/aws/aws-sdk-go/aws" - elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi" - "github.com/go-logr/logr" - "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/util/sets" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/algorithm" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/config" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/tracking" -) - -const ( - // ELBV2 API supports up to 20 resource per DescribeTags API call. - defaultDescribeTagsChunkSize = 20 -) - -// LoadBalancer with it's tags. -type LoadBalancerWithTags struct { - LoadBalancer *elbv2sdk.LoadBalancer - Tags map[string]string -} - -// TargetGroup with it's tags. -type TargetGroupWithTags struct { - TargetGroup *elbv2sdk.TargetGroup - Tags map[string]string -} - -// Listener with it's tags. -type ListenerWithTags struct { - Listener *elbv2sdk.Listener - Tags map[string]string -} - -// ListenerRule with tags -type ListenerRuleWithTags struct { - ListenerRule *elbv2sdk.Rule - Tags map[string]string -} - -// options for ReconcileTags API. -type ReconcileTagsOptions struct { - // CurrentTags on resources. - // when it's nil, the TaggingManager will try to get the CurrentTags from AWS - CurrentTags map[string]string - - // IgnoredTagKeys defines the tag keys that should be ignored. - // these tags shouldn't be altered or deleted. - IgnoredTagKeys []string -} - -func (opts *ReconcileTagsOptions) ApplyOptions(options []ReconcileTagsOption) { - for _, option := range options { - option(opts) - } -} - -type ReconcileTagsOption func(opts *ReconcileTagsOptions) - -// WithCurrentTags is a reconcile option that supplies current tags. -func WithCurrentTags(tags map[string]string) ReconcileTagsOption { - return func(opts *ReconcileTagsOptions) { - opts.CurrentTags = tags - } -} - -// WithIgnoredTagKeys is a reconcile option that configures IgnoredTagKeys. -func WithIgnoredTagKeys(ignoredTagKeys []string) ReconcileTagsOption { - return func(opts *ReconcileTagsOptions) { - opts.IgnoredTagKeys = append(opts.IgnoredTagKeys, ignoredTagKeys...) - } -} - -// abstraction around tagging operations for ELBV2. -type TaggingManager interface { - // ReconcileTags will reconcile tags on resources. - ReconcileTags(ctx context.Context, arn string, desiredTags map[string]string, opts ...ReconcileTagsOption) error - - // ListLoadBalancers returns LoadBalancers that matches any of the tagging requirements. - ListLoadBalancers(ctx context.Context, tagFilters ...tracking.TagFilter) ([]LoadBalancerWithTags, error) - - // ListTargetGroups returns TargetGroups that matches any of the tagging requirements. - ListTargetGroups(ctx context.Context, tagFilters ...tracking.TagFilter) ([]TargetGroupWithTags, error) - - // ListListeners returns the LoadBalancer listeners along with tags - ListListeners(ctx context.Context, lbARN string) ([]ListenerWithTags, error) - - // ListListenerRules returns the Listener Rules along with tags - ListListenerRules(ctx context.Context, lsARN string) ([]ListenerRuleWithTags, error) -} - -// NewDefaultTaggingManager constructs default TaggingManager. -func NewDefaultTaggingManager(elbv2Client services.ELBV2, vpcID string, featureGates config.FeatureGates, rgt services.RGT, logger logr.Logger) *defaultTaggingManager { - return &defaultTaggingManager{ - elbv2Client: elbv2Client, - vpcID: vpcID, - featureGates: featureGates, - logger: logger, - describeTagsChunkSize: defaultDescribeTagsChunkSize, - rgt: rgt, - } -} - -var _ TaggingManager = &defaultTaggingManager{} - -// default implementation for TaggingManager -// @TODO: deprecate ELB API and only use AWS Resource Groups Tagging API to optimize this implementation once RGT has PrivateLink support. -type defaultTaggingManager struct { - elbv2Client services.ELBV2 - vpcID string - featureGates config.FeatureGates - logger logr.Logger - describeTagsChunkSize int - rgt services.RGT -} - -func (m *defaultTaggingManager) ReconcileTags(ctx context.Context, arn string, desiredTags map[string]string, opts ...ReconcileTagsOption) error { - reconcileOpts := ReconcileTagsOptions{ - CurrentTags: nil, - IgnoredTagKeys: nil, - } - reconcileOpts.ApplyOptions(opts) - currentTags := reconcileOpts.CurrentTags - if currentTags == nil { - tagsByARN, err := m.describeResourceTagsNative(ctx, []string{arn}) - if err != nil { - return err - } - currentTags = tagsByARN[arn] - } - - tagsToUpdate, tagsToRemove := algorithm.DiffStringMap(desiredTags, currentTags) - for _, ignoredTagKey := range reconcileOpts.IgnoredTagKeys { - delete(tagsToUpdate, ignoredTagKey) - delete(tagsToRemove, ignoredTagKey) - } - - if len(tagsToUpdate) > 0 { - req := &elbv2sdk.AddTagsInput{ - ResourceArns: []*string{awssdk.String(arn)}, - Tags: convertTagsToSDKTags(tagsToUpdate), - } - - m.logger.Info("adding resource tags", - "arn", arn, - "change", tagsToUpdate) - if _, err := m.elbv2Client.AddTagsWithContext(ctx, req); err != nil { - return err - } - m.logger.Info("added resource tags", - "arn", arn) - } - - if len(tagsToRemove) > 0 { - tagKeys := sets.StringKeySet(tagsToRemove).List() - req := &elbv2sdk.RemoveTagsInput{ - ResourceArns: []*string{awssdk.String(arn)}, - TagKeys: awssdk.StringSlice(tagKeys), - } - - m.logger.Info("removing resource tags", - "arn", arn, - "change", tagKeys) - if _, err := m.elbv2Client.RemoveTagsWithContext(ctx, req); err != nil { - return err - } - m.logger.Info("removed resource tags", - "arn", arn) - } - return nil -} - -func (m *defaultTaggingManager) ListListeners(ctx context.Context, lbARN string) ([]ListenerWithTags, error) { - req := &elbv2sdk.DescribeListenersInput{ - LoadBalancerArn: awssdk.String(lbARN), - } - listeners, err := m.elbv2Client.DescribeListenersAsList(ctx, req) - if err != nil { - return nil, err - } - lsARNs := make([]string, 0, len(listeners)) - lsByARN := make(map[string]*elbv2sdk.Listener, len(listeners)) - for _, listener := range listeners { - lsARN := awssdk.StringValue(listener.ListenerArn) - lsARNs = append(lsARNs, lsARN) - lsByARN[lsARN] = listener - } - var tagsByARN map[string]map[string]string - if m.featureGates.Enabled(config.ListenerRulesTagging) { - tagsByARN, err = m.describeResourceTagsNative(ctx, lsARNs) - if err != nil { - return nil, err - } - } - var sdkLSs []ListenerWithTags - for _, arn := range lsARNs { - tags := tagsByARN[arn] - sdkLSs = append(sdkLSs, ListenerWithTags{ - Listener: lsByARN[arn], - Tags: tags, - }) - } - return sdkLSs, err -} - -func (m *defaultTaggingManager) ListListenerRules(ctx context.Context, lsARN string) ([]ListenerRuleWithTags, error) { - req := &elbv2sdk.DescribeRulesInput{ - ListenerArn: awssdk.String(lsARN), - } - rules, err := m.elbv2Client.DescribeRulesAsList(ctx, req) - if err != nil { - return nil, err - } - lrARNs := make([]string, 0, len(rules)) - lrByARN := make(map[string]*elbv2sdk.Rule, len(rules)) - for _, rule := range rules { - lrARN := awssdk.StringValue(rule.RuleArn) - lrARNs = append(lrARNs, lrARN) - lrByARN[lrARN] = rule - } - var tagsByARN map[string]map[string]string - if m.featureGates.Enabled(config.ListenerRulesTagging) { - tagsByARN, err = m.describeResourceTagsNative(ctx, lrARNs) - if err != nil { - return nil, err - } - } - var sdkLRs []ListenerRuleWithTags - for _, arn := range lrARNs { - tags := tagsByARN[arn] - sdkLRs = append(sdkLRs, ListenerRuleWithTags{ - ListenerRule: lrByARN[arn], - Tags: tags, - }) - } - return sdkLRs, err -} - -func (m *defaultTaggingManager) ListLoadBalancers(ctx context.Context, tagFilters ...tracking.TagFilter) ([]LoadBalancerWithTags, error) { - if m.featureGates.Enabled(config.EnableRGTAPI) { - return m.listLoadBalancersRGT(ctx, tagFilters) - } - return m.listLoadBalancersNative(ctx, tagFilters) -} - -func (m *defaultTaggingManager) ListTargetGroups(ctx context.Context, tagFilters ...tracking.TagFilter) ([]TargetGroupWithTags, error) { - if m.featureGates.Enabled(config.EnableRGTAPI) { - return m.listTargetGroupsRGT(ctx, tagFilters) - } - return m.listTargetGroupsNative(ctx, tagFilters) - -} - -func (m *defaultTaggingManager) listLoadBalancersRGT(ctx context.Context, tagFilters []tracking.TagFilter) ([]LoadBalancerWithTags, error) { - // use a map to avoid potential duplication in returned resources - resourceTagsByARN := make(map[string][]*resourcegroupstaggingapi.Tag) - for _, tagFilter := range tagFilters { - req := &resourcegroupstaggingapi.GetResourcesInput{ - TagFilters: convertTagFiltersToRGTTagFilters(tagFilter), - ResourceTypeFilters: aws.StringSlice([]string{services.ResourceTypeELBLoadBalancer}), - } - resources, err := m.rgt.GetResourcesAsList(ctx, req) - if err != nil { - return nil, err - } - for _, resource := range resources { - if _, exists := resourceTagsByARN[aws.StringValue(resource.ResourceARN)]; !exists { - resourceTagsByARN[aws.StringValue(resource.ResourceARN)] = resource.Tags - } - } - } - var matchedLBs []LoadBalancerWithTags - for resourceARN, resourceTags := range resourceTagsByARN { - elbv2Req := &elbv2sdk.DescribeLoadBalancersInput{ - LoadBalancerArns: []*string{&resourceARN}, - } - elbv2Resp, err := m.elbv2Client.DescribeLoadBalancersAsList(ctx, elbv2Req) - if err != nil { - return nil, err - } - if len(elbv2Resp) == 0 { - return nil, errors.Errorf("no load balancer found for the arn: %v", resourceARN) - } - matchedLBs = append(matchedLBs, LoadBalancerWithTags{ - LoadBalancer: elbv2Resp[0], - Tags: services.ParseRGTTags(resourceTags), - }) - } - return matchedLBs, nil -} - -func (m *defaultTaggingManager) listLoadBalancersNative(ctx context.Context, tagFilters []tracking.TagFilter) ([]LoadBalancerWithTags, error) { - req := &elbv2sdk.DescribeLoadBalancersInput{} - lbs, err := m.elbv2Client.DescribeLoadBalancersAsList(ctx, req) - if err != nil { - return nil, err - } - lbARNsWithinVPC := make([]string, 0, len(lbs)) - lbByARNWithinVPC := make(map[string]*elbv2sdk.LoadBalancer, len(lbs)) - for _, lb := range lbs { - if awssdk.StringValue(lb.VpcId) != m.vpcID { - continue - } - lbARN := awssdk.StringValue(lb.LoadBalancerArn) - lbARNsWithinVPC = append(lbARNsWithinVPC, lbARN) - lbByARNWithinVPC[lbARN] = lb - } - tagsByARN, err := m.describeResourceTagsNative(ctx, lbARNsWithinVPC) - if err != nil { - return nil, err - } - - var matchedLBs []LoadBalancerWithTags - for _, arn := range lbARNsWithinVPC { - tags := tagsByARN[arn] - matchedAnyTagFilter := false - for _, tagFilter := range tagFilters { - if tagFilter.Matches(tags) { - matchedAnyTagFilter = true - break - } - } - if matchedAnyTagFilter { - matchedLBs = append(matchedLBs, LoadBalancerWithTags{ - LoadBalancer: lbByARNWithinVPC[arn], - Tags: tags, - }) - } - } - return matchedLBs, nil -} - -func (m *defaultTaggingManager) listTargetGroupsRGT(ctx context.Context, tagFilters []tracking.TagFilter) ([]TargetGroupWithTags, error) { - // use a map to avoid potential duplication in returned resources - resourceTagsByARN := make(map[string][]*resourcegroupstaggingapi.Tag) - for _, tagFilter := range tagFilters { - req := &resourcegroupstaggingapi.GetResourcesInput{ - TagFilters: convertTagFiltersToRGTTagFilters(tagFilter), - ResourceTypeFilters: aws.StringSlice([]string{services.ResourceTypeELBTargetGroup}), - } - resources, err := m.rgt.GetResourcesAsList(ctx, req) - if err != nil { - return nil, err - } - for _, resource := range resources { - if _, exists := resourceTagsByARN[aws.StringValue(resource.ResourceARN)]; !exists { - resourceTagsByARN[aws.StringValue(resource.ResourceARN)] = resource.Tags - } - } - } - var matchedTGs []TargetGroupWithTags - for resourceARN, resourceTags := range resourceTagsByARN { - elbv2Req := &elbv2sdk.DescribeTargetGroupsInput{ - TargetGroupArns: []*string{&resourceARN}, - } - elbv2Resp, err := m.elbv2Client.DescribeTargetGroupsAsList(ctx, elbv2Req) - if err != nil { - return nil, err - } - if len(elbv2Resp) == 0 { - return nil, errors.Errorf("no target group found for the arn: %v", resourceARN) - } - matchedTGs = append(matchedTGs, TargetGroupWithTags{ - TargetGroup: elbv2Resp[0], - Tags: services.ParseRGTTags(resourceTags), - }) - } - return matchedTGs, nil -} - -func (m *defaultTaggingManager) listTargetGroupsNative(ctx context.Context, tagFilters []tracking.TagFilter) ([]TargetGroupWithTags, error) { - req := &elbv2sdk.DescribeTargetGroupsInput{} - tgs, err := m.elbv2Client.DescribeTargetGroupsAsList(ctx, req) - if err != nil { - return nil, err - } - - tgARNsWithinVPC := make([]string, 0, len(tgs)) - tgByARNWithinVPC := make(map[string]*elbv2sdk.TargetGroup, len(tgs)) - for _, tg := range tgs { - if awssdk.StringValue(tg.VpcId) != m.vpcID { - continue - } - tgARN := awssdk.StringValue(tg.TargetGroupArn) - tgARNsWithinVPC = append(tgARNsWithinVPC, tgARN) - tgByARNWithinVPC[tgARN] = tg - } - tagsByARN, err := m.describeResourceTagsNative(ctx, tgARNsWithinVPC) - if err != nil { - return nil, err - } - - var matchedTGs []TargetGroupWithTags - for _, arn := range tgARNsWithinVPC { - tags := tagsByARN[arn] - matchedAnyTagFilter := false - for _, tagFilter := range tagFilters { - if tagFilter.Matches(tags) { - matchedAnyTagFilter = true - break - } - } - if matchedAnyTagFilter { - matchedTGs = append(matchedTGs, TargetGroupWithTags{ - TargetGroup: tgByARNWithinVPC[arn], - Tags: tags, - }) - } - } - return matchedTGs, nil -} - -// describeResourceTagsNative describes tags for elbv2 resources. -// returns tags indexed by resource ARN. -func (m *defaultTaggingManager) describeResourceTagsNative(ctx context.Context, arns []string) (map[string]map[string]string, error) { - tagsByARN := make(map[string]map[string]string, len(arns)) - arnsChunks := algorithm.ChunkStrings(arns, m.describeTagsChunkSize) - for _, arnsChunk := range arnsChunks { - req := &elbv2sdk.DescribeTagsInput{ - ResourceArns: awssdk.StringSlice(arnsChunk), - } - resp, err := m.elbv2Client.DescribeTagsWithContext(ctx, req) - if err != nil { - return nil, err - } - for _, tagDescription := range resp.TagDescriptions { - tagsByARN[awssdk.StringValue(tagDescription.ResourceArn)] = convertSDKTagsToTags(tagDescription.Tags) - } - } - return tagsByARN, nil -} - -// convert tags into AWS SDK tag presentation. -func convertTagsToSDKTags(tags map[string]string) []*elbv2sdk.Tag { - if len(tags) == 0 { - return nil - } - sdkTags := make([]*elbv2sdk.Tag, 0, len(tags)) - - for _, key := range sets.StringKeySet(tags).List() { - sdkTags = append(sdkTags, &elbv2sdk.Tag{ - Key: awssdk.String(key), - Value: awssdk.String(tags[key]), - }) - } - return sdkTags -} - -// convert AWS SDK tag presentation into tags. -func convertSDKTagsToTags(sdkTags []*elbv2sdk.Tag) map[string]string { - tags := make(map[string]string, len(sdkTags)) - for _, sdkTag := range sdkTags { - tags[awssdk.StringValue(sdkTag.Key)] = awssdk.StringValue(sdkTag.Value) - } - return tags -} - -// convert tagFilters to RGTTagFilters -func convertTagFiltersToRGTTagFilters(tagFilter tracking.TagFilter) []*resourcegroupstaggingapi.TagFilter { - var RGTTagFilters []*resourcegroupstaggingapi.TagFilter - for k, v := range tagFilter { - RGTTagFilters = append(RGTTagFilters, &resourcegroupstaggingapi.TagFilter{ - Key: aws.String(k), - Values: aws.StringSlice(v), - }) - } - return RGTTagFilters -} diff --git a/pkg/deploy/elbv2/tagging_manager_mocks.go b/pkg/deploy/elbv2/tagging_manager_mocks.go deleted file mode 100644 index ca2a06002..000000000 --- a/pkg/deploy/elbv2/tagging_manager_mocks.go +++ /dev/null @@ -1,125 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/elbv2 (interfaces: TaggingManager) - -// Package elbv2 is a generated GoMock package. -package elbv2 - -import ( - context "context" - reflect "reflect" - - gomock "github.com/golang/mock/gomock" - tracking "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/tracking" -) - -// MockTaggingManager is a mock of TaggingManager interface. -type MockTaggingManager struct { - ctrl *gomock.Controller - recorder *MockTaggingManagerMockRecorder -} - -// MockTaggingManagerMockRecorder is the mock recorder for MockTaggingManager. -type MockTaggingManagerMockRecorder struct { - mock *MockTaggingManager -} - -// NewMockTaggingManager creates a new mock instance. -func NewMockTaggingManager(ctrl *gomock.Controller) *MockTaggingManager { - mock := &MockTaggingManager{ctrl: ctrl} - mock.recorder = &MockTaggingManagerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockTaggingManager) EXPECT() *MockTaggingManagerMockRecorder { - return m.recorder -} - -// ListListenerRules mocks base method. -func (m *MockTaggingManager) ListListenerRules(arg0 context.Context, arg1 string) ([]ListenerRuleWithTags, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListListenerRules", arg0, arg1) - ret0, _ := ret[0].([]ListenerRuleWithTags) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListListenerRules indicates an expected call of ListListenerRules. -func (mr *MockTaggingManagerMockRecorder) ListListenerRules(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListListenerRules", reflect.TypeOf((*MockTaggingManager)(nil).ListListenerRules), arg0, arg1) -} - -// ListListeners mocks base method. -func (m *MockTaggingManager) ListListeners(arg0 context.Context, arg1 string) ([]ListenerWithTags, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListListeners", arg0, arg1) - ret0, _ := ret[0].([]ListenerWithTags) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListListeners indicates an expected call of ListListeners. -func (mr *MockTaggingManagerMockRecorder) ListListeners(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListListeners", reflect.TypeOf((*MockTaggingManager)(nil).ListListeners), arg0, arg1) -} - -// ListLoadBalancers mocks base method. -func (m *MockTaggingManager) ListLoadBalancers(arg0 context.Context, arg1 ...tracking.TagFilter) ([]LoadBalancerWithTags, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0} - for _, a := range arg1 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "ListLoadBalancers", varargs...) - ret0, _ := ret[0].([]LoadBalancerWithTags) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListLoadBalancers indicates an expected call of ListLoadBalancers. -func (mr *MockTaggingManagerMockRecorder) ListLoadBalancers(arg0 interface{}, arg1 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0}, arg1...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLoadBalancers", reflect.TypeOf((*MockTaggingManager)(nil).ListLoadBalancers), varargs...) -} - -// ListTargetGroups mocks base method. -func (m *MockTaggingManager) ListTargetGroups(arg0 context.Context, arg1 ...tracking.TagFilter) ([]TargetGroupWithTags, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0} - for _, a := range arg1 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "ListTargetGroups", varargs...) - ret0, _ := ret[0].([]TargetGroupWithTags) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListTargetGroups indicates an expected call of ListTargetGroups. -func (mr *MockTaggingManagerMockRecorder) ListTargetGroups(arg0 interface{}, arg1 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0}, arg1...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListTargetGroups", reflect.TypeOf((*MockTaggingManager)(nil).ListTargetGroups), varargs...) -} - -// ReconcileTags mocks base method. -func (m *MockTaggingManager) ReconcileTags(arg0 context.Context, arg1 string, arg2 map[string]string, arg3 ...ReconcileTagsOption) error { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1, arg2} - for _, a := range arg3 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "ReconcileTags", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// ReconcileTags indicates an expected call of ReconcileTags. -func (mr *MockTaggingManagerMockRecorder) ReconcileTags(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReconcileTags", reflect.TypeOf((*MockTaggingManager)(nil).ReconcileTags), varargs...) -} diff --git a/pkg/deploy/elbv2/tagging_manager_test.go b/pkg/deploy/elbv2/tagging_manager_test.go deleted file mode 100644 index 65aceabe2..000000000 --- a/pkg/deploy/elbv2/tagging_manager_test.go +++ /dev/null @@ -1,1384 +0,0 @@ -package elbv2 - -import ( - "context" - "testing" - - awssdk "github.com/aws/aws-sdk-go/aws" - elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/go-logr/logr" - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/assert" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/config" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/tracking" - "sigs.k8s.io/controller-runtime/pkg/log" -) - -func Test_defaultTaggingManager_ReconcileTags(t *testing.T) { - type describeTagsWithContextCall struct { - req *elbv2sdk.DescribeTagsInput - resp *elbv2sdk.DescribeTagsOutput - err error - } - type addTagsWithContextCall struct { - req *elbv2sdk.AddTagsInput - resp *elbv2sdk.AddTagsOutput - err error - } - type removeTagsWithContextCall struct { - req *elbv2sdk.RemoveTagsInput - resp *elbv2sdk.RemoveTagsOutput - err error - } - - type fields struct { - describeTagsWithContextCalls []describeTagsWithContextCall - addTagsWithContextCalls []addTagsWithContextCall - removeTagsWithContextCalls []removeTagsWithContextCall - } - type args struct { - arn string - desiredTags map[string]string - opts []ReconcileTagsOption - } - tests := []struct { - name string - fields fields - args args - wantErr error - }{ - { - name: "standard case - add/update/remove tags", - fields: fields{ - describeTagsWithContextCalls: []describeTagsWithContextCall{ - { - req: &elbv2sdk.DescribeTagsInput{ - ResourceArns: []*string{awssdk.String("my-arn")}, - }, - resp: &elbv2sdk.DescribeTagsOutput{ - TagDescriptions: []*elbv2sdk.TagDescription{ - { - ResourceArn: awssdk.String("my-arn"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB"), - }, - { - Key: awssdk.String("keyC"), - Value: awssdk.String("valueC"), - }, - }, - }, - }, - }, - }, - }, - addTagsWithContextCalls: []addTagsWithContextCall{ - { - req: &elbv2sdk.AddTagsInput{ - ResourceArns: []*string{awssdk.String("my-arn")}, - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB2"), - }, - { - Key: awssdk.String("keyD"), - Value: awssdk.String("valueD"), - }, - }, - }, - }, - }, - removeTagsWithContextCalls: []removeTagsWithContextCall{ - { - req: &elbv2sdk.RemoveTagsInput{ - ResourceArns: []*string{awssdk.String("my-arn")}, - TagKeys: []*string{awssdk.String("keyC")}, - }, - }, - }, - }, - args: args{ - arn: "my-arn", - desiredTags: map[string]string{ - "keyA": "valueA", - "keyB": "valueB2", - "keyD": "valueD", - }, - opts: nil, - }, - }, - { - name: "standard case - with currentTags provided", - fields: fields{ - describeTagsWithContextCalls: nil, - addTagsWithContextCalls: []addTagsWithContextCall{ - { - req: &elbv2sdk.AddTagsInput{ - ResourceArns: []*string{awssdk.String("my-arn")}, - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB2"), - }, - { - Key: awssdk.String("keyD"), - Value: awssdk.String("valueD"), - }, - }, - }, - }, - }, - removeTagsWithContextCalls: []removeTagsWithContextCall{ - { - req: &elbv2sdk.RemoveTagsInput{ - ResourceArns: []*string{awssdk.String("my-arn")}, - TagKeys: []*string{awssdk.String("keyC")}, - }, - }, - }, - }, - args: args{ - arn: "my-arn", - desiredTags: map[string]string{ - "keyA": "valueA", - "keyB": "valueB2", - "keyD": "valueD", - }, - opts: []ReconcileTagsOption{ - WithCurrentTags(map[string]string{ - "keyA": "valueA", - "keyB": "valueB", - "keyC": "valueC", - }), - }, - }, - }, - { - name: "ignore specific tag updates and deletes", - fields: fields{ - describeTagsWithContextCalls: nil, - addTagsWithContextCalls: []addTagsWithContextCall{ - { - req: &elbv2sdk.AddTagsInput{ - ResourceArns: []*string{awssdk.String("my-arn")}, - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyC"), - Value: awssdk.String("valueC2"), - }, - { - Key: awssdk.String("keyD"), - Value: awssdk.String("valueD"), - }, - }, - }, - }, - }, - removeTagsWithContextCalls: []removeTagsWithContextCall{ - { - req: &elbv2sdk.RemoveTagsInput{ - ResourceArns: []*string{awssdk.String("my-arn")}, - TagKeys: []*string{awssdk.String("keyF")}, - }, - }, - }, - }, - args: args{ - arn: "my-arn", - desiredTags: map[string]string{ - "keyA": "valueA", - "keyB": "valueB2", - "keyC": "valueC2", - "keyD": "valueD", - }, - opts: []ReconcileTagsOption{ - WithCurrentTags(map[string]string{ - "keyA": "valueA", - "keyB": "valueB", - "keyC": "valueC", - "keyE": "valueE", - "keyF": "valueF", - }), - WithIgnoredTagKeys([]string{"keyB", "keyE"}), - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - elbv2Client := services.NewMockELBV2(ctrl) - featureGates := config.NewFeatureGates() - for _, call := range tt.fields.describeTagsWithContextCalls { - elbv2Client.EXPECT().DescribeTagsWithContext(gomock.Any(), call.req).Return(call.resp, call.err) - } - for _, call := range tt.fields.addTagsWithContextCalls { - elbv2Client.EXPECT().AddTagsWithContext(gomock.Any(), call.req).Return(call.resp, call.err) - } - for _, call := range tt.fields.removeTagsWithContextCalls { - elbv2Client.EXPECT().RemoveTagsWithContext(gomock.Any(), call.req).Return(call.resp, call.err) - } - - m := &defaultTaggingManager{ - elbv2Client: elbv2Client, - vpcID: "vpc-xxxxxxx", - logger: logr.New(&log.NullLogSink{}), - describeTagsChunkSize: defaultDescribeTagsChunkSize, - featureGates: featureGates, - } - err := m.ReconcileTags(context.Background(), tt.args.arn, tt.args.desiredTags, tt.args.opts...) - if tt.wantErr != nil { - assert.EqualError(t, err, tt.wantErr.Error()) - } else { - assert.NoError(t, err) - } - }) - } -} - -func Test_defaultTaggingManager_ListLoadBalancers(t *testing.T) { - type describeLoadBalancersAsListCall struct { - req *elbv2sdk.DescribeLoadBalancersInput - resp []*elbv2sdk.LoadBalancer - err error - } - type describeTagsWithContextCall struct { - req *elbv2sdk.DescribeTagsInput - resp *elbv2sdk.DescribeTagsOutput - err error - } - type fields struct { - describeLoadBalancersAsListCalls []describeLoadBalancersAsListCall - describeTagsWithContextCalls []describeTagsWithContextCall - } - type args struct { - tagFilters []tracking.TagFilter - } - tests := []struct { - name string - fields fields - args args - want []LoadBalancerWithTags - wantErr error - }{ - { - name: "2/3 loadBalancers matches single tagFilter; 0 loadBalancers filtered out on VPC ID", - fields: fields{ - describeLoadBalancersAsListCalls: []describeLoadBalancersAsListCall{ - { - req: &elbv2sdk.DescribeLoadBalancersInput{}, - resp: []*elbv2sdk.LoadBalancer{ - { - LoadBalancerArn: awssdk.String("lb-1"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - { - LoadBalancerArn: awssdk.String("lb-2"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - { - LoadBalancerArn: awssdk.String("lb-3"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - }, - }, - }, - describeTagsWithContextCalls: []describeTagsWithContextCall{ - { - req: &elbv2sdk.DescribeTagsInput{ - ResourceArns: awssdk.StringSlice([]string{"lb-1", "lb-2", "lb-3"}), - }, - resp: &elbv2sdk.DescribeTagsOutput{ - TagDescriptions: []*elbv2sdk.TagDescription{ - { - ResourceArn: awssdk.String("lb-1"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA1"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB1"), - }, - }, - }, - { - ResourceArn: awssdk.String("lb-2"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA2"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB2"), - }, - }, - }, - { - ResourceArn: awssdk.String("lb-3"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA3"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB3"), - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - tagFilters: []tracking.TagFilter{ - { - "keyA": {"valueA1", "valueA3"}, - }, - }, - }, - want: []LoadBalancerWithTags{ - { - LoadBalancer: &elbv2sdk.LoadBalancer{LoadBalancerArn: awssdk.String("lb-1"), VpcId: awssdk.String("vpc-xxxxxxx")}, - Tags: map[string]string{ - "keyA": "valueA1", - "keyB": "valueB1", - }, - }, - { - LoadBalancer: &elbv2sdk.LoadBalancer{LoadBalancerArn: awssdk.String("lb-3"), VpcId: awssdk.String("vpc-xxxxxxx")}, - Tags: map[string]string{ - "keyA": "valueA3", - "keyB": "valueB3", - }, - }, - }, - }, - { - name: "1/3 loadBalancers matches single tagFilter; 1 loadBalancer filtered out on VPC ID", - fields: fields{ - describeLoadBalancersAsListCalls: []describeLoadBalancersAsListCall{ - { - req: &elbv2sdk.DescribeLoadBalancersInput{}, - resp: []*elbv2sdk.LoadBalancer{ - { - LoadBalancerArn: awssdk.String("lb-1"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - { - LoadBalancerArn: awssdk.String("lb-2"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - { - LoadBalancerArn: awssdk.String("lb-3"), - VpcId: awssdk.String("vpc-aaaaaaa"), - }, - }, - }, - }, - describeTagsWithContextCalls: []describeTagsWithContextCall{ - { - req: &elbv2sdk.DescribeTagsInput{ - ResourceArns: awssdk.StringSlice([]string{"lb-1", "lb-2"}), - }, - resp: &elbv2sdk.DescribeTagsOutput{ - TagDescriptions: []*elbv2sdk.TagDescription{ - { - ResourceArn: awssdk.String("lb-1"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA1"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB1"), - }, - }, - }, - { - ResourceArn: awssdk.String("lb-2"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA2"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB2"), - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - tagFilters: []tracking.TagFilter{ - { - "keyA": {"valueA1", "valueA3"}, - }, - }, - }, - want: []LoadBalancerWithTags{ - { - LoadBalancer: &elbv2sdk.LoadBalancer{LoadBalancerArn: awssdk.String("lb-1"), VpcId: awssdk.String("vpc-xxxxxxx")}, - Tags: map[string]string{ - "keyA": "valueA1", - "keyB": "valueB1", - }, - }, - }, - }, - { - name: "1/3 loadBalancers matches single tagFilter; 2 loadBalancers filtered out on VPC ID", - fields: fields{ - describeLoadBalancersAsListCalls: []describeLoadBalancersAsListCall{ - { - req: &elbv2sdk.DescribeLoadBalancersInput{}, - resp: []*elbv2sdk.LoadBalancer{ - { - LoadBalancerArn: awssdk.String("lb-1"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - { - LoadBalancerArn: awssdk.String("lb-2"), - VpcId: awssdk.String("vpc-yyyyyyy"), - }, - { - LoadBalancerArn: awssdk.String("lb-3"), - VpcId: awssdk.String("vpc-aaaaaaa"), - }, - }, - }, - }, - describeTagsWithContextCalls: []describeTagsWithContextCall{ - { - req: &elbv2sdk.DescribeTagsInput{ - ResourceArns: awssdk.StringSlice([]string{"lb-1"}), - }, - resp: &elbv2sdk.DescribeTagsOutput{ - TagDescriptions: []*elbv2sdk.TagDescription{ - { - ResourceArn: awssdk.String("lb-1"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA1"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB1"), - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - tagFilters: []tracking.TagFilter{ - { - "keyA": {"valueA1", "valueA3"}, - }, - }, - }, - want: []LoadBalancerWithTags{ - { - LoadBalancer: &elbv2sdk.LoadBalancer{LoadBalancerArn: awssdk.String("lb-1"), VpcId: awssdk.String("vpc-xxxxxxx")}, - Tags: map[string]string{ - "keyA": "valueA1", - "keyB": "valueB1", - }, - }, - }, - }, - { - name: "0/3 loadBalancers matches single tagFilter; 0 loadBalancers filtered out on VPC ID", - fields: fields{ - describeLoadBalancersAsListCalls: []describeLoadBalancersAsListCall{ - { - req: &elbv2sdk.DescribeLoadBalancersInput{}, - resp: []*elbv2sdk.LoadBalancer{ - { - LoadBalancerArn: awssdk.String("lb-1"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - { - LoadBalancerArn: awssdk.String("lb-2"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - { - LoadBalancerArn: awssdk.String("lb-3"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - }, - }, - }, - describeTagsWithContextCalls: []describeTagsWithContextCall{ - { - req: &elbv2sdk.DescribeTagsInput{ - ResourceArns: awssdk.StringSlice([]string{"lb-1", "lb-2", "lb-3"}), - }, - resp: &elbv2sdk.DescribeTagsOutput{ - TagDescriptions: []*elbv2sdk.TagDescription{ - { - ResourceArn: awssdk.String("lb-1"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA1"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB1"), - }, - }, - }, - { - ResourceArn: awssdk.String("lb-2"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA2"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB2"), - }, - }, - }, - { - ResourceArn: awssdk.String("lb-3"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA3"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB3"), - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - tagFilters: []tracking.TagFilter{ - { - "keyA": {"valueA4"}, - }, - }, - }, - want: nil, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - elbv2Client := services.NewMockELBV2(ctrl) - featureGates := config.NewFeatureGates() - for _, call := range tt.fields.describeLoadBalancersAsListCalls { - elbv2Client.EXPECT().DescribeLoadBalancersAsList(gomock.Any(), call.req).Return(call.resp, call.err) - } - for _, call := range tt.fields.describeTagsWithContextCalls { - elbv2Client.EXPECT().DescribeTagsWithContext(gomock.Any(), call.req).Return(call.resp, call.err) - } - - m := &defaultTaggingManager{ - elbv2Client: elbv2Client, - vpcID: "vpc-xxxxxxx", - describeTagsChunkSize: defaultDescribeTagsChunkSize, - featureGates: featureGates, - } - got, err := m.ListLoadBalancers(context.Background(), tt.args.tagFilters...) - if tt.wantErr != nil { - assert.EqualError(t, err, tt.wantErr.Error()) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.want, got) - } - }) - } -} - -func Test_defaultTaggingManager_ListTargetGroups(t *testing.T) { - type describeTargetGroupsAsListCall struct { - req *elbv2sdk.DescribeTargetGroupsInput - resp []*elbv2sdk.TargetGroup - err error - } - type describeTagsWithContextCall struct { - req *elbv2sdk.DescribeTagsInput - resp *elbv2sdk.DescribeTagsOutput - err error - } - type fields struct { - describeTargetGroupsAsListCalls []describeTargetGroupsAsListCall - describeTagsWithContextCalls []describeTagsWithContextCall - } - type args struct { - tagFilters []tracking.TagFilter - } - tests := []struct { - name string - fields fields - args args - want []TargetGroupWithTags - wantErr error - }{ - { - name: "2/3 targetGroups matches single tagFilter; 0 targetGroups filtered out on VPC ID", - fields: fields{ - describeTargetGroupsAsListCalls: []describeTargetGroupsAsListCall{ - { - req: &elbv2sdk.DescribeTargetGroupsInput{}, - resp: []*elbv2sdk.TargetGroup{ - { - TargetGroupArn: awssdk.String("tg-1"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - { - TargetGroupArn: awssdk.String("tg-2"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - { - TargetGroupArn: awssdk.String("tg-3"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - }, - }, - }, - describeTagsWithContextCalls: []describeTagsWithContextCall{ - { - req: &elbv2sdk.DescribeTagsInput{ - ResourceArns: awssdk.StringSlice([]string{"tg-1", "tg-2", "tg-3"}), - }, - resp: &elbv2sdk.DescribeTagsOutput{ - TagDescriptions: []*elbv2sdk.TagDescription{ - { - ResourceArn: awssdk.String("tg-1"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA1"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB1"), - }, - }, - }, - { - ResourceArn: awssdk.String("tg-2"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA2"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB2"), - }, - }, - }, - { - ResourceArn: awssdk.String("tg-3"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA3"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB3"), - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - tagFilters: []tracking.TagFilter{ - { - "keyA": {"valueA1", "valueA3"}, - }, - }, - }, - want: []TargetGroupWithTags{ - { - TargetGroup: &elbv2sdk.TargetGroup{TargetGroupArn: awssdk.String("tg-1"), VpcId: awssdk.String("vpc-xxxxxxx")}, - Tags: map[string]string{ - "keyA": "valueA1", - "keyB": "valueB1", - }, - }, - { - TargetGroup: &elbv2sdk.TargetGroup{TargetGroupArn: awssdk.String("tg-3"), VpcId: awssdk.String("vpc-xxxxxxx")}, - Tags: map[string]string{ - "keyA": "valueA3", - "keyB": "valueB3", - }, - }, - }, - }, - { - name: "1/3 targetGroups matches single tagFilter; 1 targetGroup filtered out on VPC ID", - fields: fields{ - describeTargetGroupsAsListCalls: []describeTargetGroupsAsListCall{ - { - req: &elbv2sdk.DescribeTargetGroupsInput{}, - resp: []*elbv2sdk.TargetGroup{ - { - TargetGroupArn: awssdk.String("tg-1"), - VpcId: awssdk.String("vpc-yyyyyyy"), - }, - { - TargetGroupArn: awssdk.String("tg-2"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - { - TargetGroupArn: awssdk.String("tg-3"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - }, - }, - }, - describeTagsWithContextCalls: []describeTagsWithContextCall{ - { - req: &elbv2sdk.DescribeTagsInput{ - ResourceArns: awssdk.StringSlice([]string{"tg-2", "tg-3"}), - }, - resp: &elbv2sdk.DescribeTagsOutput{ - TagDescriptions: []*elbv2sdk.TagDescription{ - { - ResourceArn: awssdk.String("tg-2"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA2"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB2"), - }, - }, - }, - { - ResourceArn: awssdk.String("tg-3"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA3"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB3"), - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - tagFilters: []tracking.TagFilter{ - { - "keyA": {"valueA1", "valueA3"}, - }, - }, - }, - want: []TargetGroupWithTags{ - { - TargetGroup: &elbv2sdk.TargetGroup{TargetGroupArn: awssdk.String("tg-3"), VpcId: awssdk.String("vpc-xxxxxxx")}, - Tags: map[string]string{ - "keyA": "valueA3", - "keyB": "valueB3", - }, - }, - }, - }, - { - name: "1/3 targetGroups matches single tagFilter; 2 targetGroups filtered out on VPC ID", - fields: fields{ - describeTargetGroupsAsListCalls: []describeTargetGroupsAsListCall{ - { - req: &elbv2sdk.DescribeTargetGroupsInput{}, - resp: []*elbv2sdk.TargetGroup{ - { - TargetGroupArn: awssdk.String("tg-1"), - VpcId: awssdk.String("vpc-yyyyyyy"), - }, - { - TargetGroupArn: awssdk.String("tg-2"), - VpcId: awssdk.String("vpc-ccccccc"), - }, - { - TargetGroupArn: awssdk.String("tg-3"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - }, - }, - }, - describeTagsWithContextCalls: []describeTagsWithContextCall{ - { - req: &elbv2sdk.DescribeTagsInput{ - ResourceArns: awssdk.StringSlice([]string{"tg-3"}), - }, - resp: &elbv2sdk.DescribeTagsOutput{ - TagDescriptions: []*elbv2sdk.TagDescription{ - { - ResourceArn: awssdk.String("tg-3"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA3"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB3"), - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - tagFilters: []tracking.TagFilter{ - { - "keyA": {"valueA1", "valueA3"}, - }, - }, - }, - want: []TargetGroupWithTags{ - { - TargetGroup: &elbv2sdk.TargetGroup{TargetGroupArn: awssdk.String("tg-3"), VpcId: awssdk.String("vpc-xxxxxxx")}, - Tags: map[string]string{ - "keyA": "valueA3", - "keyB": "valueB3", - }, - }, - }, - }, - { - name: "0/3 targetGroups matches single tagFilter; 0 targetGroups filtered out on VPC ID", - fields: fields{ - describeTargetGroupsAsListCalls: []describeTargetGroupsAsListCall{ - { - req: &elbv2sdk.DescribeTargetGroupsInput{}, - resp: []*elbv2sdk.TargetGroup{ - { - TargetGroupArn: awssdk.String("tg-1"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - { - TargetGroupArn: awssdk.String("tg-2"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - { - TargetGroupArn: awssdk.String("tg-3"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - }, - }, - }, - describeTagsWithContextCalls: []describeTagsWithContextCall{ - { - req: &elbv2sdk.DescribeTagsInput{ - ResourceArns: awssdk.StringSlice([]string{"tg-1", "tg-2", "tg-3"}), - }, - resp: &elbv2sdk.DescribeTagsOutput{ - TagDescriptions: []*elbv2sdk.TagDescription{ - { - ResourceArn: awssdk.String("tg-1"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA1"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB1"), - }, - }, - }, - { - ResourceArn: awssdk.String("tg-2"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA2"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB2"), - }, - }, - }, - { - ResourceArn: awssdk.String("tg-3"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA3"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB3"), - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - tagFilters: []tracking.TagFilter{ - { - "keyA": {"valueA4"}, - }, - }, - }, - want: nil, - }, - { - name: "2/4 targetGroups matches first tagFilter, 2/4 targetGroups matches second tagFilter", - fields: fields{ - describeTargetGroupsAsListCalls: []describeTargetGroupsAsListCall{ - { - req: &elbv2sdk.DescribeTargetGroupsInput{}, - resp: []*elbv2sdk.TargetGroup{ - { - TargetGroupArn: awssdk.String("tg-1"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - { - TargetGroupArn: awssdk.String("tg-2"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - { - TargetGroupArn: awssdk.String("tg-3"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - { - TargetGroupArn: awssdk.String("tg-4"), - VpcId: awssdk.String("vpc-xxxxxxx"), - }, - }, - }, - }, - describeTagsWithContextCalls: []describeTagsWithContextCall{ - { - req: &elbv2sdk.DescribeTagsInput{ - ResourceArns: awssdk.StringSlice([]string{"tg-1", "tg-2", "tg-3", "tg-4"}), - }, - resp: &elbv2sdk.DescribeTagsOutput{ - TagDescriptions: []*elbv2sdk.TagDescription{ - { - ResourceArn: awssdk.String("tg-1"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA1"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB1"), - }, - }, - }, - { - ResourceArn: awssdk.String("tg-2"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA2"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB2"), - }, - }, - }, - { - ResourceArn: awssdk.String("tg-3"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA3"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB3"), - }, - }, - }, - { - ResourceArn: awssdk.String("tg-4"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA4"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB4"), - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - tagFilters: []tracking.TagFilter{ - { - "keyA": {"valueA1", "valueA2"}, - }, - { - "keyA": {"valueA2", "valueA4"}, - }, - }, - }, - want: []TargetGroupWithTags{ - { - TargetGroup: &elbv2sdk.TargetGroup{TargetGroupArn: awssdk.String("tg-1"), VpcId: awssdk.String("vpc-xxxxxxx")}, - Tags: map[string]string{ - "keyA": "valueA1", - "keyB": "valueB1", - }, - }, - { - TargetGroup: &elbv2sdk.TargetGroup{TargetGroupArn: awssdk.String("tg-2"), VpcId: awssdk.String("vpc-xxxxxxx")}, - Tags: map[string]string{ - "keyA": "valueA2", - "keyB": "valueB2", - }, - }, - { - TargetGroup: &elbv2sdk.TargetGroup{TargetGroupArn: awssdk.String("tg-4"), VpcId: awssdk.String("vpc-xxxxxxx")}, - Tags: map[string]string{ - "keyA": "valueA4", - "keyB": "valueB4", - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - elbv2Client := services.NewMockELBV2(ctrl) - featureGates := config.NewFeatureGates() - for _, call := range tt.fields.describeTargetGroupsAsListCalls { - elbv2Client.EXPECT().DescribeTargetGroupsAsList(gomock.Any(), call.req).Return(call.resp, call.err) - } - for _, call := range tt.fields.describeTagsWithContextCalls { - elbv2Client.EXPECT().DescribeTagsWithContext(gomock.Any(), call.req).Return(call.resp, call.err) - } - - m := &defaultTaggingManager{ - elbv2Client: elbv2Client, - vpcID: "vpc-xxxxxxx", - describeTagsChunkSize: defaultDescribeTagsChunkSize, - featureGates: featureGates, - } - got, err := m.ListTargetGroups(context.Background(), tt.args.tagFilters...) - if tt.wantErr != nil { - assert.EqualError(t, err, tt.wantErr.Error()) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.want, got) - } - }) - } -} - -func Test_defaultTaggingManager_describeResourceTagsNative(t *testing.T) { - type describeTagsWithContextCall struct { - req *elbv2sdk.DescribeTagsInput - resp *elbv2sdk.DescribeTagsOutput - err error - } - type fields struct { - describeTagsWithContextCalls []describeTagsWithContextCall - } - type args struct { - arns []string - } - tests := []struct { - name string - fields fields - args args - want map[string]map[string]string - wantErr error - }{ - { - name: "single resource", - fields: fields{ - describeTagsWithContextCalls: []describeTagsWithContextCall{ - { - req: &elbv2sdk.DescribeTagsInput{ - ResourceArns: []*string{awssdk.String("my-arn")}, - }, - resp: &elbv2sdk.DescribeTagsOutput{ - TagDescriptions: []*elbv2sdk.TagDescription{ - { - ResourceArn: awssdk.String("my-arn"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB"), - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - arns: []string{"my-arn"}, - }, - want: map[string]map[string]string{ - "my-arn": { - "keyA": "valueA", - "keyB": "valueB", - }, - }, - }, - { - name: "multiple resource(more than chunkSize)", - fields: fields{ - describeTagsWithContextCalls: []describeTagsWithContextCall{ - { - req: &elbv2sdk.DescribeTagsInput{ - ResourceArns: []*string{awssdk.String("my-arn1"), awssdk.String("my-arn2")}, - }, - resp: &elbv2sdk.DescribeTagsOutput{ - TagDescriptions: []*elbv2sdk.TagDescription{ - { - ResourceArn: awssdk.String("my-arn1"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA1"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB1"), - }, - }, - }, - { - ResourceArn: awssdk.String("my-arn2"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA2"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB2"), - }, - }, - }, - }, - }, - }, - { - req: &elbv2sdk.DescribeTagsInput{ - ResourceArns: []*string{awssdk.String("my-arn3")}, - }, - resp: &elbv2sdk.DescribeTagsOutput{ - TagDescriptions: []*elbv2sdk.TagDescription{ - { - ResourceArn: awssdk.String("my-arn3"), - Tags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA3"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB3"), - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - arns: []string{"my-arn1", "my-arn2", "my-arn3"}, - }, - want: map[string]map[string]string{ - "my-arn1": { - "keyA": "valueA1", - "keyB": "valueB1", - }, - "my-arn2": { - "keyA": "valueA2", - "keyB": "valueB2", - }, - "my-arn3": { - "keyA": "valueA3", - "keyB": "valueB3", - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - elbv2Client := services.NewMockELBV2(ctrl) - for _, call := range tt.fields.describeTagsWithContextCalls { - elbv2Client.EXPECT().DescribeTagsWithContext(gomock.Any(), call.req).Return(call.resp, call.err) - } - - m := &defaultTaggingManager{ - elbv2Client: elbv2Client, - vpcID: "vpc-xxxxxxx", - describeTagsChunkSize: 2, - } - got, err := m.describeResourceTagsNative(context.Background(), tt.args.arns) - if tt.wantErr != nil { - assert.EqualError(t, err, tt.wantErr.Error()) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.want, got) - } - }) - } -} - -func Test_convertTagsToSDKTags(t *testing.T) { - type args struct { - tags map[string]string - } - tests := []struct { - name string - args args - want []*elbv2sdk.Tag - }{ - { - name: "non-empty case", - args: args{ - tags: map[string]string{ - "keyA": "valueA", - "keyB": "valueB", - }, - }, - want: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB"), - }, - }, - }, - { - name: "nil case", - args: args{tags: nil}, - want: nil, - }, - { - name: "empty case", - args: args{tags: map[string]string{}}, - want: nil, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := convertTagsToSDKTags(tt.args.tags) - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_convertSDKTagsToTags(t *testing.T) { - type args struct { - sdkTags []*elbv2sdk.Tag - } - tests := []struct { - name string - args args - want map[string]string - }{ - { - name: "non-empty case", - args: args{ - sdkTags: []*elbv2sdk.Tag{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB"), - }, - }, - }, - want: map[string]string{ - "keyA": "valueA", - "keyB": "valueB", - }, - }, - { - name: "nil case", - args: args{ - sdkTags: nil, - }, - want: map[string]string{}, - }, - { - name: "empty case", - args: args{ - sdkTags: []*elbv2sdk.Tag{}, - }, - want: map[string]string{}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := convertSDKTagsToTags(tt.args.sdkTags) - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/deploy/elbv2/target_group_attributes_reconciler.go b/pkg/deploy/elbv2/target_group_attributes_reconciler.go deleted file mode 100644 index 65ecbac5f..000000000 --- a/pkg/deploy/elbv2/target_group_attributes_reconciler.go +++ /dev/null @@ -1,95 +0,0 @@ -package elbv2 - -import ( - "context" - awssdk "github.com/aws/aws-sdk-go/aws" - elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/util/sets" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/algorithm" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" -) - -// reconciler for TargetGroup attributes -type TargetGroupAttributesReconciler interface { - // Reconcile TargetGroup attributes - Reconcile(ctx context.Context, resTG *elbv2model.TargetGroup, sdkTG TargetGroupWithTags) error -} - -// NewDefaultTargetGroupAttributesReconciler constructs new TargetGroupAttributesReconciler. -func NewDefaultTargetGroupAttributesReconciler(elbv2Client services.ELBV2, logger logr.Logger) *defaultTargetGroupAttributeReconciler { - return &defaultTargetGroupAttributeReconciler{ - elbv2Client: elbv2Client, - logger: logger, - } -} - -var _ TargetGroupAttributesReconciler = &defaultTargetGroupAttributeReconciler{} - -// default implementation for TargetGroupAttributesReconciler -type defaultTargetGroupAttributeReconciler struct { - elbv2Client services.ELBV2 - logger logr.Logger -} - -func (r *defaultTargetGroupAttributeReconciler) Reconcile(ctx context.Context, resTG *elbv2model.TargetGroup, sdkTG TargetGroupWithTags) error { - desiredAttrs := r.getDesiredTargetGroupAttributes(ctx, resTG) - currentAttrs, err := r.getCurrentTargetGroupAttributes(ctx, sdkTG) - if err != nil { - return err - } - - attributesToUpdate, _ := algorithm.DiffStringMap(desiredAttrs, currentAttrs) - if len(attributesToUpdate) > 0 { - req := &elbv2sdk.ModifyTargetGroupAttributesInput{ - TargetGroupArn: sdkTG.TargetGroup.TargetGroupArn, - Attributes: nil, - } - for _, attrKey := range sets.StringKeySet(attributesToUpdate).List() { - req.Attributes = append(req.Attributes, &elbv2sdk.TargetGroupAttribute{ - Key: awssdk.String(attrKey), - Value: awssdk.String(attributesToUpdate[attrKey]), - }) - } - - r.logger.Info("modifying targetGroup attributes", - "stackID", resTG.Stack().StackID(), - "resourceID", resTG.ID(), - "arn", awssdk.StringValue(sdkTG.TargetGroup.TargetGroupArn), - "change", attributesToUpdate) - if _, err := r.elbv2Client.ModifyTargetGroupAttributesWithContext(ctx, req); err != nil { - return err - } - r.logger.Info("modified targetGroup attributes", - "stackID", resTG.Stack().StackID(), - "resourceID", resTG.ID(), - "arn", awssdk.StringValue(sdkTG.TargetGroup.TargetGroupArn)) - } - return nil -} - -func (r *defaultTargetGroupAttributeReconciler) getDesiredTargetGroupAttributes(ctx context.Context, resTG *elbv2model.TargetGroup) map[string]string { - tgAttributes := make(map[string]string, len(resTG.Spec.TargetGroupAttributes)) - for _, attr := range resTG.Spec.TargetGroupAttributes { - tgAttributes[attr.Key] = attr.Value - } - return tgAttributes -} - -func (r *defaultTargetGroupAttributeReconciler) getCurrentTargetGroupAttributes(ctx context.Context, sdkTG TargetGroupWithTags) (map[string]string, error) { - req := &elbv2sdk.DescribeTargetGroupAttributesInput{ - TargetGroupArn: sdkTG.TargetGroup.TargetGroupArn, - } - - resp, err := r.elbv2Client.DescribeTargetGroupAttributesWithContext(ctx, req) - if err != nil { - return nil, err - } - - tgAttributes := make(map[string]string, len(resp.Attributes)) - for _, attr := range resp.Attributes { - tgAttributes[awssdk.StringValue(attr.Key)] = awssdk.StringValue(attr.Value) - } - return tgAttributes, nil -} diff --git a/pkg/deploy/elbv2/target_group_attributes_reconciler_test.go b/pkg/deploy/elbv2/target_group_attributes_reconciler_test.go deleted file mode 100644 index ac74415c6..000000000 --- a/pkg/deploy/elbv2/target_group_attributes_reconciler_test.go +++ /dev/null @@ -1,332 +0,0 @@ -package elbv2 - -import ( - "context" - "testing" - - awssdk "github.com/aws/aws-sdk-go/aws" - elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/go-logr/logr" - "github.com/golang/mock/gomock" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - coremodel "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" - "sigs.k8s.io/controller-runtime/pkg/log" -) - -func Test_defaultTargetGroupAttributeReconciler_Reconcile(t *testing.T) { - type describeTargetGroupAttributesWithContextCall struct { - req *elbv2sdk.DescribeTargetGroupAttributesInput - resp *elbv2sdk.DescribeTargetGroupAttributesOutput - err error - } - - type modifyTargetGroupAttributesWithContextCall struct { - req *elbv2sdk.ModifyTargetGroupAttributesInput - resp *elbv2sdk.ModifyTargetGroupAttributesOutput - err error - } - - type fields struct { - describeTargetGroupAttributesWithContextCalls []describeTargetGroupAttributesWithContextCall - modifyTargetGroupAttributesWithContextCalls []modifyTargetGroupAttributesWithContextCall - } - type args struct { - sdkTG TargetGroupWithTags - resTG *elbv2model.TargetGroup - } - - stack := coremodel.NewDefaultStack(coremodel.StackID{Namespace: "namespace", Name: "name"}) - tests := []struct { - name string - fields fields - args args - wantErr error - }{ - { - name: "multiple attributes should be updated", - fields: fields{ - describeTargetGroupAttributesWithContextCalls: []describeTargetGroupAttributesWithContextCall{ - { - req: &elbv2sdk.DescribeTargetGroupAttributesInput{ - TargetGroupArn: awssdk.String("my-arn"), - }, - resp: &elbv2sdk.DescribeTargetGroupAttributesOutput{ - Attributes: []*elbv2sdk.TargetGroupAttribute{ - { - Key: awssdk.String("slow_start.duration_second"), - Value: awssdk.String("50"), - }, - { - Key: awssdk.String("stickiness.enabled"), - Value: awssdk.String("false"), - }, - }, - }, - }, - }, - modifyTargetGroupAttributesWithContextCalls: []modifyTargetGroupAttributesWithContextCall{ - { - req: &elbv2sdk.ModifyTargetGroupAttributesInput{ - TargetGroupArn: awssdk.String("my-arn"), - Attributes: []*elbv2sdk.TargetGroupAttribute{ - { - Key: awssdk.String("slow_start.duration_second"), - Value: awssdk.String("100"), - }, - { - Key: awssdk.String("stickiness.enabled"), - Value: awssdk.String("true"), - }, - }, - }, - }, - }, - }, - args: args{ - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("my-arn"), - }, - }, - resTG: &elbv2model.TargetGroup{ - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::TargetGroup", "id-1"), - Spec: elbv2model.TargetGroupSpec{ - TargetGroupAttributes: []elbv2model.TargetGroupAttribute{ - { - Key: "slow_start.duration_second", - Value: "100", - }, - { - Key: "stickiness.enabled", - Value: "true", - }, - }, - }, - }, - }, - }, - { - name: "no attributes should be updated", - fields: fields{ - describeTargetGroupAttributesWithContextCalls: []describeTargetGroupAttributesWithContextCall{ - { - req: &elbv2sdk.DescribeTargetGroupAttributesInput{ - TargetGroupArn: awssdk.String("my-arn"), - }, - resp: &elbv2sdk.DescribeTargetGroupAttributesOutput{ - Attributes: []*elbv2sdk.TargetGroupAttribute{ - { - Key: awssdk.String("slow_start.duration_second"), - Value: awssdk.String("50"), - }, - { - Key: awssdk.String("stickiness.enabled"), - Value: awssdk.String("false"), - }, - }, - }, - }, - }, - modifyTargetGroupAttributesWithContextCalls: nil, - }, - args: args{ - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("my-arn"), - }, - }, - resTG: &elbv2model.TargetGroup{ - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::TargetGroup", "id-1"), - Spec: elbv2model.TargetGroupSpec{ - TargetGroupAttributes: []elbv2model.TargetGroupAttribute{ - { - Key: "slow_start.duration_second", - Value: "50", - }, - }, - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - elbv2Client := services.NewMockELBV2(ctrl) - for _, call := range tt.fields.describeTargetGroupAttributesWithContextCalls { - elbv2Client.EXPECT().DescribeTargetGroupAttributesWithContext(gomock.Any(), call.req).Return(call.resp, call.err) - } - for _, call := range tt.fields.modifyTargetGroupAttributesWithContextCalls { - elbv2Client.EXPECT().ModifyTargetGroupAttributesWithContext(gomock.Any(), call.req).Return(call.resp, call.err) - } - r := &defaultTargetGroupAttributeReconciler{ - elbv2Client: elbv2Client, - logger: logr.New(&log.NullLogSink{}), - } - err := r.Reconcile(context.Background(), tt.args.resTG, tt.args.sdkTG) - if tt.wantErr != nil { - assert.EqualError(t, err, tt.wantErr.Error()) - } else { - assert.NoError(t, err) - } - }) - } -} - -func Test_defaultTargetGroupAttributeReconciler_getDesiredTargetGroupAttributes(t *testing.T) { - type args struct { - resTG *elbv2model.TargetGroup - } - tests := []struct { - name string - args args - want map[string]string - }{ - { - name: "standard case", - args: args{ - resTG: &elbv2model.TargetGroup{ - Spec: elbv2model.TargetGroupSpec{ - TargetGroupAttributes: []elbv2model.TargetGroupAttribute{ - { - Key: "keyA", - Value: "valueA", - }, - { - Key: "keyB", - Value: "valueB", - }, - }, - }, - }, - }, - want: map[string]string{ - "keyA": "valueA", - "keyB": "valueB", - }, - }, - { - name: "nil attributes case", - args: args{ - resTG: &elbv2model.TargetGroup{ - Spec: elbv2model.TargetGroupSpec{ - TargetGroupAttributes: nil, - }, - }, - }, - want: map[string]string{}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := &defaultTargetGroupAttributeReconciler{} - got := r.getDesiredTargetGroupAttributes(context.Background(), tt.args.resTG) - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_defaultTargetGroupAttributeReconciler_getCurrentTargetGroupAttributes(t *testing.T) { - type describeTargetGroupAttributesWithContextCall struct { - req *elbv2sdk.DescribeTargetGroupAttributesInput - resp *elbv2sdk.DescribeTargetGroupAttributesOutput - err error - } - type fields struct { - describeTargetGroupAttributesWithContextCalls []describeTargetGroupAttributesWithContextCall - } - type args struct { - sdkTG TargetGroupWithTags - } - tests := []struct { - name string - fields fields - args args - want map[string]string - wantErr error - }{ - { - name: "standard case", - fields: fields{ - describeTargetGroupAttributesWithContextCalls: []describeTargetGroupAttributesWithContextCall{ - { - req: &elbv2sdk.DescribeTargetGroupAttributesInput{ - TargetGroupArn: awssdk.String("my-arn"), - }, - resp: &elbv2sdk.DescribeTargetGroupAttributesOutput{ - Attributes: []*elbv2sdk.TargetGroupAttribute{ - { - Key: awssdk.String("keyA"), - Value: awssdk.String("valueA"), - }, - { - Key: awssdk.String("keyB"), - Value: awssdk.String("valueB"), - }, - }, - }, - }, - }, - }, - args: args{ - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("my-arn"), - }, - Tags: nil, - }, - }, - want: map[string]string{ - "keyA": "valueA", - "keyB": "valueB", - }, - }, - { - name: "error case", - fields: fields{ - describeTargetGroupAttributesWithContextCalls: []describeTargetGroupAttributesWithContextCall{ - { - req: &elbv2sdk.DescribeTargetGroupAttributesInput{ - TargetGroupArn: awssdk.String("my-arn"), - }, - err: errors.New("some error"), - }, - }, - }, - args: args{ - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("my-arn"), - }, - Tags: nil, - }, - }, - wantErr: errors.New("some error"), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - elbv2Client := services.NewMockELBV2(ctrl) - for _, call := range tt.fields.describeTargetGroupAttributesWithContextCalls { - elbv2Client.EXPECT().DescribeTargetGroupAttributesWithContext(gomock.Any(), call.req).Return(call.resp, call.err) - } - - r := &defaultTargetGroupAttributeReconciler{ - elbv2Client: elbv2Client, - } - got, err := r.getCurrentTargetGroupAttributes(context.Background(), tt.args.sdkTG) - if tt.wantErr != nil { - assert.EqualError(t, err, tt.wantErr.Error()) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.want, got) - } - }) - } -} diff --git a/pkg/deploy/elbv2/target_group_binding_manager.go b/pkg/deploy/elbv2/target_group_binding_manager.go deleted file mode 100644 index 12959fd19..000000000 --- a/pkg/deploy/elbv2/target_group_binding_manager.go +++ /dev/null @@ -1,242 +0,0 @@ -package elbv2 - -import ( - "context" - awssdk "github.com/aws/aws-sdk-go/aws" - "github.com/go-logr/logr" - "github.com/pkg/errors" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/equality" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" - elbv2api "github.com/sonal-chauhan/aws-load-balancer-controller/apis/elbv2/v1beta1" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/tracking" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/k8s" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" - "sigs.k8s.io/controller-runtime/pkg/client" - "time" -) - -const ( - defaultWaitTGBObservedPollInterval = 200 * time.Millisecond - defaultWaitTGBObservedTimeout = 60 * time.Second - defaultWaitTGBDeletionPollInterval = 200 * time.Millisecond - defaultWaitTGBDeletionTimeout = 60 * time.Second -) - -// TargetGroupBindingManager is responsible for create/update/delete TargetGroupBinding resources. -type TargetGroupBindingManager interface { - Create(ctx context.Context, resTGB *elbv2model.TargetGroupBindingResource) (elbv2model.TargetGroupBindingResourceStatus, error) - - Update(ctx context.Context, resTGB *elbv2model.TargetGroupBindingResource, k8sTGB *elbv2api.TargetGroupBinding) (elbv2model.TargetGroupBindingResourceStatus, error) - - Delete(ctx context.Context, k8sTGB *elbv2api.TargetGroupBinding) error -} - -// NewDefaultTargetGroupBindingManager constructs new defaultTargetGroupBindingManager -func NewDefaultTargetGroupBindingManager(k8sClient client.Client, trackingProvider tracking.Provider, logger logr.Logger) *defaultTargetGroupBindingManager { - return &defaultTargetGroupBindingManager{ - k8sClient: k8sClient, - trackingProvider: trackingProvider, - logger: logger, - - waitTGBObservedPollInterval: defaultWaitTGBObservedPollInterval, - waitTGBObservedTimout: defaultWaitTGBObservedTimeout, - waitTGBDeletionPollInterval: defaultWaitTGBDeletionPollInterval, - waitTGBDeletionTimeout: defaultWaitTGBDeletionTimeout, - } -} - -var _ TargetGroupBindingManager = &defaultTargetGroupBindingManager{} - -// default implementation for TargetGroupBindingManager. -type defaultTargetGroupBindingManager struct { - k8sClient client.Client - trackingProvider tracking.Provider - logger logr.Logger - - waitTGBObservedPollInterval time.Duration - waitTGBObservedTimout time.Duration - waitTGBDeletionPollInterval time.Duration - waitTGBDeletionTimeout time.Duration -} - -func (m *defaultTargetGroupBindingManager) Create(ctx context.Context, resTGB *elbv2model.TargetGroupBindingResource) (elbv2model.TargetGroupBindingResourceStatus, error) { - k8sTGBSpec, err := buildK8sTargetGroupBindingSpec(ctx, resTGB) - if err != nil { - return elbv2model.TargetGroupBindingResourceStatus{}, err - } - - stackLabels := m.trackingProvider.StackLabels(resTGB.Stack()) - k8sTGB := &elbv2api.TargetGroupBinding{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: resTGB.Spec.Template.Namespace, - Name: resTGB.Spec.Template.Name, - Labels: stackLabels, - }, - Spec: k8sTGBSpec, - } - - m.logger.Info("creating targetGroupBinding", - "stackID", resTGB.Stack().StackID(), - "resourceID", resTGB.ID()) - if err := m.k8sClient.Create(ctx, k8sTGB); err != nil { - return elbv2model.TargetGroupBindingResourceStatus{}, err - } - m.logger.Info("created targetGroupBinding", - "stackID", resTGB.Stack().StackID(), - "resourceID", resTGB.ID(), - "targetGroupBinding", k8s.NamespacedName(k8sTGB)) - return buildResTargetGroupBindingStatus(k8sTGB), nil -} - -func (m *defaultTargetGroupBindingManager) Update(ctx context.Context, resTGB *elbv2model.TargetGroupBindingResource, k8sTGB *elbv2api.TargetGroupBinding) (elbv2model.TargetGroupBindingResourceStatus, error) { - k8sTGBSpec, err := buildK8sTargetGroupBindingSpec(ctx, resTGB) - if err != nil { - return elbv2model.TargetGroupBindingResourceStatus{}, err - } - if equality.Semantic.DeepEqual(k8sTGB.Spec, k8sTGBSpec) { - return buildResTargetGroupBindingStatus(k8sTGB), nil - } - - oldK8sTGB := k8sTGB.DeepCopy() - k8sTGB.Spec = k8sTGBSpec - m.logger.Info("modifying targetGroupBinding", - "stackID", resTGB.Stack().StackID(), - "resourceID", resTGB.ID(), - "targetGroupBinding", k8s.NamespacedName(k8sTGB)) - if err := m.k8sClient.Patch(ctx, k8sTGB, client.MergeFrom(oldK8sTGB)); err != nil { - return elbv2model.TargetGroupBindingResourceStatus{}, err - } - if err := m.waitUntilTargetGroupBindingObserved(ctx, k8sTGB); err != nil { - return elbv2model.TargetGroupBindingResourceStatus{}, err - } - m.logger.Info("modified targetGroupBinding", - "stackID", resTGB.Stack().StackID(), - "resourceID", resTGB.ID(), - "targetGroupBinding", k8s.NamespacedName(k8sTGB)) - return buildResTargetGroupBindingStatus(k8sTGB), nil -} - -func (m *defaultTargetGroupBindingManager) Delete(ctx context.Context, tgb *elbv2api.TargetGroupBinding) error { - m.logger.Info("deleting targetGroupBinding", - "targetGroupBinding", k8s.NamespacedName(tgb)) - if err := m.k8sClient.Delete(ctx, tgb); err != nil { - return err - } - if err := m.waitUntilTargetGroupBindingDeleted(ctx, tgb); err != nil { - return errors.Wrap(err, "failed to wait targetGroupBinding deletion") - } - m.logger.Info("deleted targetGroupBinding", - "targetGroupBinding", k8s.NamespacedName(tgb)) - return nil -} - -func (m *defaultTargetGroupBindingManager) waitUntilTargetGroupBindingObserved(ctx context.Context, tgb *elbv2api.TargetGroupBinding) error { - ctx, cancel := context.WithTimeout(ctx, m.waitTGBObservedTimout) - defer cancel() - - observedTGB := &elbv2api.TargetGroupBinding{} - return wait.PollImmediateUntil(m.waitTGBObservedPollInterval, func() (bool, error) { - if err := m.k8sClient.Get(ctx, k8s.NamespacedName(tgb), observedTGB); err != nil { - return false, err - } - if awssdk.Int64Value(observedTGB.Status.ObservedGeneration) >= tgb.Generation { - return true, nil - } - - return false, nil - }, ctx.Done()) -} - -func (m *defaultTargetGroupBindingManager) waitUntilTargetGroupBindingDeleted(ctx context.Context, tgb *elbv2api.TargetGroupBinding) error { - ctx, cancel := context.WithTimeout(ctx, m.waitTGBDeletionTimeout) - defer cancel() - - observedTGB := &elbv2api.TargetGroupBinding{} - return wait.PollImmediateUntil(m.waitTGBDeletionPollInterval, func() (bool, error) { - if err := m.k8sClient.Get(ctx, k8s.NamespacedName(tgb), observedTGB); err != nil { - if apierrors.IsNotFound(err) { - return true, nil - } - return false, err - } - return false, nil - }, ctx.Done()) -} - -func buildK8sTargetGroupBindingSpec(ctx context.Context, resTGB *elbv2model.TargetGroupBindingResource) (elbv2api.TargetGroupBindingSpec, error) { - tgARN, err := resTGB.Spec.Template.Spec.TargetGroupARN.Resolve(ctx) - if err != nil { - return elbv2api.TargetGroupBindingSpec{}, err - } - - k8sTGBSpec := elbv2api.TargetGroupBindingSpec{ - TargetGroupARN: tgARN, - TargetType: resTGB.Spec.Template.Spec.TargetType, - ServiceRef: resTGB.Spec.Template.Spec.ServiceRef, - } - - if resTGB.Spec.Template.Spec.Networking != nil { - k8sTGBNetworking, err := buildK8sTargetGroupBindingNetworking(ctx, *resTGB.Spec.Template.Spec.Networking) - if err != nil { - return elbv2api.TargetGroupBindingSpec{}, err - } - k8sTGBSpec.Networking = &k8sTGBNetworking - } - k8sTGBSpec.NodeSelector = resTGB.Spec.Template.Spec.NodeSelector - k8sTGBSpec.IPAddressType = resTGB.Spec.Template.Spec.IPAddressType - return k8sTGBSpec, nil -} - -func buildK8sTargetGroupBindingNetworking(ctx context.Context, resTGBNetworking elbv2model.TargetGroupBindingNetworking) (elbv2api.TargetGroupBindingNetworking, error) { - k8sIngress := make([]elbv2api.NetworkingIngressRule, 0, len(resTGBNetworking.Ingress)) - for _, rule := range resTGBNetworking.Ingress { - k8sPeers := make([]elbv2api.NetworkingPeer, 0, len(rule.From)) - for _, peer := range rule.From { - peer, err := buildK8sNetworkingPeer(ctx, peer) - if err != nil { - return elbv2api.TargetGroupBindingNetworking{}, err - } - k8sPeers = append(k8sPeers, peer) - } - k8sIngress = append(k8sIngress, elbv2api.NetworkingIngressRule{ - From: k8sPeers, - Ports: rule.Ports, - }) - } - return elbv2api.TargetGroupBindingNetworking{ - Ingress: k8sIngress, - }, nil -} - -func buildK8sNetworkingPeer(ctx context.Context, resNetworkingPeer elbv2model.NetworkingPeer) (elbv2api.NetworkingPeer, error) { - if resNetworkingPeer.IPBlock != nil { - return elbv2api.NetworkingPeer{ - IPBlock: resNetworkingPeer.IPBlock, - }, nil - } - if resNetworkingPeer.SecurityGroup != nil { - groupID, err := resNetworkingPeer.SecurityGroup.GroupID.Resolve(ctx) - if err != nil { - return elbv2api.NetworkingPeer{}, err - } - return elbv2api.NetworkingPeer{ - SecurityGroup: &elbv2api.SecurityGroup{ - GroupID: groupID, - }, - }, nil - } - return elbv2api.NetworkingPeer{}, errors.New("either ipBlock or securityGroup should be specified") -} - -func buildResTargetGroupBindingStatus(k8sTGB *elbv2api.TargetGroupBinding) elbv2model.TargetGroupBindingResourceStatus { - return elbv2model.TargetGroupBindingResourceStatus{ - TargetGroupBindingRef: corev1.ObjectReference{ - Namespace: k8sTGB.Namespace, - Name: k8sTGB.Name, - UID: k8sTGB.UID, - }, - } -} diff --git a/pkg/deploy/elbv2/target_group_binding_synthesizer.go b/pkg/deploy/elbv2/target_group_binding_synthesizer.go deleted file mode 100644 index 9b239c682..000000000 --- a/pkg/deploy/elbv2/target_group_binding_synthesizer.go +++ /dev/null @@ -1,149 +0,0 @@ -package elbv2 - -import ( - "context" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/util/sets" - elbv2api "github.com/sonal-chauhan/aws-load-balancer-controller/apis/elbv2/v1beta1" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/tracking" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -// NewTargetGroupBindingSynthesizer constructs new targetGroupBindingSynthesizer -func NewTargetGroupBindingSynthesizer(k8sClient client.Client, trackingProvider tracking.Provider, tgbManager TargetGroupBindingManager, logger logr.Logger, stack core.Stack) *targetGroupBindingSynthesizer { - return &targetGroupBindingSynthesizer{ - k8sClient: k8sClient, - trackingProvider: trackingProvider, - tgbManager: tgbManager, - logger: logger, - stack: stack, - - unmatchedK8sTGBs: nil, - } -} - -// targetGroupBindingSynthesizer is responsible for synthesize TargetGroupBinding resources types for certain stack. -type targetGroupBindingSynthesizer struct { - k8sClient client.Client - trackingProvider tracking.Provider - tgbManager TargetGroupBindingManager - logger logr.Logger - stack core.Stack - - unmatchedK8sTGBs []*elbv2api.TargetGroupBinding -} - -func (s *targetGroupBindingSynthesizer) Synthesize(ctx context.Context) error { - var resTGBs []*elbv2model.TargetGroupBindingResource - s.stack.ListResources(&resTGBs) - k8sTGBs, err := s.findK8sTargetGroupBindings(ctx) - if err != nil { - return err - } - - matchedResAndK8sTGBs, unmatchedResTGBs, unmatchedK8sTGBs, err := matchResAndK8sTargetGroupBindings(resTGBs, k8sTGBs) - if err != nil { - return err - } - s.unmatchedK8sTGBs = unmatchedK8sTGBs - - for _, resTGB := range unmatchedResTGBs { - tgbStatus, err := s.tgbManager.Create(ctx, resTGB) - if err != nil { - return err - } - resTGB.SetStatus(tgbStatus) - } - for _, resAndK8sTGB := range matchedResAndK8sTGBs { - tgbStatus, err := s.tgbManager.Update(ctx, resAndK8sTGB.resTGB, resAndK8sTGB.k8sTGB) - if err != nil { - return err - } - resAndK8sTGB.resTGB.SetStatus(tgbStatus) - } - return nil -} - -func (s *targetGroupBindingSynthesizer) PostSynthesize(ctx context.Context) error { - for _, k8sTGB := range s.unmatchedK8sTGBs { - if err := s.tgbManager.Delete(ctx, k8sTGB); err != nil { - return err - } - } - return nil -} - -func (s *targetGroupBindingSynthesizer) findK8sTargetGroupBindings(ctx context.Context) ([]*elbv2api.TargetGroupBinding, error) { - stackLabels := s.trackingProvider.StackLabels(s.stack) - - tgbList := &elbv2api.TargetGroupBindingList{} - if err := s.k8sClient.List(ctx, tgbList, client.MatchingLabels(stackLabels)); err != nil { - return nil, err - } - - tgbs := make([]*elbv2api.TargetGroupBinding, 0, len(tgbList.Items)) - for i := range tgbList.Items { - tgbs = append(tgbs, &tgbList.Items[i]) - } - return tgbs, nil -} - -type resAndK8sTargetGroupBindingPair struct { - resTGB *elbv2model.TargetGroupBindingResource - k8sTGB *elbv2api.TargetGroupBinding -} - -func matchResAndK8sTargetGroupBindings(resTGBs []*elbv2model.TargetGroupBindingResource, k8sTGBs []*elbv2api.TargetGroupBinding) ([]resAndK8sTargetGroupBindingPair, []*elbv2model.TargetGroupBindingResource, []*elbv2api.TargetGroupBinding, error) { - var matchedResAndK8sTGBs []resAndK8sTargetGroupBindingPair - var unmatchedResTGBs []*elbv2model.TargetGroupBindingResource - var unmatchedK8sTGBs []*elbv2api.TargetGroupBinding - resTGBsByARN, err := mapResTargetGroupBindingByARN(resTGBs) - if err != nil { - return nil, nil, nil, err - } - k8sTGBsByARN := mapK8sTargetGroupBindingByARN(k8sTGBs) - - resTGBARNs := sets.StringKeySet(resTGBsByARN) - k8sTGBARNs := sets.StringKeySet(k8sTGBsByARN) - for _, tgARN := range resTGBARNs.Intersection(k8sTGBARNs).List() { - resTGB := resTGBsByARN[tgARN] - k8sTGB := k8sTGBsByARN[tgARN] - matchedResAndK8sTGBs = append(matchedResAndK8sTGBs, resAndK8sTargetGroupBindingPair{ - resTGB: resTGB, - k8sTGB: k8sTGB, - }) - } - - for _, tgARN := range resTGBARNs.Difference(k8sTGBARNs).List() { - unmatchedResTGBs = append(unmatchedResTGBs, resTGBsByARN[tgARN]) - } - for _, tgARN := range k8sTGBARNs.Difference(resTGBARNs).List() { - unmatchedK8sTGBs = append(unmatchedK8sTGBs, k8sTGBsByARN[tgARN]) - } - - return matchedResAndK8sTGBs, unmatchedResTGBs, unmatchedK8sTGBs, nil -} - -func mapResTargetGroupBindingByARN(resTGBs []*elbv2model.TargetGroupBindingResource) (map[string]*elbv2model.TargetGroupBindingResource, error) { - ctx := context.Background() - resTGBsByARN := make(map[string]*elbv2model.TargetGroupBindingResource, len(resTGBs)) - for _, resTGB := range resTGBs { - tgARN, err := resTGB.Spec.Template.Spec.TargetGroupARN.Resolve(ctx) - if err != nil { - return nil, err - } - resTGBsByARN[tgARN] = resTGB - } - return resTGBsByARN, nil -} - -func mapK8sTargetGroupBindingByARN(k8sTGBs []*elbv2api.TargetGroupBinding) map[string]*elbv2api.TargetGroupBinding { - k8sTGBsByARN := make(map[string]*elbv2api.TargetGroupBinding, len(k8sTGBs)) - for _, k8sTGB := range k8sTGBs { - tgARN := k8sTGB.Spec.TargetGroupARN - k8sTGBsByARN[tgARN] = k8sTGB - } - return k8sTGBsByARN -} diff --git a/pkg/deploy/elbv2/target_group_manager.go b/pkg/deploy/elbv2/target_group_manager.go deleted file mode 100644 index c12c91220..000000000 --- a/pkg/deploy/elbv2/target_group_manager.go +++ /dev/null @@ -1,261 +0,0 @@ -package elbv2 - -import ( - "context" - awssdk "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/go-logr/logr" - "github.com/pkg/errors" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/tracking" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/runtime" - "time" -) - -const ( - defaultWaitTGDeletionPollInterval = 2 * time.Second - defaultWaitTGDeletionTimeout = 20 * time.Second -) - -// TargetGroupManager is responsible for create/update/delete TargetGroup resources. -type TargetGroupManager interface { - Create(ctx context.Context, resTG *elbv2model.TargetGroup) (elbv2model.TargetGroupStatus, error) - - Update(ctx context.Context, resTG *elbv2model.TargetGroup, sdkTG TargetGroupWithTags) (elbv2model.TargetGroupStatus, error) - - Delete(ctx context.Context, sdkTG TargetGroupWithTags) error -} - -// NewDefaultTargetGroupManager constructs new defaultTargetGroupManager. -func NewDefaultTargetGroupManager(elbv2Client services.ELBV2, trackingProvider tracking.Provider, - taggingManager TaggingManager, vpcID string, externalManagedTags []string, logger logr.Logger) *defaultTargetGroupManager { - return &defaultTargetGroupManager{ - elbv2Client: elbv2Client, - trackingProvider: trackingProvider, - taggingManager: taggingManager, - attributesReconciler: NewDefaultTargetGroupAttributesReconciler(elbv2Client, logger), - vpcID: vpcID, - externalManagedTags: externalManagedTags, - logger: logger, - - waitTGDeletionPollInterval: defaultWaitTGDeletionPollInterval, - waitTGDeletionTimeout: defaultWaitTGDeletionTimeout, - } -} - -var _ TargetGroupManager = &defaultTargetGroupManager{} - -// default implementation for TargetGroupManager -type defaultTargetGroupManager struct { - elbv2Client services.ELBV2 - trackingProvider tracking.Provider - taggingManager TaggingManager - attributesReconciler TargetGroupAttributesReconciler - vpcID string - externalManagedTags []string - - logger logr.Logger - - waitTGDeletionPollInterval time.Duration - waitTGDeletionTimeout time.Duration -} - -func (m *defaultTargetGroupManager) Create(ctx context.Context, resTG *elbv2model.TargetGroup) (elbv2model.TargetGroupStatus, error) { - req := buildSDKCreateTargetGroupInput(resTG.Spec) - req.VpcId = awssdk.String(m.vpcID) - tgTags := m.trackingProvider.ResourceTags(resTG.Stack(), resTG, resTG.Spec.Tags) - req.Tags = convertTagsToSDKTags(tgTags) - - m.logger.Info("creating targetGroup", - "stackID", resTG.Stack().StackID(), - "resourceID", resTG.ID()) - resp, err := m.elbv2Client.CreateTargetGroupWithContext(ctx, req) - if err != nil { - return elbv2model.TargetGroupStatus{}, err - } - sdkTG := TargetGroupWithTags{ - TargetGroup: resp.TargetGroups[0], - Tags: tgTags, - } - m.logger.Info("created targetGroup", - "stackID", resTG.Stack().StackID(), - "resourceID", resTG.ID(), - "arn", awssdk.StringValue(sdkTG.TargetGroup.TargetGroupArn)) - if err := m.attributesReconciler.Reconcile(ctx, resTG, sdkTG); err != nil { - return elbv2model.TargetGroupStatus{}, err - } - - return buildResTargetGroupStatus(sdkTG), nil -} - -func (m *defaultTargetGroupManager) Update(ctx context.Context, resTG *elbv2model.TargetGroup, sdkTG TargetGroupWithTags) (elbv2model.TargetGroupStatus, error) { - if err := m.updateSDKTargetGroupWithTags(ctx, resTG, sdkTG); err != nil { - return elbv2model.TargetGroupStatus{}, err - } - if err := m.updateSDKTargetGroupWithHealthCheck(ctx, resTG, sdkTG); err != nil { - return elbv2model.TargetGroupStatus{}, err - } - if err := m.attributesReconciler.Reconcile(ctx, resTG, sdkTG); err != nil { - return elbv2model.TargetGroupStatus{}, err - } - - return buildResTargetGroupStatus(sdkTG), nil -} - -func (m *defaultTargetGroupManager) Delete(ctx context.Context, sdkTG TargetGroupWithTags) error { - req := &elbv2sdk.DeleteTargetGroupInput{ - TargetGroupArn: sdkTG.TargetGroup.TargetGroupArn, - } - - m.logger.Info("deleting targetGroup", - "arn", awssdk.StringValue(req.TargetGroupArn)) - if err := runtime.RetryImmediateOnError(m.waitTGDeletionPollInterval, m.waitTGDeletionTimeout, isTargetGroupResourceInUseError, func() error { - _, err := m.elbv2Client.DeleteTargetGroupWithContext(ctx, req) - return err - }); err != nil { - return errors.Wrap(err, "failed to delete targetGroup") - } - m.logger.Info("deleted targetGroup", - "arn", awssdk.StringValue(req.TargetGroupArn)) - - return nil -} - -func (m *defaultTargetGroupManager) updateSDKTargetGroupWithHealthCheck(ctx context.Context, resTG *elbv2model.TargetGroup, sdkTG TargetGroupWithTags) error { - if !isSDKTargetGroupHealthCheckDrifted(resTG.Spec, sdkTG) { - return nil - } - req := buildSDKModifyTargetGroupInput(resTG.Spec) - req.TargetGroupArn = sdkTG.TargetGroup.TargetGroupArn - - m.logger.Info("modifying targetGroup healthCheck", - "stackID", resTG.Stack().StackID(), - "resourceID", resTG.ID(), - "arn", awssdk.StringValue(sdkTG.TargetGroup.TargetGroupArn)) - if _, err := m.elbv2Client.ModifyTargetGroupWithContext(ctx, req); err != nil { - return err - } - m.logger.Info("modified targetGroup healthCheck", - "stackID", resTG.Stack().StackID(), - "resourceID", resTG.ID(), - "arn", awssdk.StringValue(sdkTG.TargetGroup.TargetGroupArn)) - - return nil -} - -func (m *defaultTargetGroupManager) updateSDKTargetGroupWithTags(ctx context.Context, resTG *elbv2model.TargetGroup, sdkTG TargetGroupWithTags) error { - desiredTGTags := m.trackingProvider.ResourceTags(resTG.Stack(), resTG, resTG.Spec.Tags) - return m.taggingManager.ReconcileTags(ctx, awssdk.StringValue(sdkTG.TargetGroup.TargetGroupArn), desiredTGTags, - WithCurrentTags(sdkTG.Tags), - WithIgnoredTagKeys(m.trackingProvider.LegacyTagKeys()), - WithIgnoredTagKeys(m.externalManagedTags)) -} - -func isSDKTargetGroupHealthCheckDrifted(tgSpec elbv2model.TargetGroupSpec, sdkTG TargetGroupWithTags) bool { - if tgSpec.HealthCheckConfig == nil { - return false - } - sdkObj := sdkTG.TargetGroup - hcConfig := *tgSpec.HealthCheckConfig - if hcConfig.Port != nil && hcConfig.Port.String() != awssdk.StringValue(sdkObj.HealthCheckPort) { - return true - } - if hcConfig.Protocol != nil && string(*hcConfig.Protocol) != awssdk.StringValue(sdkObj.HealthCheckProtocol) { - return true - } - if hcConfig.Path != nil && awssdk.StringValue(hcConfig.Path) != awssdk.StringValue(sdkObj.HealthCheckPath) { - return true - } - if hcConfig.Matcher != nil && (sdkObj.Matcher == nil || awssdk.StringValue(hcConfig.Matcher.GRPCCode) != awssdk.StringValue(sdkObj.Matcher.GrpcCode) || awssdk.StringValue(hcConfig.Matcher.HTTPCode) != awssdk.StringValue(sdkObj.Matcher.HttpCode)) { - return true - } - if hcConfig.IntervalSeconds != nil && awssdk.Int64Value(hcConfig.IntervalSeconds) != awssdk.Int64Value(sdkObj.HealthCheckIntervalSeconds) { - return true - } - if hcConfig.TimeoutSeconds != nil && awssdk.Int64Value(hcConfig.TimeoutSeconds) != awssdk.Int64Value(sdkObj.HealthCheckTimeoutSeconds) { - return true - } - if hcConfig.HealthyThresholdCount != nil && awssdk.Int64Value(hcConfig.HealthyThresholdCount) != awssdk.Int64Value(sdkObj.HealthyThresholdCount) { - return true - } - if hcConfig.UnhealthyThresholdCount != nil && awssdk.Int64Value(hcConfig.UnhealthyThresholdCount) != awssdk.Int64Value(sdkObj.UnhealthyThresholdCount) { - return true - } - return false -} - -func buildSDKCreateTargetGroupInput(tgSpec elbv2model.TargetGroupSpec) *elbv2sdk.CreateTargetGroupInput { - sdkObj := &elbv2sdk.CreateTargetGroupInput{} - sdkObj.Name = awssdk.String(tgSpec.Name) - sdkObj.TargetType = awssdk.String(string(tgSpec.TargetType)) - sdkObj.Port = awssdk.Int64(tgSpec.Port) - sdkObj.Protocol = awssdk.String(string(tgSpec.Protocol)) - if tgSpec.IPAddressType != nil && *tgSpec.IPAddressType != elbv2model.TargetGroupIPAddressTypeIPv4 { - sdkObj.IpAddressType = (*string)(tgSpec.IPAddressType) - } - if tgSpec.ProtocolVersion != nil { - sdkObj.ProtocolVersion = (*string)(tgSpec.ProtocolVersion) - } - if tgSpec.HealthCheckConfig != nil { - hcConfig := *tgSpec.HealthCheckConfig - sdkObj.HealthCheckEnabled = awssdk.Bool(true) - if hcConfig.Port != nil { - sdkObj.HealthCheckPort = awssdk.String(hcConfig.Port.String()) - } - sdkObj.HealthCheckProtocol = (*string)(hcConfig.Protocol) - sdkObj.HealthCheckPath = hcConfig.Path - if tgSpec.HealthCheckConfig.Matcher != nil { - sdkObj.Matcher = buildSDKMatcher(*hcConfig.Matcher) - } - sdkObj.HealthCheckIntervalSeconds = hcConfig.IntervalSeconds - sdkObj.HealthCheckTimeoutSeconds = hcConfig.TimeoutSeconds - sdkObj.HealthyThresholdCount = hcConfig.HealthyThresholdCount - sdkObj.UnhealthyThresholdCount = hcConfig.UnhealthyThresholdCount - } - - return sdkObj -} - -func buildSDKModifyTargetGroupInput(tgSpec elbv2model.TargetGroupSpec) *elbv2sdk.ModifyTargetGroupInput { - sdkObj := &elbv2sdk.ModifyTargetGroupInput{} - if tgSpec.HealthCheckConfig != nil { - hcConfig := *tgSpec.HealthCheckConfig - sdkObj.HealthCheckEnabled = awssdk.Bool(true) - if hcConfig.Port != nil { - sdkObj.HealthCheckPort = awssdk.String(hcConfig.Port.String()) - } - sdkObj.HealthCheckProtocol = (*string)(hcConfig.Protocol) - sdkObj.HealthCheckPath = hcConfig.Path - if tgSpec.HealthCheckConfig.Matcher != nil { - sdkObj.Matcher = buildSDKMatcher(*hcConfig.Matcher) - } - sdkObj.HealthCheckIntervalSeconds = hcConfig.IntervalSeconds - sdkObj.HealthCheckTimeoutSeconds = hcConfig.TimeoutSeconds - sdkObj.HealthyThresholdCount = hcConfig.HealthyThresholdCount - sdkObj.UnhealthyThresholdCount = hcConfig.UnhealthyThresholdCount - } - return sdkObj -} - -func buildSDKMatcher(modelMatcher elbv2model.HealthCheckMatcher) *elbv2sdk.Matcher { - return &elbv2sdk.Matcher{ - GrpcCode: modelMatcher.GRPCCode, - HttpCode: modelMatcher.HTTPCode, - } -} - -func buildResTargetGroupStatus(sdkTG TargetGroupWithTags) elbv2model.TargetGroupStatus { - return elbv2model.TargetGroupStatus{ - TargetGroupARN: awssdk.StringValue(sdkTG.TargetGroup.TargetGroupArn), - } -} - -func isTargetGroupResourceInUseError(err error) bool { - var awsErr awserr.Error - if errors.As(err, &awsErr) { - return awsErr.Code() == "ResourceInUse" - } - return false -} diff --git a/pkg/deploy/elbv2/target_group_manager_test.go b/pkg/deploy/elbv2/target_group_manager_test.go deleted file mode 100644 index 78013b813..000000000 --- a/pkg/deploy/elbv2/target_group_manager_test.go +++ /dev/null @@ -1,682 +0,0 @@ -package elbv2 - -import ( - awssdk "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/util/intstr" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" - "testing" -) - -func Test_isSDKTargetGroupHealthCheckDrifted(t *testing.T) { - port9090 := intstr.FromInt(9090) - protocolHTTP := elbv2model.ProtocolHTTP - type args struct { - tgSpec elbv2model.TargetGroupSpec - sdkTG TargetGroupWithTags - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "healthCheck isn't drifted", - args: args{ - tgSpec: elbv2model.TargetGroupSpec{ - Name: "my-tg", - TargetType: elbv2model.TargetTypeIP, - Port: 8080, - Protocol: elbv2model.ProtocolHTTP, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port9090, - Protocol: &protocolHTTP, - Path: awssdk.String("/healthcheck"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckIntervalSeconds: awssdk.Int64(10), - HealthCheckPath: awssdk.String("/healthcheck"), - HealthCheckPort: awssdk.String("9090"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - Matcher: &elbv2sdk.Matcher{HttpCode: awssdk.String("200")}, - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - want: false, - }, - { - name: "port changed", - args: args{ - tgSpec: elbv2model.TargetGroupSpec{ - Name: "my-tg", - TargetType: elbv2model.TargetTypeIP, - Port: 8080, - Protocol: elbv2model.ProtocolHTTP, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port9090, - Protocol: &protocolHTTP, - Path: awssdk.String("/healthcheck"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckIntervalSeconds: awssdk.Int64(10), - HealthCheckPath: awssdk.String("/healthcheck"), - HealthCheckPort: awssdk.String("9091"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - Matcher: &elbv2sdk.Matcher{HttpCode: awssdk.String("200")}, - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - want: true, - }, - { - name: "protocol changed", - args: args{ - tgSpec: elbv2model.TargetGroupSpec{ - Name: "my-tg", - TargetType: elbv2model.TargetTypeIP, - Port: 8080, - Protocol: elbv2model.ProtocolHTTP, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port9090, - Protocol: &protocolHTTP, - Path: awssdk.String("/healthcheck"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckIntervalSeconds: awssdk.Int64(10), - HealthCheckPath: awssdk.String("/healthcheck"), - HealthCheckPort: awssdk.String("9090"), - HealthCheckProtocol: awssdk.String("TCP"), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - Matcher: &elbv2sdk.Matcher{HttpCode: awssdk.String("200")}, - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - want: true, - }, - { - name: "HealthCheckPath changed", - args: args{ - tgSpec: elbv2model.TargetGroupSpec{ - Name: "my-tg", - TargetType: elbv2model.TargetTypeIP, - Port: 8080, - Protocol: elbv2model.ProtocolHTTP, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port9090, - Protocol: &protocolHTTP, - Path: awssdk.String("/healthcheck"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckIntervalSeconds: awssdk.Int64(10), - HealthCheckPath: awssdk.String("/some-other-path"), - HealthCheckPort: awssdk.String("9090"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - Matcher: &elbv2sdk.Matcher{HttpCode: awssdk.String("200")}, - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - want: true, - }, - { - name: "matcher changed", - args: args{ - tgSpec: elbv2model.TargetGroupSpec{ - Name: "my-tg", - TargetType: elbv2model.TargetTypeIP, - Port: 8080, - Protocol: elbv2model.ProtocolHTTP, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port9090, - Protocol: &protocolHTTP, - Path: awssdk.String("/healthcheck"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckIntervalSeconds: awssdk.Int64(10), - HealthCheckPath: awssdk.String("/healthcheck"), - HealthCheckPort: awssdk.String("9090"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - Matcher: &elbv2sdk.Matcher{HttpCode: awssdk.String("503")}, - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - want: true, - }, - { - name: "matcher GrpcCode changed", - args: args{ - tgSpec: elbv2model.TargetGroupSpec{ - Name: "my-tg", - TargetType: elbv2model.TargetTypeIP, - Port: 8080, - Protocol: elbv2model.ProtocolHTTP, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port9090, - Protocol: &protocolHTTP, - Path: awssdk.String("/healthcheck"), - Matcher: &elbv2model.HealthCheckMatcher{GRPCCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckIntervalSeconds: awssdk.Int64(10), - HealthCheckPath: awssdk.String("/healthcheck"), - HealthCheckPort: awssdk.String("9090"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - Matcher: &elbv2sdk.Matcher{GrpcCode: awssdk.String("503")}, - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - want: true, - }, - { - name: "intervalSeconds changed", - args: args{ - tgSpec: elbv2model.TargetGroupSpec{ - Name: "my-tg", - TargetType: elbv2model.TargetTypeIP, - Port: 8080, - Protocol: elbv2model.ProtocolHTTP, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port9090, - Protocol: &protocolHTTP, - Path: awssdk.String("/healthcheck"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckIntervalSeconds: awssdk.Int64(11), - HealthCheckPath: awssdk.String("/healthcheck"), - HealthCheckPort: awssdk.String("9090"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - Matcher: &elbv2sdk.Matcher{HttpCode: awssdk.String("200")}, - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - want: true, - }, - { - name: "timeoutSeconds changed", - args: args{ - tgSpec: elbv2model.TargetGroupSpec{ - Name: "my-tg", - TargetType: elbv2model.TargetTypeIP, - Port: 8080, - Protocol: elbv2model.ProtocolHTTP, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port9090, - Protocol: &protocolHTTP, - Path: awssdk.String("/healthcheck"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckIntervalSeconds: awssdk.Int64(10), - HealthCheckPath: awssdk.String("/healthcheck"), - HealthCheckPort: awssdk.String("9090"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckTimeoutSeconds: awssdk.Int64(6), - HealthyThresholdCount: awssdk.Int64(3), - Matcher: &elbv2sdk.Matcher{HttpCode: awssdk.String("200")}, - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - want: true, - }, - { - name: "healthyThresholdCount changed", - args: args{ - tgSpec: elbv2model.TargetGroupSpec{ - Name: "my-tg", - TargetType: elbv2model.TargetTypeIP, - Port: 8080, - Protocol: elbv2model.ProtocolHTTP, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port9090, - Protocol: &protocolHTTP, - Path: awssdk.String("/healthcheck"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckIntervalSeconds: awssdk.Int64(10), - HealthCheckPath: awssdk.String("/healthcheck"), - HealthCheckPort: awssdk.String("9090"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(4), - Matcher: &elbv2sdk.Matcher{HttpCode: awssdk.String("200")}, - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - want: true, - }, - { - name: "UnhealthyThresholdCount changed", - args: args{ - tgSpec: elbv2model.TargetGroupSpec{ - Name: "my-tg", - TargetType: elbv2model.TargetTypeIP, - Port: 8080, - Protocol: elbv2model.ProtocolHTTP, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port9090, - Protocol: &protocolHTTP, - Path: awssdk.String("/healthcheck"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckIntervalSeconds: awssdk.Int64(10), - HealthCheckPath: awssdk.String("/healthcheck"), - HealthCheckPort: awssdk.String("9090"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - Matcher: &elbv2sdk.Matcher{HttpCode: awssdk.String("200")}, - UnhealthyThresholdCount: awssdk.Int64(3), - }, - }, - }, - want: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := isSDKTargetGroupHealthCheckDrifted(tt.args.tgSpec, tt.args.sdkTG) - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_buildSDKCreateTargetGroupInput(t *testing.T) { - port9090 := intstr.FromInt(9090) - protocolHTTP := elbv2model.ProtocolHTTP - protocolVersionHTTP2 := elbv2model.ProtocolVersionHTTP2 - ipAddressTypeIPv4 := elbv2model.TargetGroupIPAddressTypeIPv4 - ipAddressTypeIPv6 := elbv2model.TargetGroupIPAddressTypeIPv6 - type args struct { - tgSpec elbv2model.TargetGroupSpec - } - tests := []struct { - name string - args args - want *elbv2sdk.CreateTargetGroupInput - }{ - { - name: "standard case", - args: args{ - tgSpec: elbv2model.TargetGroupSpec{ - Name: "my-tg", - TargetType: elbv2model.TargetTypeIP, - Port: 8080, - Protocol: elbv2model.ProtocolHTTP, - IPAddressType: &ipAddressTypeIPv4, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port9090, - Protocol: &protocolHTTP, - Path: awssdk.String("/healthcheck"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - want: &elbv2sdk.CreateTargetGroupInput{ - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckIntervalSeconds: awssdk.Int64(10), - HealthCheckPath: awssdk.String("/healthcheck"), - HealthCheckPort: awssdk.String("9090"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - Matcher: &elbv2sdk.Matcher{HttpCode: awssdk.String("200")}, - UnhealthyThresholdCount: awssdk.Int64(2), - Name: awssdk.String("my-tg"), - Port: awssdk.Int64(8080), - Protocol: awssdk.String("HTTP"), - TargetType: awssdk.String("ip"), - }, - }, - { - name: "standard case with protocol version", - args: args{ - tgSpec: elbv2model.TargetGroupSpec{ - Name: "my-tg", - TargetType: elbv2model.TargetTypeIP, - Port: 8080, - Protocol: elbv2model.ProtocolHTTP, - ProtocolVersion: &protocolVersionHTTP2, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port9090, - Protocol: &protocolHTTP, - Path: awssdk.String("/healthcheck"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - want: &elbv2sdk.CreateTargetGroupInput{ - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckIntervalSeconds: awssdk.Int64(10), - HealthCheckPath: awssdk.String("/healthcheck"), - HealthCheckPort: awssdk.String("9090"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - Matcher: &elbv2sdk.Matcher{HttpCode: awssdk.String("200")}, - UnhealthyThresholdCount: awssdk.Int64(2), - Name: awssdk.String("my-tg"), - Port: awssdk.Int64(8080), - Protocol: awssdk.String("HTTP"), - ProtocolVersion: awssdk.String("HTTP2"), - TargetType: awssdk.String("ip"), - }, - }, - { - name: "standard case ipv6 address", - args: args{ - tgSpec: elbv2model.TargetGroupSpec{ - Name: "my-tg", - TargetType: elbv2model.TargetTypeIP, - Port: 8080, - Protocol: elbv2model.ProtocolHTTP, - IPAddressType: &ipAddressTypeIPv6, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port9090, - Protocol: &protocolHTTP, - Path: awssdk.String("/healthcheck"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - want: &elbv2sdk.CreateTargetGroupInput{ - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckIntervalSeconds: awssdk.Int64(10), - HealthCheckPath: awssdk.String("/healthcheck"), - HealthCheckPort: awssdk.String("9090"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - Matcher: &elbv2sdk.Matcher{HttpCode: awssdk.String("200")}, - UnhealthyThresholdCount: awssdk.Int64(2), - Name: awssdk.String("my-tg"), - Port: awssdk.Int64(8080), - Protocol: awssdk.String("HTTP"), - TargetType: awssdk.String("ip"), - IpAddressType: awssdk.String("ipv6"), - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := buildSDKCreateTargetGroupInput(tt.args.tgSpec) - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_buildSDKModifyTargetGroupInput(t *testing.T) { - port9090 := intstr.FromInt(9090) - protocolHTTP := elbv2model.ProtocolHTTP - type args struct { - tgSpec elbv2model.TargetGroupSpec - } - tests := []struct { - name string - args args - want *elbv2sdk.ModifyTargetGroupInput - }{ - { - name: "standard case", - args: args{ - tgSpec: elbv2model.TargetGroupSpec{ - Name: "my-tg", - TargetType: elbv2model.TargetTypeIP, - Port: 8080, - Protocol: elbv2model.ProtocolHTTP, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port9090, - Protocol: &protocolHTTP, - Path: awssdk.String("/healthcheck"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - want: &elbv2sdk.ModifyTargetGroupInput{ - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckIntervalSeconds: awssdk.Int64(10), - HealthCheckPath: awssdk.String("/healthcheck"), - HealthCheckPort: awssdk.String("9090"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - Matcher: &elbv2sdk.Matcher{HttpCode: awssdk.String("200")}, - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := buildSDKModifyTargetGroupInput(tt.args.tgSpec) - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_buildSDKMatcher(t *testing.T) { - type args struct { - modelMatcher elbv2model.HealthCheckMatcher - protocolVersion elbv2model.ProtocolVersion - } - tests := []struct { - name string - args args - want *elbv2sdk.Matcher - }{ - { - name: "standard case", - args: args{ - modelMatcher: elbv2model.HealthCheckMatcher{ - HTTPCode: awssdk.String("200"), - }, - protocolVersion: elbv2model.ProtocolVersionHTTP1, - }, - want: &elbv2sdk.Matcher{ - HttpCode: awssdk.String("200"), - }, - }, - { - name: "grpc case", - args: args{ - modelMatcher: elbv2model.HealthCheckMatcher{ - GRPCCode: awssdk.String("2"), - }, - protocolVersion: elbv2model.ProtocolVersionGRPC, - }, - want: &elbv2sdk.Matcher{ - GrpcCode: awssdk.String("2"), - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := buildSDKMatcher(tt.args.modelMatcher) - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_buildResTargetGroupStatus(t *testing.T) { - type args struct { - sdkTG TargetGroupWithTags - } - tests := []struct { - name string - args args - want elbv2model.TargetGroupStatus - }{ - { - name: "standard case", - args: args{ - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("my-arn"), - }, - }, - }, - want: elbv2model.TargetGroupStatus{TargetGroupARN: "my-arn"}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := buildResTargetGroupStatus(tt.args.sdkTG) - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_isTargetGroupResourceInUseError(t *testing.T) { - type args struct { - err error - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "is ResourceInUse error", - args: args{ - err: awserr.New("ResourceInUse", "some message", nil), - }, - want: true, - }, - { - name: "wraps ResourceInUse error", - args: args{ - err: errors.Wrap(awserr.New("ResourceInUse", "some message", nil), "wrapped message"), - }, - want: true, - }, - { - name: "isn't ResourceInUse error", - args: args{ - err: errors.New("some other error"), - }, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := isTargetGroupResourceInUseError(tt.args.err) - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/deploy/elbv2/target_group_synthesizer.go b/pkg/deploy/elbv2/target_group_synthesizer.go deleted file mode 100644 index a57b2365d..000000000 --- a/pkg/deploy/elbv2/target_group_synthesizer.go +++ /dev/null @@ -1,205 +0,0 @@ -package elbv2 - -import ( - "context" - awssdk "github.com/aws/aws-sdk-go/aws" - "github.com/go-logr/logr" - "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/util/sets" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/config" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/tracking" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" -) - -// NewTargetGroupSynthesizer constructs targetGroupSynthesizer -func NewTargetGroupSynthesizer(elbv2Client services.ELBV2, trackingProvider tracking.Provider, taggingManager TaggingManager, - tgManager TargetGroupManager, logger logr.Logger, featureGates config.FeatureGates, stack core.Stack) *targetGroupSynthesizer { - return &targetGroupSynthesizer{ - elbv2Client: elbv2Client, - trackingProvider: trackingProvider, - taggingManager: taggingManager, - tgManager: tgManager, - featureGates: featureGates, - logger: logger, - stack: stack, - unmatchedSDKTGs: nil, - } -} - -// targetGroupSynthesizer is responsible for synthesize TargetGroup resources types for certain stack. -type targetGroupSynthesizer struct { - elbv2Client services.ELBV2 - trackingProvider tracking.Provider - taggingManager TaggingManager - tgManager TargetGroupManager - featureGates config.FeatureGates - logger logr.Logger - - stack core.Stack - unmatchedSDKTGs []TargetGroupWithTags -} - -func (s *targetGroupSynthesizer) Synthesize(ctx context.Context) error { - var resTGs []*elbv2model.TargetGroup - s.stack.ListResources(&resTGs) - sdkTGs, err := s.findSDKTargetGroups(ctx) - if err != nil { - return err - } - matchedResAndSDKTGs, unmatchedResTGs, unmatchedSDKTGs, err := matchResAndSDKTargetGroups(resTGs, sdkTGs, - s.trackingProvider.ResourceIDTagKey(), s.featureGates) - if err != nil { - return err - } - - // For TargetGroups, we delete unmatched ones during post synthesize given below facts: - // * unmatched targetGroups might still be use by a listener rule. - s.unmatchedSDKTGs = unmatchedSDKTGs - - for _, resTG := range unmatchedResTGs { - tgStatus, err := s.tgManager.Create(ctx, resTG) - if err != nil { - return err - } - resTG.SetStatus(tgStatus) - } - for _, resAndSDKTG := range matchedResAndSDKTGs { - tgStatus, err := s.tgManager.Update(ctx, resAndSDKTG.resTG, resAndSDKTG.sdkTG) - if err != nil { - return err - } - resAndSDKTG.resTG.SetStatus(tgStatus) - } - return nil -} - -func (s *targetGroupSynthesizer) PostSynthesize(ctx context.Context) error { - for _, sdkTG := range s.unmatchedSDKTGs { - if err := s.tgManager.Delete(ctx, sdkTG); err != nil { - return err - } - } - return nil -} - -// findSDKTargetGroups will find all AWS TargetGroups created for stack. -func (s *targetGroupSynthesizer) findSDKTargetGroups(ctx context.Context) ([]TargetGroupWithTags, error) { - stackTags := s.trackingProvider.StackTags(s.stack) - stackTagsLegacy := s.trackingProvider.StackTagsLegacy(s.stack) - return s.taggingManager.ListTargetGroups(ctx, - tracking.TagsAsTagFilter(stackTags), - tracking.TagsAsTagFilter(stackTagsLegacy)) -} - -type resAndSDKTargetGroupPair struct { - resTG *elbv2model.TargetGroup - sdkTG TargetGroupWithTags -} - -func matchResAndSDKTargetGroups(resTGs []*elbv2model.TargetGroup, sdkTGs []TargetGroupWithTags, - resourceIDTagKey string, featureGates config.FeatureGates) ([]resAndSDKTargetGroupPair, []*elbv2model.TargetGroup, []TargetGroupWithTags, error) { - var matchedResAndSDKTGs []resAndSDKTargetGroupPair - var unmatchedResTGs []*elbv2model.TargetGroup - var unmatchedSDKTGs []TargetGroupWithTags - - resTGsByID := mapResTargetGroupByResourceID(resTGs) - sdkTGsByID, err := mapSDKTargetGroupByResourceID(sdkTGs, resourceIDTagKey) - if err != nil { - return nil, nil, nil, err - } - - resTGIDs := sets.StringKeySet(resTGsByID) - sdkTGIDs := sets.StringKeySet(sdkTGsByID) - for _, resID := range resTGIDs.Intersection(sdkTGIDs).List() { - resTG := resTGsByID[resID] - sdkTGs := sdkTGsByID[resID] - foundMatch := false - for _, sdkTG := range sdkTGs { - if isSDKTargetGroupRequiresReplacement(sdkTG, resTG, featureGates) { - unmatchedSDKTGs = append(unmatchedSDKTGs, sdkTG) - continue - } - matchedResAndSDKTGs = append(matchedResAndSDKTGs, resAndSDKTargetGroupPair{ - resTG: resTG, - sdkTG: sdkTG, - }) - foundMatch = true - } - if !foundMatch { - unmatchedResTGs = append(unmatchedResTGs, resTG) - } - } - for _, resID := range resTGIDs.Difference(sdkTGIDs).List() { - unmatchedResTGs = append(unmatchedResTGs, resTGsByID[resID]) - } - for _, resID := range sdkTGIDs.Difference(resTGIDs).List() { - unmatchedSDKTGs = append(unmatchedSDKTGs, sdkTGsByID[resID]...) - } - - return matchedResAndSDKTGs, unmatchedResTGs, unmatchedSDKTGs, nil -} - -func mapResTargetGroupByResourceID(resTGs []*elbv2model.TargetGroup) map[string]*elbv2model.TargetGroup { - resTGsByID := make(map[string]*elbv2model.TargetGroup, len(resTGs)) - for _, resTG := range resTGs { - resTGsByID[resTG.ID()] = resTG - } - return resTGsByID -} - -func mapSDKTargetGroupByResourceID(sdkTGs []TargetGroupWithTags, resourceIDTagKey string) (map[string][]TargetGroupWithTags, error) { - sdkTGsByID := make(map[string][]TargetGroupWithTags, len(sdkTGs)) - for _, sdkTG := range sdkTGs { - resourceID, ok := sdkTG.Tags[resourceIDTagKey] - if !ok { - return nil, errors.Errorf("unexpected targetGroup with no resourceID: %v", awssdk.StringValue(sdkTG.TargetGroup.TargetGroupArn)) - } - sdkTGsByID[resourceID] = append(sdkTGsByID[resourceID], sdkTG) - } - return sdkTGsByID, nil -} - -// isSDKTargetGroupRequiresReplacement checks whether a sdk TargetGroup requires replacement to fulfill a TargetGroup resource. -func isSDKTargetGroupRequiresReplacement(sdkTG TargetGroupWithTags, resTG *elbv2model.TargetGroup, featureGates config.FeatureGates) bool { - if string(resTG.Spec.TargetType) != awssdk.StringValue(sdkTG.TargetGroup.TargetType) { - return true - } - if string(resTG.Spec.Protocol) != awssdk.StringValue(sdkTG.TargetGroup.Protocol) { - return true - } - if resTG.Spec.ProtocolVersion != nil { - if string(*resTG.Spec.ProtocolVersion) != awssdk.StringValue(sdkTG.TargetGroup.ProtocolVersion) { - return true - } - } - - return isSDKTargetGroupRequiresReplacementDueToNLBHealthCheck(sdkTG, resTG, featureGates) -} - -// most of the healthCheck settings for NLB targetGroups cannot be changed for now. -func isSDKTargetGroupRequiresReplacementDueToNLBHealthCheck(sdkTG TargetGroupWithTags, resTG *elbv2model.TargetGroup, featureGates config.FeatureGates) bool { - if resTG.Spec.HealthCheckConfig == nil || featureGates.Enabled(config.NLBHealthCheckAdvancedConfig) { - return false - } - if resTG.Spec.Protocol != elbv2model.ProtocolTCP && resTG.Spec.Protocol != elbv2model.ProtocolUDP && - resTG.Spec.Protocol != elbv2model.ProtocolTCP_UDP && resTG.Spec.Protocol != elbv2model.ProtocolTLS { - return false - } - sdkObj := sdkTG.TargetGroup - hcConfig := *resTG.Spec.HealthCheckConfig - if hcConfig.Protocol != nil && string(*hcConfig.Protocol) != awssdk.StringValue(sdkObj.HealthCheckProtocol) { - return true - } - if hcConfig.Matcher != nil && (sdkObj.Matcher == nil || awssdk.StringValue(hcConfig.Matcher.GRPCCode) != awssdk.StringValue(sdkObj.Matcher.GrpcCode) || awssdk.StringValue(hcConfig.Matcher.HTTPCode) != awssdk.StringValue(sdkObj.Matcher.HttpCode)) { - return true - } - if hcConfig.IntervalSeconds != nil && awssdk.Int64Value(hcConfig.IntervalSeconds) != awssdk.Int64Value(sdkObj.HealthCheckIntervalSeconds) { - return true - } - if hcConfig.TimeoutSeconds != nil && awssdk.Int64Value(hcConfig.TimeoutSeconds) != awssdk.Int64Value(sdkObj.HealthCheckTimeoutSeconds) { - return true - } - return false -} diff --git a/pkg/deploy/elbv2/target_group_synthesizer_test.go b/pkg/deploy/elbv2/target_group_synthesizer_test.go deleted file mode 100644 index f65382267..000000000 --- a/pkg/deploy/elbv2/target_group_synthesizer_test.go +++ /dev/null @@ -1,994 +0,0 @@ -package elbv2 - -import ( - awssdk "github.com/aws/aws-sdk-go/aws" - elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/util/intstr" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/config" - coremodel "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" - "testing" -) - -func Test_matchResAndSDKTargetGroups(t *testing.T) { - stack := coremodel.NewDefaultStack(coremodel.StackID{Namespace: "namespace", Name: "name"}) - type args struct { - resTGs []*elbv2model.TargetGroup - sdkTGs []TargetGroupWithTags - resourceIDTagKey string - } - tests := []struct { - name string - args args - want []resAndSDKTargetGroupPair - want1 []*elbv2model.TargetGroup - want2 []TargetGroupWithTags - wantErr error - }{ - { - name: "all TargetGroup has match", - args: args{ - resTGs: []*elbv2model.TargetGroup{ - { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::TargetGroup", "id-1"), - Spec: elbv2model.TargetGroupSpec{ - Name: "id-1", - }, - }, - { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::TargetGroup", "id-2"), - Spec: elbv2model.TargetGroupSpec{ - Name: "id-2", - }, - }, - }, - sdkTGs: []TargetGroupWithTags{ - { - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - { - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-2"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-2", - }, - }, - }, - resourceIDTagKey: "ingress.k8s.aws/resource", - }, - want: []resAndSDKTargetGroupPair{ - { - resTG: &elbv2model.TargetGroup{ - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::TargetGroup", "id-1"), - Spec: elbv2model.TargetGroupSpec{ - Name: "id-1", - }, - }, - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - }, - { - resTG: &elbv2model.TargetGroup{ - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::TargetGroup", "id-2"), - Spec: elbv2model.TargetGroupSpec{ - Name: "id-2", - }, - }, - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-2"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-2", - }, - }, - }, - }, - }, - { - name: "some res TargetGroup don't have match", - args: args{ - resTGs: []*elbv2model.TargetGroup{ - { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::TargetGroup", "id-1"), - Spec: elbv2model.TargetGroupSpec{ - Name: "id-1", - }, - }, - { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::TargetGroup", "id-2"), - Spec: elbv2model.TargetGroupSpec{ - Name: "id-2", - }, - }, - }, - sdkTGs: []TargetGroupWithTags{ - { - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - }, - resourceIDTagKey: "ingress.k8s.aws/resource", - }, - want: []resAndSDKTargetGroupPair{ - { - resTG: &elbv2model.TargetGroup{ - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::TargetGroup", "id-1"), - Spec: elbv2model.TargetGroupSpec{ - Name: "id-1", - }, - }, - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - }, - }, - want1: []*elbv2model.TargetGroup{ - { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::TargetGroup", "id-2"), - Spec: elbv2model.TargetGroupSpec{ - Name: "id-2", - }, - }, - }, - }, - { - name: "some sdk TargetGroup don't have match", - args: args{ - resTGs: []*elbv2model.TargetGroup{ - { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::TargetGroup", "id-1"), - Spec: elbv2model.TargetGroupSpec{ - Name: "id-1", - }, - }, - }, - sdkTGs: []TargetGroupWithTags{ - { - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - { - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-2"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-2", - }, - }, - }, - resourceIDTagKey: "ingress.k8s.aws/resource", - }, - want: []resAndSDKTargetGroupPair{ - { - resTG: &elbv2model.TargetGroup{ - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::TargetGroup", "id-1"), - Spec: elbv2model.TargetGroupSpec{ - Name: "id-1", - }, - }, - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - }, - }, - want2: []TargetGroupWithTags{ - { - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-2"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-2", - }, - }, - }, - }, - { - name: "one TargetGroup need to be replaced", - args: args{ - resTGs: []*elbv2model.TargetGroup{ - { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::TargetGroup", "id-1"), - Spec: elbv2model.TargetGroupSpec{ - Name: "my-name", - TargetType: elbv2model.TargetTypeIP, - }, - }, - }, - sdkTGs: []TargetGroupWithTags{ - { - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-1"), - TargetType: awssdk.String("instance"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - { - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-2"), - TargetType: awssdk.String("ip"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - }, - resourceIDTagKey: "ingress.k8s.aws/resource", - }, - want: []resAndSDKTargetGroupPair{ - { - resTG: &elbv2model.TargetGroup{ - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::TargetGroup", "id-1"), - Spec: elbv2model.TargetGroupSpec{ - Name: "my-name", - TargetType: elbv2model.TargetTypeIP, - }, - }, - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-2"), - TargetType: awssdk.String("ip"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - }, - }, - want2: []TargetGroupWithTags{ - { - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-1"), - TargetType: awssdk.String("instance"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - featureGates := config.NewFeatureGates() - got, got1, got2, err := matchResAndSDKTargetGroups(tt.args.resTGs, tt.args.sdkTGs, tt.args.resourceIDTagKey, featureGates) - if tt.wantErr != nil { - assert.EqualError(t, err, tt.wantErr.Error()) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.want, got) - assert.Equal(t, tt.want1, got1) - assert.Equal(t, tt.want2, got2) - } - }) - } -} - -func Test_mapResTargetGroupByResourceID(t *testing.T) { - stack := coremodel.NewDefaultStack(coremodel.StackID{Namespace: "namespace", Name: "name"}) - type args struct { - resTGs []*elbv2model.TargetGroup - } - tests := []struct { - name string - args args - want map[string]*elbv2model.TargetGroup - }{ - { - name: "standard case", - args: args{ - resTGs: []*elbv2model.TargetGroup{ - { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::TargetGroup", "id-1"), - Spec: elbv2model.TargetGroupSpec{ - Name: "id-1", - }, - }, - { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::TargetGroup", "id-2"), - Spec: elbv2model.TargetGroupSpec{ - Name: "id-2", - }, - }, - }, - }, - want: map[string]*elbv2model.TargetGroup{ - "id-1": { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::TargetGroup", "id-1"), - Spec: elbv2model.TargetGroupSpec{ - Name: "id-1", - }, - }, - "id-2": { - ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::TargetGroup", "id-2"), - Spec: elbv2model.TargetGroupSpec{ - Name: "id-2", - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := mapResTargetGroupByResourceID(tt.args.resTGs) - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_mapSDKTargetGroupByResourceID(t *testing.T) { - type args struct { - sdkTGs []TargetGroupWithTags - resourceIDTagKey string - } - tests := []struct { - name string - args args - want map[string][]TargetGroupWithTags - wantErr error - }{ - { - name: "standard case", - args: args{ - sdkTGs: []TargetGroupWithTags{ - { - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - { - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-2"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-2", - }, - }, - }, - resourceIDTagKey: "ingress.k8s.aws/resource", - }, - want: map[string][]TargetGroupWithTags{ - "id-1": { - { - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - }, - "id-2": { - { - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-2"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-2", - }, - }, - }, - }, - }, - { - name: "multiple targetGroups with same ID", - args: args{ - sdkTGs: []TargetGroupWithTags{ - { - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - { - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-2A"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-2", - }, - }, - { - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-2B"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-2", - }, - }, - }, - resourceIDTagKey: "ingress.k8s.aws/resource", - }, - want: map[string][]TargetGroupWithTags{ - "id-1": { - { - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-1", - }, - }, - }, - "id-2": { - { - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-2A"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-2", - }, - }, - { - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-2B"), - }, - Tags: map[string]string{ - "ingress.k8s.aws/resource": "id-2", - }, - }, - }, - }, - }, - { - name: "targetGroups without resourceID tag", - args: args{ - sdkTGs: []TargetGroupWithTags{ - { - TargetGroup: &elbv2sdk.TargetGroup{ - TargetGroupArn: awssdk.String("arn-1"), - }, - Tags: map[string]string{}, - }, - }, - resourceIDTagKey: "ingress.k8s.aws/resource", - }, - wantErr: errors.New("unexpected targetGroup with no resourceID: arn-1"), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := mapSDKTargetGroupByResourceID(tt.args.sdkTGs, tt.args.resourceIDTagKey) - if tt.wantErr != nil { - assert.EqualError(t, err, tt.wantErr.Error()) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.want, got) - } - }) - } -} - -func Test_isSDKTargetGroupRequiresReplacement(t *testing.T) { - port8080 := intstr.FromInt(8080) - protocolHTTP := elbv2model.ProtocolHTTP - type args struct { - sdkTG TargetGroupWithTags - resTG *elbv2model.TargetGroup - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "targetGroup don't need replacement", - args: args{ - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - TargetType: awssdk.String("ip"), - Port: awssdk.Int64(8080), - Protocol: awssdk.String("HTTP"), - TargetGroupName: awssdk.String("my-tg"), - }, - }, - resTG: &elbv2model.TargetGroup{ - Spec: elbv2model.TargetGroupSpec{ - TargetType: elbv2model.TargetTypeIP, - Port: 8080, - Protocol: elbv2model.ProtocolHTTP, - Name: "my-tg", - }, - }, - }, - want: false, - }, - { - name: "name-only change shouldn't need replacement", - args: args{ - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - TargetType: awssdk.String("ip"), - Port: awssdk.Int64(8080), - Protocol: awssdk.String("HTTP"), - TargetGroupName: awssdk.String("my-tg1"), - }, - }, - resTG: &elbv2model.TargetGroup{ - Spec: elbv2model.TargetGroupSpec{ - TargetType: elbv2model.TargetTypeIP, - Port: 8080, - Protocol: elbv2model.ProtocolHTTP, - Name: "my-tg", - }, - }, - }, - want: false, - }, - { - name: "port-only change shouldn't need replacement", - args: args{ - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - TargetType: awssdk.String("ip"), - Port: awssdk.Int64(9090), - Protocol: awssdk.String("HTTP"), - TargetGroupName: awssdk.String("my-tg"), - }, - }, - resTG: &elbv2model.TargetGroup{ - Spec: elbv2model.TargetGroupSpec{ - TargetType: elbv2model.TargetTypeIP, - Port: 8080, - Protocol: elbv2model.ProtocolHTTP, - Name: "my-tg", - }, - }, - }, - want: false, - }, - { - name: "targetType change need replacement", - args: args{ - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - TargetType: awssdk.String("instance"), - Port: awssdk.Int64(8080), - Protocol: awssdk.String("HTTP"), - TargetGroupName: awssdk.String("my-tg"), - }, - }, - resTG: &elbv2model.TargetGroup{ - Spec: elbv2model.TargetGroupSpec{ - TargetType: elbv2model.TargetTypeIP, - Port: 8080, - Protocol: elbv2model.ProtocolHTTP, - Name: "my-tg", - }, - }, - }, - want: true, - }, - { - name: "protocol change need replacement", - args: args{ - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - TargetType: awssdk.String("ip"), - Port: awssdk.Int64(8080), - Protocol: awssdk.String("TCP"), - TargetGroupName: awssdk.String("my-tg"), - }, - }, - resTG: &elbv2model.TargetGroup{ - Spec: elbv2model.TargetGroupSpec{ - TargetType: elbv2model.TargetTypeIP, - Port: 8080, - Protocol: elbv2model.ProtocolHTTP, - Name: "my-tg", - }, - }, - }, - want: true, - }, - { - name: "healthCheck change needs no replacement for protocol change", - args: args{ - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - Protocol: awssdk.String("TCP"), - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckPort: awssdk.String("8080"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckPath: awssdk.String("/"), - Matcher: &elbv2sdk.Matcher{ - HttpCode: awssdk.String("200"), - }, - HealthCheckIntervalSeconds: awssdk.Int64(11), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - resTG: &elbv2model.TargetGroup{ - Spec: elbv2model.TargetGroupSpec{ - Protocol: elbv2model.ProtocolTCP, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port8080, - Protocol: &protocolHTTP, - Path: awssdk.String("/"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - }, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - featureGates := config.NewFeatureGates() - got := isSDKTargetGroupRequiresReplacement(tt.args.sdkTG, tt.args.resTG, featureGates) - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_isSDKTargetGroupRequiresReplacementDueToNLBHealthCheck(t *testing.T) { - port8080 := intstr.FromInt(8080) - protocolHTTP := elbv2model.ProtocolHTTP - type args struct { - sdkTG TargetGroupWithTags - resTG *elbv2model.TargetGroup - disableAdvancedNLBHealthCheckConfig bool - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "NLB TargetGroup healthCheck haven't changed", - args: args{ - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - Protocol: awssdk.String("TCP"), - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckPort: awssdk.String("8080"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckPath: awssdk.String("/"), - Matcher: &elbv2sdk.Matcher{ - HttpCode: awssdk.String("200"), - }, - HealthCheckIntervalSeconds: awssdk.Int64(10), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - resTG: &elbv2model.TargetGroup{ - Spec: elbv2model.TargetGroupSpec{ - Protocol: elbv2model.ProtocolTCP, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port8080, - Protocol: &protocolHTTP, - Path: awssdk.String("/"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - }, - want: false, - }, - { - name: "NLB TargetGroup healthCheck cannot change protocol without advanced config", - args: args{ - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - Protocol: awssdk.String("TCP"), - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckPort: awssdk.String("8080"), - HealthCheckProtocol: awssdk.String("HTTPS"), - HealthCheckPath: awssdk.String("/"), - Matcher: &elbv2sdk.Matcher{ - HttpCode: awssdk.String("200"), - }, - HealthCheckIntervalSeconds: awssdk.Int64(10), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - resTG: &elbv2model.TargetGroup{ - Spec: elbv2model.TargetGroupSpec{ - Protocol: elbv2model.ProtocolTCP, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port8080, - Protocol: &protocolHTTP, - Path: awssdk.String("/"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - disableAdvancedNLBHealthCheckConfig: true, - }, - want: true, - }, - { - name: "NLB TargetGroup healthCheck cannot changed matcher", - args: args{ - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - Protocol: awssdk.String("TCP"), - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckPort: awssdk.String("8080"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckPath: awssdk.String("/"), - Matcher: &elbv2sdk.Matcher{ - HttpCode: awssdk.String("300"), - }, - HealthCheckIntervalSeconds: awssdk.Int64(10), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - resTG: &elbv2model.TargetGroup{ - Spec: elbv2model.TargetGroupSpec{ - Protocol: elbv2model.ProtocolTCP, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port8080, - Protocol: &protocolHTTP, - Path: awssdk.String("/"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - disableAdvancedNLBHealthCheckConfig: true, - }, - want: true, - }, - { - name: "NLB TargetGroup healthCheck cannot change intervalSeconds", - args: args{ - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - Protocol: awssdk.String("TCP"), - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckPort: awssdk.String("8080"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckPath: awssdk.String("/"), - Matcher: &elbv2sdk.Matcher{ - HttpCode: awssdk.String("200"), - }, - HealthCheckIntervalSeconds: awssdk.Int64(11), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - resTG: &elbv2model.TargetGroup{ - Spec: elbv2model.TargetGroupSpec{ - Protocol: elbv2model.ProtocolTCP, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port8080, - Protocol: &protocolHTTP, - Path: awssdk.String("/"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - disableAdvancedNLBHealthCheckConfig: true, - }, - want: true, - }, - { - name: "NLB TargetGroup healthCheck cannot change timeoutSecond", - args: args{ - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - Protocol: awssdk.String("TCP"), - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckPort: awssdk.String("8080"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckPath: awssdk.String("/"), - Matcher: &elbv2sdk.Matcher{ - HttpCode: awssdk.String("200"), - }, - HealthCheckIntervalSeconds: awssdk.Int64(10), - HealthCheckTimeoutSeconds: awssdk.Int64(6), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - resTG: &elbv2model.TargetGroup{ - Spec: elbv2model.TargetGroupSpec{ - Protocol: elbv2model.ProtocolTCP, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port8080, - Protocol: &protocolHTTP, - Path: awssdk.String("/"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - disableAdvancedNLBHealthCheckConfig: true, - }, - want: true, - }, - { - name: "NLB TargetGroup healthCheck can change port", - args: args{ - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - Protocol: awssdk.String("TCP"), - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckPort: awssdk.String("9090"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckPath: awssdk.String("/"), - Matcher: &elbv2sdk.Matcher{ - HttpCode: awssdk.String("200"), - }, - HealthCheckIntervalSeconds: awssdk.Int64(10), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - resTG: &elbv2model.TargetGroup{ - Spec: elbv2model.TargetGroupSpec{ - Protocol: elbv2model.ProtocolTCP, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port8080, - Protocol: &protocolHTTP, - Path: awssdk.String("/"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - }, - want: false, - }, - { - name: "NLB TargetGroup healthCheck can change path", - args: args{ - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - Protocol: awssdk.String("TCP"), - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckPort: awssdk.String("8080"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckPath: awssdk.String("/some-other"), - Matcher: &elbv2sdk.Matcher{ - HttpCode: awssdk.String("200"), - }, - HealthCheckIntervalSeconds: awssdk.Int64(10), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - resTG: &elbv2model.TargetGroup{ - Spec: elbv2model.TargetGroupSpec{ - Protocol: elbv2model.ProtocolTCP, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port8080, - Protocol: &protocolHTTP, - Path: awssdk.String("/"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - }, - want: false, - }, - { - name: "NLB TargetGroup healthCheck can change healthyThresholdCount", - args: args{ - sdkTG: TargetGroupWithTags{ - TargetGroup: &elbv2sdk.TargetGroup{ - Protocol: awssdk.String("TCP"), - HealthCheckEnabled: awssdk.Bool(true), - HealthCheckPort: awssdk.String("8080"), - HealthCheckProtocol: awssdk.String("HTTP"), - HealthCheckPath: awssdk.String("/"), - Matcher: &elbv2sdk.Matcher{ - HttpCode: awssdk.String("200"), - }, - HealthCheckIntervalSeconds: awssdk.Int64(10), - HealthCheckTimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(4), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - resTG: &elbv2model.TargetGroup{ - Spec: elbv2model.TargetGroupSpec{ - Protocol: elbv2model.ProtocolTCP, - HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{ - Port: &port8080, - Protocol: &protocolHTTP, - Path: awssdk.String("/"), - Matcher: &elbv2model.HealthCheckMatcher{HTTPCode: awssdk.String("200")}, - IntervalSeconds: awssdk.Int64(10), - TimeoutSeconds: awssdk.Int64(5), - HealthyThresholdCount: awssdk.Int64(3), - UnhealthyThresholdCount: awssdk.Int64(2), - }, - }, - }, - }, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - featureGates := config.NewFeatureGates() - if tt.args.disableAdvancedNLBHealthCheckConfig { - featureGates.Disable(config.NLBHealthCheckAdvancedConfig) - } - got := isSDKTargetGroupRequiresReplacementDueToNLBHealthCheck(tt.args.sdkTG, tt.args.resTG, featureGates) - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/deploy/shield/protection_manager.go b/pkg/deploy/shield/protection_manager.go deleted file mode 100644 index a3855a1f9..000000000 --- a/pkg/deploy/shield/protection_manager.go +++ /dev/null @@ -1,152 +0,0 @@ -package shield - -import ( - "context" - awssdk "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - shieldsdk "github.com/aws/aws-sdk-go/service/shield" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/util/cache" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - "time" -) - -const ( - defaultProtectionInfoByResourceARNCacheTTL = 10 * time.Minute - // service subscription rarely changes, cache it with longer period. - defaultSubscriptionStateCacheTTL = 2 * time.Hour - subscriptionStateCacheKey = "subscriptionState" -) - -type ProtectionManager interface { - // CreateProtection creates shield protection for resource. returns protectionID. - CreateProtection(ctx context.Context, resourceARN string, protectionName string) (string, error) - - // DeleteProtection deletes shield protection for resource. - DeleteProtection(ctx context.Context, resourceARN string, protectionID string) error - - // GetProtection returns shield protection information for resource. - // returns nil if no protection exists. - GetProtection(ctx context.Context, resourceARN string) (*ProtectionInfo, error) - - // IsSubscribed checks whether subscribed to shield service. - IsSubscribed(ctx context.Context) (bool, error) -} - -func NewDefaultProtectionManager(shieldClient services.Shield, logger logr.Logger) *defaultProtectionManager { - return &defaultProtectionManager{ - shieldClient: shieldClient, - logger: logger, - protectionInfoByResourceARNCache: cache.NewExpiring(), - protectionInfoByResourceARNCacheTTL: defaultProtectionInfoByResourceARNCacheTTL, - subscriptionStateCache: cache.NewExpiring(), - subscriptionStateCacheTTL: defaultSubscriptionStateCacheTTL, - } -} - -var _ ProtectionManager = &defaultProtectionManager{} - -type defaultProtectionManager struct { - shieldClient services.Shield - logger logr.Logger - - protectionInfoByResourceARNCache *cache.Expiring - protectionInfoByResourceARNCacheTTL time.Duration - subscriptionStateCache *cache.Expiring - subscriptionStateCacheTTL time.Duration -} - -type ProtectionInfo struct { - Name string - ID string -} - -func (m *defaultProtectionManager) CreateProtection(ctx context.Context, resourceARN string, protectionName string) (string, error) { - req := &shieldsdk.CreateProtectionInput{ - ResourceArn: awssdk.String(resourceARN), - Name: awssdk.String(protectionName), - } - m.logger.Info("enabling shield protection", - "resourceARN", resourceARN, - "protectionName", protectionName) - resp, err := m.shieldClient.CreateProtectionWithContext(ctx, req) - if err != nil { - return "", err - } - protectionID := awssdk.StringValue(resp.ProtectionId) - m.logger.Info("enabled shield protection", - "resourceARN", resourceARN, - "protectionName", protectionName, - "protectionID", protectionID) - protectionInfo := &ProtectionInfo{ - Name: protectionName, - ID: protectionID, - } - m.protectionInfoByResourceARNCache.Set(resourceARN, protectionInfo, m.protectionInfoByResourceARNCacheTTL) - return protectionID, nil -} - -func (m *defaultProtectionManager) DeleteProtection(ctx context.Context, resourceARN string, protectionID string) error { - req := &shieldsdk.DeleteProtectionInput{ - ProtectionId: awssdk.String(protectionID), - } - m.logger.Info("disabling shield protection", - "resourceARN", resourceARN, - "protectionID", protectionID) - _, err := m.shieldClient.DeleteProtectionWithContext(ctx, req) - if err != nil { - return err - } - m.logger.Info("disabled shield protection", - "resourceARN", resourceARN) - - var protectionInfo *ProtectionInfo - m.protectionInfoByResourceARNCache.Set(resourceARN, protectionInfo, m.protectionInfoByResourceARNCacheTTL) - return nil -} - -func (m *defaultProtectionManager) GetProtection(ctx context.Context, resourceARN string) (*ProtectionInfo, error) { - rawCacheItem, exists := m.protectionInfoByResourceARNCache.Get(resourceARN) - if exists { - return rawCacheItem.(*ProtectionInfo), nil - } - - req := &shieldsdk.DescribeProtectionInput{ - ResourceArn: awssdk.String(resourceARN), - } - resp, err := m.shieldClient.DescribeProtectionWithContext(ctx, req) - var protectionInfo *ProtectionInfo - if err != nil { - aerr, ok := err.(awserr.Error) - if ok && aerr.Code() == shieldsdk.ErrCodeResourceNotFoundException { - protectionInfo = nil - } else { - return nil, err - } - } - if resp.Protection != nil { - protectionInfo = &ProtectionInfo{ - Name: awssdk.StringValue(resp.Protection.Name), - ID: awssdk.StringValue(resp.Protection.Id), - } - } - m.protectionInfoByResourceARNCache.Set(resourceARN, protectionInfo, m.protectionInfoByResourceARNCacheTTL) - return protectionInfo, nil -} - -func (m *defaultProtectionManager) IsSubscribed(ctx context.Context) (bool, error) { - rawCacheItem, exists := m.subscriptionStateCache.Get(subscriptionStateCacheKey) - if exists { - subscriptionState := rawCacheItem.(string) - return subscriptionState == shieldsdk.SubscriptionStateActive, nil - } - - req := &shieldsdk.GetSubscriptionStateInput{} - resp, err := m.shieldClient.GetSubscriptionStateWithContext(ctx, req) - if err != nil { - return false, err - } - subscriptionState := awssdk.StringValue(resp.SubscriptionState) - m.subscriptionStateCache.Set(subscriptionStateCacheKey, subscriptionState, m.subscriptionStateCacheTTL) - return subscriptionState == shieldsdk.SubscriptionStateActive, nil -} diff --git a/pkg/deploy/shield/protection_manager_test.go b/pkg/deploy/shield/protection_manager_test.go deleted file mode 100644 index b89cb62fc..000000000 --- a/pkg/deploy/shield/protection_manager_test.go +++ /dev/null @@ -1,171 +0,0 @@ -package shield - -import ( - "context" - "testing" - "time" - - awssdk "github.com/aws/aws-sdk-go/aws" - shieldsdk "github.com/aws/aws-sdk-go/service/shield" - "github.com/go-logr/logr" - "github.com/golang/mock/gomock" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/util/cache" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - "sigs.k8s.io/controller-runtime/pkg/log" -) - -func Test_defaultProtectionManager_IsSubscribed(t *testing.T) { - type getSubscriptionStateCall struct { - req *shieldsdk.GetSubscriptionStateInput - resp *shieldsdk.GetSubscriptionStateOutput - err error - } - type fields struct { - subscriptionStateCacheTTL time.Duration - getSubscriptionStateCalls []getSubscriptionStateCall - } - type isSubscribedCall struct { - want bool - wantErr error - } - tests := []struct { - name string - fields fields - isSubscribedCalls []isSubscribedCall - }{ - { - name: "invoke isSubscribed once without cache - subscriptionState == ACTIVE", - fields: fields{ - subscriptionStateCacheTTL: 2 * time.Hour, - getSubscriptionStateCalls: []getSubscriptionStateCall{ - { - req: &shieldsdk.GetSubscriptionStateInput{}, - resp: &shieldsdk.GetSubscriptionStateOutput{ - SubscriptionState: awssdk.String(shieldsdk.SubscriptionStateActive), - }, - }, - }, - }, - isSubscribedCalls: []isSubscribedCall{ - { - want: true, - }, - }, - }, - { - name: "invoke isSubscribed once without cache - subscriptionState == INACTIVE", - fields: fields{ - subscriptionStateCacheTTL: 2 * time.Hour, - getSubscriptionStateCalls: []getSubscriptionStateCall{ - { - req: &shieldsdk.GetSubscriptionStateInput{}, - resp: &shieldsdk.GetSubscriptionStateOutput{ - SubscriptionState: awssdk.String(shieldsdk.SubscriptionStateInactive), - }, - }, - }, - }, - isSubscribedCalls: []isSubscribedCall{ - { - want: false, - }, - }, - }, - { - name: "invoke isSubscribed once without cache - AWS API error", - fields: fields{ - subscriptionStateCacheTTL: 2 * time.Hour, - getSubscriptionStateCalls: []getSubscriptionStateCall{ - { - req: &shieldsdk.GetSubscriptionStateInput{}, - err: errors.New("some aws api error"), - }, - }, - }, - isSubscribedCalls: []isSubscribedCall{ - { - wantErr: errors.New("some aws api error"), - }, - }, - }, - { - name: "invoke isSubscribed twice with cache - two call within cacheTTL", - fields: fields{ - subscriptionStateCacheTTL: 2 * time.Hour, - getSubscriptionStateCalls: []getSubscriptionStateCall{ - { - req: &shieldsdk.GetSubscriptionStateInput{}, - resp: &shieldsdk.GetSubscriptionStateOutput{ - SubscriptionState: awssdk.String(shieldsdk.SubscriptionStateInactive), - }, - }, - }, - }, - isSubscribedCalls: []isSubscribedCall{ - { - want: false, - }, - { - want: false, - }, - }, - }, - { - name: "invoke isSubscribed twice with cache - two call beyond cacheTTL", - fields: fields{ - subscriptionStateCacheTTL: 0, - getSubscriptionStateCalls: []getSubscriptionStateCall{ - { - req: &shieldsdk.GetSubscriptionStateInput{}, - resp: &shieldsdk.GetSubscriptionStateOutput{ - SubscriptionState: awssdk.String(shieldsdk.SubscriptionStateInactive), - }, - }, - { - req: &shieldsdk.GetSubscriptionStateInput{}, - resp: &shieldsdk.GetSubscriptionStateOutput{ - SubscriptionState: awssdk.String(shieldsdk.SubscriptionStateActive), - }, - }, - }, - }, - isSubscribedCalls: []isSubscribedCall{ - { - want: false, - }, - { - want: true, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - shieldClient := services.NewMockShield(ctrl) - for _, call := range tt.fields.getSubscriptionStateCalls { - shieldClient.EXPECT().GetSubscriptionStateWithContext(gomock.Any(), call.req).Return(call.resp, call.err) - } - - m := &defaultProtectionManager{ - shieldClient: shieldClient, - logger: logr.New(&log.NullLogSink{}), - subscriptionStateCache: cache.NewExpiring(), - subscriptionStateCacheTTL: tt.fields.subscriptionStateCacheTTL, - } - for _, call := range tt.isSubscribedCalls { - got, err := m.IsSubscribed(context.Background()) - if call.wantErr != nil { - assert.EqualError(t, err, call.wantErr.Error()) - } else { - assert.NoError(t, err) - assert.Equal(t, call.want, got) - } - } - }) - } -} diff --git a/pkg/deploy/shield/protection_synthesizer.go b/pkg/deploy/shield/protection_synthesizer.go deleted file mode 100644 index 984eb5963..000000000 --- a/pkg/deploy/shield/protection_synthesizer.go +++ /dev/null @@ -1,110 +0,0 @@ -package shield - -import ( - "context" - "github.com/go-logr/logr" - "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/util/sets" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" - shieldmodel "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/shield" -) - -const ( - protectionNameManaged = "managed by aws-load-balancer-controller" - protectionNameManagedLegacy = "managed by aws-alb-ingress-controller" -) - -// NewProtectionSynthesizer constructs new protectionSynthesizer -func NewProtectionSynthesizer(protectionManager ProtectionManager, logger logr.Logger, stack core.Stack) *protectionSynthesizer { - return &protectionSynthesizer{ - protectionManager: protectionManager, - logger: logger, - stack: stack, - } -} - -type protectionSynthesizer struct { - protectionManager ProtectionManager - logger logr.Logger - stack core.Stack -} - -func (s *protectionSynthesizer) Synthesize(ctx context.Context) error { - var resProtections []*shieldmodel.Protection - s.stack.ListResources(&resProtections) - resProtectionsByResARN, err := mapResProtectionByResourceARN(resProtections) - if err != nil { - return err - } - - var resLBs []*elbv2model.LoadBalancer - s.stack.ListResources(&resLBs) - for _, resLB := range resLBs { - // shield protection can only be associated with ALB for now. - if resLB.Spec.Type != elbv2model.LoadBalancerTypeApplication { - continue - } - lbARN, err := resLB.LoadBalancerARN().Resolve(ctx) - if err != nil { - return err - } - resProtections := resProtectionsByResARN[lbARN] - if err := s.synthesizeProtectionsOnLB(ctx, lbARN, resProtections); err != nil { - return err - } - } - return nil -} - -func (s *protectionSynthesizer) PostSynthesize(ctx context.Context) error { - // nothing to do here. - return nil -} - -func (s *protectionSynthesizer) synthesizeProtectionsOnLB(ctx context.Context, lbARN string, resProtections []*shieldmodel.Protection) error { - if len(resProtections) > 1 { - return errors.Errorf("[should never happen] multiple shield protection desired on LoadBalancer: %v", lbARN) - } - - enableProtection := false - if len(resProtections) == 1 { - enableProtection = true - } - - protectionInfo, err := s.protectionManager.GetProtection(ctx, lbARN) - if err != nil { - return err - } - switch { - case !enableProtection && protectionInfo != nil: - managedProtectionNames := sets.NewString(protectionNameManaged, protectionNameManagedLegacy) - if managedProtectionNames.Has(protectionInfo.Name) { - if err := s.protectionManager.DeleteProtection(ctx, lbARN, protectionInfo.ID); err != nil { - return errors.Wrap(err, "failed to delete shield protection on LoadBalancer") - } - } else { - s.logger.Info("ignoring unmanaged shield protection", - "protectionName", protectionInfo.Name, - "protectionID", protectionInfo.ID) - } - case enableProtection && protectionInfo == nil: - if _, err := s.protectionManager.CreateProtection(ctx, lbARN, protectionNameManaged); err != nil { - return errors.Wrap(err, "failed to create shield protection on LoadBalancer") - } - } - return nil -} - -func mapResProtectionByResourceARN(resProtections []*shieldmodel.Protection) (map[string][]*shieldmodel.Protection, error) { - resProtectionsByResARN := make(map[string][]*shieldmodel.Protection, len(resProtections)) - ctx := context.Background() - for _, resProtection := range resProtections { - resARN, err := resProtection.Spec.ResourceARN.Resolve(ctx) - if err != nil { - return nil, err - } - resProtectionsByResARN[resARN] = append(resProtectionsByResARN[resARN], resProtection) - } - return resProtectionsByResARN, nil -} diff --git a/pkg/deploy/stack_deployer.go b/pkg/deploy/stack_deployer.go deleted file mode 100644 index 35223b8fb..000000000 --- a/pkg/deploy/stack_deployer.go +++ /dev/null @@ -1,124 +0,0 @@ -package deploy - -import ( - "context" - "github.com/go-logr/logr" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/config" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/ec2" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/elbv2" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/shield" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/tracking" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/wafregional" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/deploy/wafv2" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/networking" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -// StackDeployer will deploy a resource stack into AWS and K8S. -type StackDeployer interface { - // Deploy a resource stack. - Deploy(ctx context.Context, stack core.Stack) error -} - -// NewDefaultStackDeployer constructs new defaultStackDeployer. -func NewDefaultStackDeployer(cloud aws.Cloud, k8sClient client.Client, - networkingSGManager networking.SecurityGroupManager, networkingSGReconciler networking.SecurityGroupReconciler, - config config.ControllerConfig, tagPrefix string, logger logr.Logger) *defaultStackDeployer { - - trackingProvider := tracking.NewDefaultProvider(tagPrefix, config.ClusterName) - ec2TaggingManager := ec2.NewDefaultTaggingManager(cloud.EC2(), networkingSGManager, cloud.VpcID(), logger) - elbv2TaggingManager := elbv2.NewDefaultTaggingManager(cloud.ELBV2(), cloud.VpcID(), config.FeatureGates, cloud.RGT(), logger) - - return &defaultStackDeployer{ - cloud: cloud, - k8sClient: k8sClient, - addonsConfig: config.AddonsConfig, - trackingProvider: trackingProvider, - ec2TaggingManager: ec2TaggingManager, - ec2SGManager: ec2.NewDefaultSecurityGroupManager(cloud.EC2(), trackingProvider, ec2TaggingManager, networkingSGReconciler, cloud.VpcID(), config.ExternalManagedTags, logger), - elbv2TaggingManager: elbv2TaggingManager, - elbv2LBManager: elbv2.NewDefaultLoadBalancerManager(cloud.ELBV2(), trackingProvider, elbv2TaggingManager, config.ExternalManagedTags, logger), - elbv2LSManager: elbv2.NewDefaultListenerManager(cloud.ELBV2(), trackingProvider, elbv2TaggingManager, config.ExternalManagedTags, config.FeatureGates, logger), - elbv2LRManager: elbv2.NewDefaultListenerRuleManager(cloud.ELBV2(), trackingProvider, elbv2TaggingManager, config.ExternalManagedTags, config.FeatureGates, logger), - elbv2TGManager: elbv2.NewDefaultTargetGroupManager(cloud.ELBV2(), trackingProvider, elbv2TaggingManager, cloud.VpcID(), config.ExternalManagedTags, logger), - elbv2TGBManager: elbv2.NewDefaultTargetGroupBindingManager(k8sClient, trackingProvider, logger), - wafv2WebACLAssociationManager: wafv2.NewDefaultWebACLAssociationManager(cloud.WAFv2(), logger), - wafRegionalWebACLAssociationManager: wafregional.NewDefaultWebACLAssociationManager(cloud.WAFRegional(), logger), - shieldProtectionManager: shield.NewDefaultProtectionManager(cloud.Shield(), logger), - featureGates: config.FeatureGates, - vpcID: cloud.VpcID(), - logger: logger, - } -} - -var _ StackDeployer = &defaultStackDeployer{} - -// defaultStackDeployer is the default implementation for StackDeployer -type defaultStackDeployer struct { - cloud aws.Cloud - k8sClient client.Client - addonsConfig config.AddonsConfig - trackingProvider tracking.Provider - ec2TaggingManager ec2.TaggingManager - ec2SGManager ec2.SecurityGroupManager - elbv2TaggingManager elbv2.TaggingManager - elbv2LBManager elbv2.LoadBalancerManager - elbv2LSManager elbv2.ListenerManager - elbv2LRManager elbv2.ListenerRuleManager - elbv2TGManager elbv2.TargetGroupManager - elbv2TGBManager elbv2.TargetGroupBindingManager - wafv2WebACLAssociationManager wafv2.WebACLAssociationManager - wafRegionalWebACLAssociationManager wafregional.WebACLAssociationManager - shieldProtectionManager shield.ProtectionManager - featureGates config.FeatureGates - vpcID string - - logger logr.Logger -} - -type ResourceSynthesizer interface { - Synthesize(ctx context.Context) error - PostSynthesize(ctx context.Context) error -} - -// Deploy a resource stack. -func (d *defaultStackDeployer) Deploy(ctx context.Context, stack core.Stack) error { - synthesizers := []ResourceSynthesizer{ - ec2.NewSecurityGroupSynthesizer(d.cloud.EC2(), d.trackingProvider, d.ec2TaggingManager, d.ec2SGManager, d.vpcID, d.logger, stack), - elbv2.NewTargetGroupSynthesizer(d.cloud.ELBV2(), d.trackingProvider, d.elbv2TaggingManager, d.elbv2TGManager, d.logger, d.featureGates, stack), - elbv2.NewLoadBalancerSynthesizer(d.cloud.ELBV2(), d.trackingProvider, d.elbv2TaggingManager, d.elbv2LBManager, d.logger, stack), - elbv2.NewListenerSynthesizer(d.cloud.ELBV2(), d.elbv2TaggingManager, d.elbv2LSManager, d.logger, stack), - elbv2.NewListenerRuleSynthesizer(d.cloud.ELBV2(), d.elbv2TaggingManager, d.elbv2LRManager, d.logger, stack), - elbv2.NewTargetGroupBindingSynthesizer(d.k8sClient, d.trackingProvider, d.elbv2TGBManager, d.logger, stack), - } - - if d.addonsConfig.WAFV2Enabled { - synthesizers = append(synthesizers, wafv2.NewWebACLAssociationSynthesizer(d.wafv2WebACLAssociationManager, d.logger, stack)) - } - if d.addonsConfig.WAFEnabled && d.cloud.WAFRegional().Available() { - synthesizers = append(synthesizers, wafregional.NewWebACLAssociationSynthesizer(d.wafRegionalWebACLAssociationManager, d.logger, stack)) - } - if d.addonsConfig.ShieldEnabled { - shieldSubscribed, err := d.shieldProtectionManager.IsSubscribed(ctx) - if err != nil { - d.logger.Error(err, "unable to determine AWS Shield subscription state, skipping AWS shield reconciliation") - } else if shieldSubscribed { - synthesizers = append(synthesizers, shield.NewProtectionSynthesizer(d.shieldProtectionManager, d.logger, stack)) - } - } - - for _, synthesizer := range synthesizers { - if err := synthesizer.Synthesize(ctx); err != nil { - return err - } - } - for i := len(synthesizers) - 1; i >= 0; i-- { - if err := synthesizers[i].PostSynthesize(ctx); err != nil { - return err - } - } - - return nil -} diff --git a/pkg/deploy/stack_marshaller.go b/pkg/deploy/stack_marshaller.go deleted file mode 100644 index 777673ab7..000000000 --- a/pkg/deploy/stack_marshaller.go +++ /dev/null @@ -1,32 +0,0 @@ -package deploy - -import ( - "encoding/json" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" -) - -// StackMarshaller will marshall a resource stack into JSON. -type StackMarshaller interface { - Marshal(stack core.Stack) (string, error) -} - -func NewDefaultStackMarshaller() *defaultStackMarshaller { - return &defaultStackMarshaller{} -} - -var _ StackMarshaller = &defaultStackMarshaller{} - -type defaultStackMarshaller struct{} - -func (m *defaultStackMarshaller) Marshal(stack core.Stack) (string, error) { - builder := NewStackSchemaBuilder(stack.StackID()) - if err := stack.TopologicalTraversal(builder); err != nil { - return "", err - } - stackSchema := builder.Build() - payload, err := json.Marshal(stackSchema) - if err != nil { - return "", err - } - return string(payload), nil -} diff --git a/pkg/deploy/stack_marshaller_test.go b/pkg/deploy/stack_marshaller_test.go deleted file mode 100644 index 03bcd8505..000000000 --- a/pkg/deploy/stack_marshaller_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package deploy - -import ( - "github.com/stretchr/testify/assert" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" - "testing" -) - -func Test_defaultDeployer_Deploy(t *testing.T) { - tests := []struct { - name string - modelBuildFunc func() core.Stack - want string - wantErr error - }{ - { - name: "single resource", - modelBuildFunc: func() core.Stack { - stack := core.NewDefaultStack(core.StackID{Namespace: "namespace", Name: "name"}) - _ = core.NewFakeResource(stack, "typeX", "resA", core.FakeResourceSpec{ - FieldA: []core.StringToken{core.LiteralStringToken("valueA")}, - }, nil) - return stack - }, - want: `{"id":"namespace/name","resources":{"typeX":{"resA":{"spec":{"fieldA":["valueA"]}}}}}`, - }, - { - name: "multiple resources", - modelBuildFunc: func() core.Stack { - stack := core.NewDefaultStack(core.StackID{Namespace: "namespace", Name: "name"}) - resA := core.NewFakeResource(stack, "typeX", "resA", core.FakeResourceSpec{ - FieldA: []core.StringToken{core.LiteralStringToken("valueA")}, - }, nil) - resB := core.NewFakeResource(stack, "typeX", "resB", core.FakeResourceSpec{ - FieldA: []core.StringToken{resA.FieldB()}, - }, nil) - _ = core.NewFakeResource(stack, "typeY", "resC", core.FakeResourceSpec{ - FieldA: []core.StringToken{core.LiteralStringToken("valueA"), resB.FieldB()}, - }, nil) - return stack - }, - want: `{"id":"namespace/name","resources":{"typeX":{"resA":{"spec":{"fieldA":["valueA"]}},"resB":{"spec":{"fieldA":[{"$ref":"#/resources/typeX/resA/status/fieldB"}]}}},"typeY":{"resC":{"spec":{"fieldA":["valueA",{"$ref":"#/resources/typeX/resB/status/fieldB"}]}}}}}`, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - d := NewDefaultStackMarshaller() - stack := tt.modelBuildFunc() - got, err := d.Marshal(stack) - assert.Equal(t, tt.want, got) - if tt.wantErr == nil { - assert.NoError(t, err) - } else { - assert.EqualError(t, err, tt.wantErr.Error()) - } - }) - } -} diff --git a/pkg/deploy/stack_schema_builder.go b/pkg/deploy/stack_schema_builder.go deleted file mode 100644 index 3ab36e779..000000000 --- a/pkg/deploy/stack_schema_builder.go +++ /dev/null @@ -1,46 +0,0 @@ -package deploy - -import ( - coremodel "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" -) - -// StackSchema represents the JSON model for stack. -type StackSchema struct { - // Stack's ID - ID string `json:"id"` - - // all resources within stack. - Resources map[string]map[string]interface{} `json:"resources"` -} - -// NewStackSchemaBuilder constructs new stackSchemaBuilder. -func NewStackSchemaBuilder(stackID coremodel.StackID) *stackSchemaBuilder { - return &stackSchemaBuilder{ - stackID: stackID, - resources: make(map[string]map[string]interface{}), - } -} - -var _ coremodel.ResourceVisitor = &stackSchemaBuilder{} - -type stackSchemaBuilder struct { - stackID coremodel.StackID - resources map[string]map[string]interface{} -} - -// Visit will visit a resource. -func (b *stackSchemaBuilder) Visit(res coremodel.Resource) error { - if _, ok := b.resources[res.Type()]; !ok { - b.resources[res.Type()] = make(map[string]interface{}) - } - b.resources[res.Type()][res.ID()] = res - return nil -} - -// Build will build StackSchema based on resources visited. -func (b *stackSchemaBuilder) Build() StackSchema { - return StackSchema{ - ID: b.stackID.String(), - Resources: b.resources, - } -} diff --git a/pkg/deploy/stack_schema_builder_test.go b/pkg/deploy/stack_schema_builder_test.go deleted file mode 100644 index 234334ab1..000000000 --- a/pkg/deploy/stack_schema_builder_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package deploy - -import ( - "github.com/stretchr/testify/assert" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" - "testing" -) - -func Test_stackSchemaBuilder_Visit(t *testing.T) { - stack := core.NewDefaultStack(core.StackID{Namespace: "namespace", Name: "name"}) - resA := core.NewFakeResource(stack, "typeX", "resA", core.FakeResourceSpec{ - FieldA: []core.StringToken{core.LiteralStringToken("valueA")}, - }, nil) - resB := core.NewFakeResource(stack, "typeX", "resB", core.FakeResourceSpec{ - FieldA: []core.StringToken{resA.FieldB()}, - }, nil) - resC := core.NewFakeResource(stack, "typeY", "resC", core.FakeResourceSpec{ - FieldA: []core.StringToken{core.LiteralStringToken("valueA"), resB.FieldB()}, - }, nil) - - type args struct { - res core.Resource - } - tests := []struct { - name string - args []args - wantStackSchema StackSchema - }{ - { - name: "single resource", - args: []args{ - { - res: resA, - }, - }, - wantStackSchema: StackSchema{ - ID: "namespace/name", - Resources: map[string]map[string]interface{}{ - "typeX": { - "resA": resA, - }, - }}, - }, - { - name: "multiple resources", - args: []args{ - { - res: resA, - }, - { - res: resB, - }, - { - res: resC, - }, - }, - wantStackSchema: StackSchema{ - ID: "namespace/name", - Resources: map[string]map[string]interface{}{ - "typeX": { - "resA": resA, - "resB": resB, - }, - "typeY": { - "resC": resC, - }, - }}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - b := NewStackSchemaBuilder(stack.StackID()) - for _, arg := range tt.args { - b.Visit(arg.res) - } - gotStackSchema := b.Build() - assert.Equal(t, tt.wantStackSchema, gotStackSchema) - }) - } -} diff --git a/pkg/deploy/tracking/provider.go b/pkg/deploy/tracking/provider.go deleted file mode 100644 index cd9db519e..000000000 --- a/pkg/deploy/tracking/provider.go +++ /dev/null @@ -1,136 +0,0 @@ -package tracking - -import ( - "fmt" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/algorithm" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" -) - -//we use AWS tags and K8s labels to track resources we have created. -// -//For AWS resources created by this controller, the tagging strategy is as follows: -// * `elbv2.k8s.aws/cluster: cluster-name` will be applied on all AWS resources. -// * `ingress.k8s.aws/stack: stack-id` will be applied on all AWS resources provisioned for Ingress resources: -// * For explicit IngressGroup, `stack-id` will be `groupName` -// * For implicit IngressGroup, `stack-id` will be `namespace/ingressName` -// * `ingress.k8s.aws/resource: resource-id` will be applied on all AWS resources provisioned for Ingress resources: -// * For LoadBalancer, `resource-id` will be `LoadBalancer` -// * For Managed LB SecurityGroup, `resource-id` will be `ManagedLBSecurityGroup` -// * For TargetGroup, `resource-id` will be `namespace/ingressName-serviceName:servicePort` -// * `service.k8s.aws/stack: stack-id` will be applied on all AWS resources provisioned for Service resources: -// * `stack-id` will be `namespace/serviceName` -// * `service.k8s.aws/resource: resource-id` will be applied on all AWS resources provisioned for Service resources: -// * For LoadBalancer, `resource-id` will be `LoadBalancer` -// * For TargetGroup, `resource-id` will be `namespace/serviceName:servicePort` -//For K8s resources created by this controller, the labelling strategy is as follows: -// * For explicit IngressGroup, the following tags will be applied on all K8s resources: -// * `ingress.k8s.aws/stack: groupName` -// * For implicit IngressGroup, the following tags will be applied on all K8s resources: -// * `ingress.k8s.aws/stack-namespace: namespace` -// * `ingress.k8s.aws/stack-name: ingressName` -// * For Service, the following tags will be applied on all K8s resources: -// * `service.k8s.aws/stack-namespace: namespace` -// * `service.k8s.aws/stack-name: serviceName` - -// AWS TagKey for cluster resources. -const clusterNameTagKey = "elbv2.k8s.aws/cluster" - -// Legacy AWS TagKey for cluster resources, which is used by AWSALBIngressController(v1.1.3+) -const clusterNameTagKeyLegacy = "ingress.k8s.aws/cluster" - -// an abstraction that generates metadata to track actual resources provisioned for stack. -type Provider interface { - // ResourceIDTagKey provide the tagKey for resourceID. - ResourceIDTagKey() string - - // StackTags provide the tags for stack. - StackTags(stack core.Stack) map[string]string - - // ResourceTags provide the tags for stack resources - ResourceTags(stack core.Stack, res core.Resource, additionalTags map[string]string) map[string]string - - // StackLabels provide the suitable k8s labels for stack. - StackLabels(stack core.Stack) map[string]string - - // StackTagsLegacy provides the tags for stack with legacy clusterName. - // this is for backwards compatibility with AWSALBIngressController(v1.1.3+) - StackTagsLegacy(stack core.Stack) map[string]string - - // LegacyTagKeys returns AWS tag keys added to AWS resources provisioned by AWSALBIngressController(v1.1.3+). - // These tag keys is required for AWSALBIngressController(v1.1.3+) to identify resources. - // To be able to downgrade AWSLoadBalancerController to AWSALBIngressController(v1.1.3+), we shouldn't remove these tag keys. - LegacyTagKeys() []string -} - -// NewDefaultProvider constructs defaultProvider -func NewDefaultProvider(tagPrefix string, clusterName string) *defaultProvider { - return &defaultProvider{ - tagPrefix: tagPrefix, - clusterName: clusterName, - } -} - -var _ Provider = &defaultProvider{} - -// defaultImplementation for Provider -type defaultProvider struct { - tagPrefix string - clusterName string -} - -func (p *defaultProvider) ResourceIDTagKey() string { - return p.prefixedTrackingKey("resource") -} - -func (p *defaultProvider) StackTags(stack core.Stack) map[string]string { - stackID := stack.StackID() - return map[string]string{ - clusterNameTagKey: p.clusterName, - p.prefixedTrackingKey("stack"): stackID.String(), - } -} - -func (p *defaultProvider) ResourceTags(stack core.Stack, res core.Resource, additionalTags map[string]string) map[string]string { - stackTags := p.StackTags(stack) - resourceIDTags := map[string]string{ - p.ResourceIDTagKey(): res.ID(), - } - return algorithm.MergeStringMap(stackTags, resourceIDTags, additionalTags) -} - -func (p *defaultProvider) StackLabels(stack core.Stack) map[string]string { - stackID := stack.StackID() - if stackID.Namespace == "" { - return map[string]string{ - p.prefixedTrackingKey("stack"): stackID.Name, - } - } - return map[string]string{ - p.prefixedTrackingKey("stack-namespace"): stackID.Namespace, - p.prefixedTrackingKey("stack-name"): stackID.Name, - } -} - -func (p *defaultProvider) StackTagsLegacy(stack core.Stack) map[string]string { - stackID := stack.StackID() - return map[string]string{ - clusterNameTagKeyLegacy: p.clusterName, - p.prefixedTrackingKey("stack"): stackID.String(), - } -} - -func (p *defaultProvider) LegacyTagKeys() []string { - return []string{ - fmt.Sprintf("kubernetes.io/cluster/%s", p.clusterName), - "kubernetes.io/cluster-name", - "kubernetes.io/namespace", - "kubernetes.io/ingress-name", - "kubernetes.io/service-name", - "kubernetes.io/service-port", - clusterNameTagKeyLegacy, - } -} - -func (p *defaultProvider) prefixedTrackingKey(tag string) string { - return fmt.Sprintf("%v/%v", p.tagPrefix, tag) -} diff --git a/pkg/deploy/tracking/provider_test.go b/pkg/deploy/tracking/provider_test.go deleted file mode 100644 index 870c8ec33..000000000 --- a/pkg/deploy/tracking/provider_test.go +++ /dev/null @@ -1,242 +0,0 @@ -package tracking - -import ( - "github.com/stretchr/testify/assert" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" - "testing" -) - -func Test_defaultProvider_ResourceIDTagKey(t *testing.T) { - tests := []struct { - name string - provider *defaultProvider - want string - }{ - { - name: "resourceTagKey for Ingress", - provider: NewDefaultProvider("ingress.k8s.aws", "cluster-name"), - want: "ingress.k8s.aws/resource", - }, - { - name: "resourceTagKey for Service", - provider: NewDefaultProvider("service.k8s.aws", "cluster-name"), - want: "service.k8s.aws/resource", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := tt.provider.ResourceIDTagKey() - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_defaultProvider_StackTags(t *testing.T) { - type args struct { - stack core.Stack - } - tests := []struct { - name string - provider *defaultProvider - args args - want map[string]string - }{ - { - name: "stackTags for explicit IngressGroup", - provider: NewDefaultProvider("ingress.k8s.aws", "cluster-name"), - args: args{stack: core.NewDefaultStack(core.StackID{Namespace: "", Name: "awesome-group"})}, - want: map[string]string{ - "elbv2.k8s.aws/cluster": "cluster-name", - "ingress.k8s.aws/stack": "awesome-group", - }, - }, - { - name: "stackTags for implicit IngressGroup", - provider: NewDefaultProvider("ingress.k8s.aws", "cluster-name"), - args: args{stack: core.NewDefaultStack(core.StackID{Namespace: "namespace", Name: "ingressName"})}, - want: map[string]string{ - "elbv2.k8s.aws/cluster": "cluster-name", - "ingress.k8s.aws/stack": "namespace/ingressName", - }, - }, - { - name: "stackTags for Service", - provider: NewDefaultProvider("service.k8s.aws", "cluster-name"), - args: args{stack: core.NewDefaultStack(core.StackID{Namespace: "namespace", Name: "serviceName"})}, - want: map[string]string{ - "elbv2.k8s.aws/cluster": "cluster-name", - "service.k8s.aws/stack": "namespace/serviceName", - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := tt.provider.StackTags(tt.args.stack) - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_defaultProvider_ResourceTags(t *testing.T) { - stack := core.NewDefaultStack(core.StackID{Namespace: "namespace", Name: "ingressName"}) - fakeRes := core.NewFakeResource(stack, "fake", "fake-id", core.FakeResourceSpec{}, nil) - - type args struct { - stack core.Stack - res core.Resource - additionalTags map[string]string - } - tests := []struct { - name string - provider *defaultProvider - args args - want map[string]string - }{ - { - name: "resourceTags for Ingress", - provider: NewDefaultProvider("ingress.k8s.aws", "cluster-name"), - args: args{ - stack: stack, - res: fakeRes, - }, - want: map[string]string{ - "elbv2.k8s.aws/cluster": "cluster-name", - "ingress.k8s.aws/stack": "namespace/ingressName", - "ingress.k8s.aws/resource": "fake-id", - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := tt.provider.ResourceTags(tt.args.stack, tt.args.res, tt.args.additionalTags) - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_defaultProvider_StackLabels(t *testing.T) { - type args struct { - stack core.Stack - } - tests := []struct { - name string - provider *defaultProvider - args args - want map[string]string - }{ - { - name: "stackLabels for explicit IngressGroup", - provider: NewDefaultProvider("ingress.k8s.aws", "cluster-name"), - args: args{stack: core.NewDefaultStack(core.StackID{Namespace: "", Name: "awesome-group"})}, - want: map[string]string{ - "ingress.k8s.aws/stack": "awesome-group", - }, - }, - { - name: "stackLabels for implicit IngressGroup", - provider: NewDefaultProvider("ingress.k8s.aws", "cluster-name"), - args: args{stack: core.NewDefaultStack(core.StackID{Namespace: "namespace", Name: "ingressName"})}, - want: map[string]string{ - "ingress.k8s.aws/stack-namespace": "namespace", - "ingress.k8s.aws/stack-name": "ingressName", - }, - }, - { - name: "stackLabels for Service", - provider: NewDefaultProvider("service.k8s.aws", "cluster-name"), - args: args{stack: core.NewDefaultStack(core.StackID{Namespace: "namespace", Name: "serviceName"})}, - want: map[string]string{ - "service.k8s.aws/stack-namespace": "namespace", - "service.k8s.aws/stack-name": "serviceName", - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := tt.provider.StackLabels(tt.args.stack) - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_defaultProvider_StackTagsLegacy(t *testing.T) { - type args struct { - stack core.Stack - } - tests := []struct { - name string - provider *defaultProvider - args args - want map[string]string - }{ - { - name: "stackTags for explicit IngressGroup", - provider: NewDefaultProvider("ingress.k8s.aws", "cluster-name"), - args: args{stack: core.NewDefaultStack(core.StackID{Namespace: "", Name: "awesome-group"})}, - want: map[string]string{ - "ingress.k8s.aws/cluster": "cluster-name", - "ingress.k8s.aws/stack": "awesome-group", - }, - }, - { - name: "stackTags for implicit IngressGroup", - provider: NewDefaultProvider("ingress.k8s.aws", "cluster-name"), - args: args{stack: core.NewDefaultStack(core.StackID{Namespace: "namespace", Name: "ingressName"})}, - want: map[string]string{ - "ingress.k8s.aws/cluster": "cluster-name", - "ingress.k8s.aws/stack": "namespace/ingressName", - }, - }, - { - name: "stackTags for Service", - provider: NewDefaultProvider("service.k8s.aws", "cluster-name"), - args: args{stack: core.NewDefaultStack(core.StackID{Namespace: "namespace", Name: "serviceName"})}, - want: map[string]string{ - "ingress.k8s.aws/cluster": "cluster-name", - "service.k8s.aws/stack": "namespace/serviceName", - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := tt.provider.StackTagsLegacy(tt.args.stack) - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_defaultProvider_LegacyTagKeys(t *testing.T) { - type fields struct { - clusterName string - } - tests := []struct { - name string - fields fields - want []string - }{ - { - name: "standard case", - fields: fields{ - clusterName: "my-cluster", - }, - want: []string{ - "kubernetes.io/cluster/my-cluster", - "kubernetes.io/cluster-name", - "kubernetes.io/namespace", - "kubernetes.io/ingress-name", - "kubernetes.io/service-name", - "kubernetes.io/service-port", - "ingress.k8s.aws/cluster", - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - p := &defaultProvider{ - clusterName: tt.fields.clusterName, - } - got := p.LegacyTagKeys() - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/deploy/tracking/tag_filter.go b/pkg/deploy/tracking/tag_filter.go deleted file mode 100644 index 1a5947155..000000000 --- a/pkg/deploy/tracking/tag_filter.go +++ /dev/null @@ -1,39 +0,0 @@ -package tracking - -// TagFilter presents tag filter for multiple TagKeys. -// the TagKey is represented by mapKey, and TagValues is represented by tagValues -// if the TagValue is empty, then it only requires the TagKey presents. -// if the TagValue is not empty, then it requires TagKey presents and one of the TagValue matches. -type TagFilter map[string][]string - -func (f TagFilter) Matches(tags map[string]string) bool { - for key, desiredValues := range f { - actualValue, ok := tags[key] - if !ok { - return false - } - if len(desiredValues) == 0 { - continue - } - matchedAnyValue := false - for _, desiredValue := range desiredValues { - if desiredValue == actualValue { - matchedAnyValue = true - break - } - } - if !matchedAnyValue { - return false - } - } - return true -} - -// TagsAsTagFilter constructs TagFilter from Tags. -func TagsAsTagFilter(tags map[string]string) TagFilter { - tagFilter := make(TagFilter, len(tags)) - for key, value := range tags { - tagFilter[key] = []string{value} - } - return tagFilter -} diff --git a/pkg/deploy/tracking/tag_filter_test.go b/pkg/deploy/tracking/tag_filter_test.go deleted file mode 100644 index f48e3a31e..000000000 --- a/pkg/deploy/tracking/tag_filter_test.go +++ /dev/null @@ -1,169 +0,0 @@ -package tracking - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func TestTagFilter_Matches(t *testing.T) { - type args struct { - tags map[string]string - } - tests := []struct { - name string - f TagFilter - args args - want bool - }{ - { - name: "empty tagFilter should match everything", - f: TagFilter{}, - args: args{ - tags: map[string]string{ - "tagA": "valueA", - }, - }, - want: true, - }, - { - name: "tagFilter with key only should match if key exists", - f: TagFilter{ - "tagA": {}, - }, - args: args{ - tags: map[string]string{ - "tagA": "valueA1", - }, - }, - want: true, - }, - { - name: "tagFilter with key and single value should match if value matches", - f: TagFilter{ - "tagA": {"valueA1"}, - }, - args: args{ - tags: map[string]string{ - "tagA": "valueA1", - }, - }, - want: true, - }, - { - name: "tagFilter with key and single value should mismatch if value mismatches", - f: TagFilter{ - "tagA": {"valueA2"}, - }, - args: args{ - tags: map[string]string{ - "tagA": "valueA1", - }, - }, - want: false, - }, - { - name: "tagFilter with key and multiple values should match if any value matches", - f: TagFilter{ - "tagA": {"valueA1", "valueA2"}, - }, - args: args{ - tags: map[string]string{ - "tagA": "valueA2", - }, - }, - want: true, - }, - { - name: "tagFilter with key and multiple values should mismatch if no value matches", - f: TagFilter{ - "tagA": {"valueA1", "valueA2"}, - }, - args: args{ - tags: map[string]string{ - "tagA": "valueA3", - }, - }, - want: false, - }, - { - name: "multiple tagFilter matches if all of them matches", - f: TagFilter{ - "tagA": {}, - "tagB": {"valueB1"}, - "tagC": {"valueC1", "valueC2"}, - }, - args: args{ - tags: map[string]string{ - "tagA": "valueA1", - "tagB": "valueB1", - "tagC": "valueC2", - }, - }, - want: true, - }, - { - name: "multiple tagFilter mismatches if any of them mismatches", - f: TagFilter{ - "tagA": {}, - "tagB": {"valueB1"}, - "tagC": {"valueC1", "valueC2"}, - }, - args: args{ - tags: map[string]string{ - "tagA": "valueA1", - "tagB": "valueB1", - "tagC": "valueC3", - }, - }, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := tt.f.Matches(tt.args.tags) - assert.Equal(t, tt.want, got) - }) - } -} - -func TestTagsAsTagFilter(t *testing.T) { - type args struct { - tags map[string]string - } - tests := []struct { - name string - args args - want TagFilter - }{ - { - name: "single k-v pair", - args: args{ - tags: map[string]string{ - "key": "value", - }, - }, - want: TagFilter{ - "key": {"value"}, - }, - }, - { - name: "multiple k-v pair", - args: args{ - tags: map[string]string{ - "key1": "value1", - "key2": "value2", - }, - }, - want: TagFilter{ - "key1": {"value1"}, - "key2": {"value2"}, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := TagsAsTagFilter(tt.args.tags) - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/deploy/wafregional/web_acl_association_manager.go b/pkg/deploy/wafregional/web_acl_association_manager.go deleted file mode 100644 index 44935c8b6..000000000 --- a/pkg/deploy/wafregional/web_acl_association_manager.go +++ /dev/null @@ -1,105 +0,0 @@ -package wafregional - -import ( - "context" - awssdk "github.com/aws/aws-sdk-go/aws" - wafregionalsdk "github.com/aws/aws-sdk-go/service/wafregional" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/util/cache" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - "time" -) - -const defaultWebACLIDByResourceARNCacheTTL = 10 * time.Minute - -// WebACLAssociationManager is responsible for manage WAFRegion webACL associations. -type WebACLAssociationManager interface { - // AssociateWebACL associate webACL to resources. - AssociateWebACL(ctx context.Context, resourceARN string, webACLID string) error - - // DisassociateWebACL disassociate webACL from resources. - DisassociateWebACL(ctx context.Context, resourceARN string) error - - // GetAssociatedWebACL returns the associated webACL for resource, returns empty if no webACL is associated. - GetAssociatedWebACL(ctx context.Context, resourceARN string) (string, error) -} - -// NewDefaultWebACLAssociationManager constructs new defaultWebACLAssociationManager. -func NewDefaultWebACLAssociationManager(wafRegionalClient services.WAFRegional, logger logr.Logger) *defaultWebACLAssociationManager { - return &defaultWebACLAssociationManager{ - wafRegionalClient: wafRegionalClient, - logger: logger, - webACLIDByResourceARNCache: cache.NewExpiring(), - webACLIDByResourceARNCacheTTL: defaultWebACLIDByResourceARNCacheTTL, - } -} - -var _ WebACLAssociationManager = &defaultWebACLAssociationManager{} - -// default implementation for WebACLAssociationManager. -type defaultWebACLAssociationManager struct { - wafRegionalClient services.WAFRegional - logger logr.Logger - - // cache that stores webACLARN indexed by resourceARN - // The cache value is string, while "" represents no webACL. - webACLIDByResourceARNCache *cache.Expiring - // ttl for webACLARNByResourceARNCache - webACLIDByResourceARNCacheTTL time.Duration -} - -func (m *defaultWebACLAssociationManager) AssociateWebACL(ctx context.Context, resourceARN string, webACLID string) error { - req := &wafregionalsdk.AssociateWebACLInput{ - ResourceArn: awssdk.String(resourceARN), - WebACLId: awssdk.String(webACLID), - } - m.logger.Info("associating WAFRegional webACL", - "resourceARN", resourceARN, - "webACLID", webACLID) - if _, err := m.wafRegionalClient.AssociateWebACLWithContext(ctx, req); err != nil { - return err - } - m.logger.Info("associated WAFRegional webACL", - "resourceARN", resourceARN, - "webACLID", webACLID) - m.webACLIDByResourceARNCache.Set(resourceARN, webACLID, m.webACLIDByResourceARNCacheTTL) - return nil -} - -func (m *defaultWebACLAssociationManager) DisassociateWebACL(ctx context.Context, resourceARN string) error { - req := &wafregionalsdk.DisassociateWebACLInput{ - ResourceArn: awssdk.String(resourceARN), - } - m.logger.Info("disassociating WAFRegional webACL", - "resourceARN", resourceARN) - if _, err := m.wafRegionalClient.DisassociateWebACLWithContext(ctx, req); err != nil { - return err - } - m.logger.Info("disassociated WAFRegional webACL", - "resourceARN", resourceARN) - m.webACLIDByResourceARNCache.Set(resourceARN, "", m.webACLIDByResourceARNCacheTTL) - return nil -} - -func (m *defaultWebACLAssociationManager) GetAssociatedWebACL(ctx context.Context, resourceARN string) (string, error) { - rawCacheItem, exists := m.webACLIDByResourceARNCache.Get(resourceARN) - if exists { - return rawCacheItem.(string), nil - } - - req := &wafregionalsdk.GetWebACLForResourceInput{ - ResourceArn: awssdk.String(resourceARN), - } - - resp, err := m.wafRegionalClient.GetWebACLForResourceWithContext(ctx, req) - if err != nil { - return "", err - } - var webACLID string - if resp.WebACLSummary != nil { - webACLID = awssdk.StringValue(resp.WebACLSummary.WebACLId) - } - - m.webACLIDByResourceARNCache.Set(resourceARN, webACLID, m.webACLIDByResourceARNCacheTTL) - return webACLID, nil -} diff --git a/pkg/deploy/wafregional/web_acl_association_synthesizer.go b/pkg/deploy/wafregional/web_acl_association_synthesizer.go deleted file mode 100644 index 4fde5b750..000000000 --- a/pkg/deploy/wafregional/web_acl_association_synthesizer.go +++ /dev/null @@ -1,100 +0,0 @@ -package wafregional - -import ( - "context" - "github.com/go-logr/logr" - "github.com/pkg/errors" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" - wafregionalmodel "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/wafregional" -) - -// NewWebACLAssociationSynthesizer constructs new webACLAssociationSynthesizer. -func NewWebACLAssociationSynthesizer(associationManager WebACLAssociationManager, logger logr.Logger, stack core.Stack) *webACLAssociationSynthesizer { - return &webACLAssociationSynthesizer{ - associationManager: associationManager, - logger: logger, - stack: stack, - } -} - -type webACLAssociationSynthesizer struct { - associationManager WebACLAssociationManager - logger logr.Logger - stack core.Stack -} - -func (s *webACLAssociationSynthesizer) Synthesize(ctx context.Context) error { - var resAssociations []*wafregionalmodel.WebACLAssociation - s.stack.ListResources(&resAssociations) - resAssociationsByResARN, err := mapResWebACLAssociationByResourceARN(resAssociations) - if err != nil { - return err - } - - var resLBs []*elbv2model.LoadBalancer - s.stack.ListResources(&resLBs) - for _, resLB := range resLBs { - // wafRegional WebACL can only be associated with ALB for now. - if resLB.Spec.Type != elbv2model.LoadBalancerTypeApplication { - continue - } - lbARN, err := resLB.LoadBalancerARN().Resolve(ctx) - if err != nil { - return err - } - resAssociations := resAssociationsByResARN[lbARN] - if err := s.synthesizeWebACLAssociationsOnLB(ctx, lbARN, resAssociations); err != nil { - return err - } - } - return nil -} - -func (s *webACLAssociationSynthesizer) PostSynthesize(ctx context.Context) error { - // nothing to do here. - return nil -} - -func (s *webACLAssociationSynthesizer) synthesizeWebACLAssociationsOnLB(ctx context.Context, lbARN string, resAssociations []*wafregionalmodel.WebACLAssociation) error { - if len(resAssociations) > 1 { - return errors.Errorf("[should never happen] multiple WAFRegional webACL desired on LoadBalancer: %v", lbARN) - } - - var desiredWebACLID string - if len(resAssociations) == 1 { - desiredWebACLID = resAssociations[0].Spec.WebACLID - } - currentWebACLID, err := s.associationManager.GetAssociatedWebACL(ctx, lbARN) - if err != nil { - return err - } - switch { - case desiredWebACLID == "" && currentWebACLID != "": - if err := s.associationManager.DisassociateWebACL(ctx, lbARN); err != nil { - return errors.Wrap(err, "failed to delete WAFv2 WAFRegional association on LoadBalancer") - } - case desiredWebACLID != "" && currentWebACLID == "": - if err := s.associationManager.AssociateWebACL(ctx, lbARN, desiredWebACLID); err != nil { - return errors.Wrap(err, "failed to create WAFv2 WAFRegional association on LoadBalancer") - } - case desiredWebACLID != "" && currentWebACLID != "" && desiredWebACLID != currentWebACLID: - if err := s.associationManager.AssociateWebACL(ctx, lbARN, desiredWebACLID); err != nil { - return errors.Wrap(err, "failed to update WAFv2 WAFRegional association on LoadBalancer") - } - } - return nil -} - -func mapResWebACLAssociationByResourceARN(resAssociations []*wafregionalmodel.WebACLAssociation) (map[string][]*wafregionalmodel.WebACLAssociation, error) { - resAssociationsByResARN := make(map[string][]*wafregionalmodel.WebACLAssociation, len(resAssociations)) - ctx := context.Background() - for _, resAssociation := range resAssociations { - resARN, err := resAssociation.Spec.ResourceARN.Resolve(ctx) - if err != nil { - return nil, err - } - resAssociationsByResARN[resARN] = append(resAssociationsByResARN[resARN], resAssociation) - } - return resAssociationsByResARN, nil -} diff --git a/pkg/deploy/wafv2/web_acl_association_manager.go b/pkg/deploy/wafv2/web_acl_association_manager.go deleted file mode 100644 index 7094de9f5..000000000 --- a/pkg/deploy/wafv2/web_acl_association_manager.go +++ /dev/null @@ -1,105 +0,0 @@ -package wafv2 - -import ( - "context" - awssdk "github.com/aws/aws-sdk-go/aws" - wafv2sdk "github.com/aws/aws-sdk-go/service/wafv2" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/util/cache" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/aws/services" - "time" -) - -const defaultWebACLARNByResourceARNCacheTTL = 10 * time.Minute - -// WebACLAssociationManager is responsible for manage WAFv2 webACL associations. -type WebACLAssociationManager interface { - // AssociateWebACL associate webACL to resources. - AssociateWebACL(ctx context.Context, resourceARN string, webACLARN string) error - - // DisassociateWebACL disassociate webACL from resources. - DisassociateWebACL(ctx context.Context, resourceARN string) error - - // GetAssociatedWebACL returns the associated webACL for resource, returns empty if no webACL is associated. - GetAssociatedWebACL(ctx context.Context, resourceARN string) (string, error) -} - -// NewDefaultWebACLAssociationManager constructs new defaultWebACLAssociationManager. -func NewDefaultWebACLAssociationManager(wafv2Client services.WAFv2, logger logr.Logger) *defaultWebACLAssociationManager { - return &defaultWebACLAssociationManager{ - wafv2Client: wafv2Client, - logger: logger, - webACLARNByResourceARNCache: cache.NewExpiring(), - webACLARNByResourceARNCacheTTL: defaultWebACLARNByResourceARNCacheTTL, - } -} - -var _ WebACLAssociationManager = &defaultWebACLAssociationManager{} - -// default implementation for WebACLAssociationManager. -type defaultWebACLAssociationManager struct { - wafv2Client services.WAFv2 - logger logr.Logger - - // cache that stores webACLARN indexed by resourceARN - // The cache value is string, while "" represents no webACL. - webACLARNByResourceARNCache *cache.Expiring - // ttl for webACLARNByResourceARNCache - webACLARNByResourceARNCacheTTL time.Duration -} - -func (m *defaultWebACLAssociationManager) AssociateWebACL(ctx context.Context, resourceARN string, webACLARN string) error { - req := &wafv2sdk.AssociateWebACLInput{ - ResourceArn: awssdk.String(resourceARN), - WebACLArn: awssdk.String(webACLARN), - } - m.logger.Info("associating WAFv2 webACL", - "resourceARN", resourceARN, - "webACLARN", webACLARN) - if _, err := m.wafv2Client.AssociateWebACLWithContext(ctx, req); err != nil { - return err - } - m.logger.Info("associated WAFv2 webACL", - "resourceARN", resourceARN, - "webACLARN", webACLARN) - m.webACLARNByResourceARNCache.Set(resourceARN, webACLARN, m.webACLARNByResourceARNCacheTTL) - return nil -} - -func (m *defaultWebACLAssociationManager) DisassociateWebACL(ctx context.Context, resourceARN string) error { - req := &wafv2sdk.DisassociateWebACLInput{ - ResourceArn: awssdk.String(resourceARN), - } - m.logger.Info("disassociating WAFv2 webACL", - "resourceARN", resourceARN) - if _, err := m.wafv2Client.DisassociateWebACLWithContext(ctx, req); err != nil { - return err - } - m.logger.Info("disassociated WAFv2 webACL", - "resourceARN", resourceARN) - m.webACLARNByResourceARNCache.Set(resourceARN, "", m.webACLARNByResourceARNCacheTTL) - return nil -} - -func (m *defaultWebACLAssociationManager) GetAssociatedWebACL(ctx context.Context, resourceARN string) (string, error) { - rawCacheItem, exists := m.webACLARNByResourceARNCache.Get(resourceARN) - if exists { - return rawCacheItem.(string), nil - } - - req := &wafv2sdk.GetWebACLForResourceInput{ - ResourceArn: awssdk.String(resourceARN), - } - - resp, err := m.wafv2Client.GetWebACLForResourceWithContext(ctx, req) - if err != nil { - return "", err - } - var webACLARN string - if resp.WebACL != nil { - webACLARN = awssdk.StringValue(resp.WebACL.ARN) - } - - m.webACLARNByResourceARNCache.Set(resourceARN, webACLARN, m.webACLARNByResourceARNCacheTTL) - return webACLARN, nil -} diff --git a/pkg/deploy/wafv2/web_acl_association_synthesizer.go b/pkg/deploy/wafv2/web_acl_association_synthesizer.go deleted file mode 100644 index 1093a2c0f..000000000 --- a/pkg/deploy/wafv2/web_acl_association_synthesizer.go +++ /dev/null @@ -1,100 +0,0 @@ -package wafv2 - -import ( - "context" - "github.com/go-logr/logr" - "github.com/pkg/errors" - "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/core" - elbv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/elbv2" - wafv2model "github.com/sonal-chauhan/aws-load-balancer-controller/pkg/model/wafv2" -) - -// NewWebACLAssociationSynthesizer constructs new webACLAssociationSynthesizer. -func NewWebACLAssociationSynthesizer(associationManager WebACLAssociationManager, logger logr.Logger, stack core.Stack) *webACLAssociationSynthesizer { - return &webACLAssociationSynthesizer{ - associationManager: associationManager, - logger: logger, - stack: stack, - } -} - -type webACLAssociationSynthesizer struct { - associationManager WebACLAssociationManager - logger logr.Logger - stack core.Stack -} - -func (s *webACLAssociationSynthesizer) Synthesize(ctx context.Context) error { - var resAssociations []*wafv2model.WebACLAssociation - s.stack.ListResources(&resAssociations) - resAssociationsByResARN, err := mapResWebACLAssociationByResourceARN(resAssociations) - if err != nil { - return err - } - - var resLBs []*elbv2model.LoadBalancer - s.stack.ListResources(&resLBs) - for _, resLB := range resLBs { - // wafv2 WebACL can only be associated with ALB for now. - if resLB.Spec.Type != elbv2model.LoadBalancerTypeApplication { - continue - } - lbARN, err := resLB.LoadBalancerARN().Resolve(ctx) - if err != nil { - return err - } - resAssociations := resAssociationsByResARN[lbARN] - if err := s.synthesizeWebACLAssociationsOnLB(ctx, lbARN, resAssociations); err != nil { - return err - } - } - return nil -} - -func (s *webACLAssociationSynthesizer) PostSynthesize(ctx context.Context) error { - // nothing to do here. - return nil -} - -func (s *webACLAssociationSynthesizer) synthesizeWebACLAssociationsOnLB(ctx context.Context, lbARN string, resAssociations []*wafv2model.WebACLAssociation) error { - if len(resAssociations) > 1 { - return errors.Errorf("[should never happen] multiple WAFv2 webACL desired on LoadBalancer: %v", lbARN) - } - - var desiredWebACLARN string - if len(resAssociations) == 1 { - desiredWebACLARN = resAssociations[0].Spec.WebACLARN - } - currentWebACLARN, err := s.associationManager.GetAssociatedWebACL(ctx, lbARN) - if err != nil { - return err - } - switch { - case desiredWebACLARN == "" && currentWebACLARN != "": - if err := s.associationManager.DisassociateWebACL(ctx, lbARN); err != nil { - return errors.Wrap(err, "failed to delete WAFv2 webACL association on LoadBalancer") - } - case desiredWebACLARN != "" && currentWebACLARN == "": - if err := s.associationManager.AssociateWebACL(ctx, lbARN, desiredWebACLARN); err != nil { - return errors.Wrap(err, "failed to create WAFv2 webACL association on LoadBalancer") - } - case desiredWebACLARN != "" && currentWebACLARN != "" && desiredWebACLARN != currentWebACLARN: - if err := s.associationManager.AssociateWebACL(ctx, lbARN, desiredWebACLARN); err != nil { - return errors.Wrap(err, "failed to update WAFv2 webACL association on LoadBalancer") - } - } - return nil -} - -func mapResWebACLAssociationByResourceARN(resAssociations []*wafv2model.WebACLAssociation) (map[string][]*wafv2model.WebACLAssociation, error) { - resAssociationsByResARN := make(map[string][]*wafv2model.WebACLAssociation, len(resAssociations)) - ctx := context.Background() - for _, resAssociation := range resAssociations { - resARN, err := resAssociation.Spec.ResourceARN.Resolve(ctx) - if err != nil { - return nil, err - } - resAssociationsByResARN[resARN] = append(resAssociationsByResARN[resARN], resAssociation) - } - return resAssociationsByResARN, nil -}