Skip to content

Commit 45c7d37

Browse files
authored
fix(ecs-cluster): fix regex name filtering for ECS clusters (#901)
Resolves an issue where ECS cluster name filtering with regex patterns was not working correctly. The previous implementation separated name and time filtering logic, causing the name filter to be ignored when time-based filtering was active. Changes: - Consolidated filtering logic to properly check both name and time filters together - Simplified the filtering flow by removing redundant helper functions - Fixed the ShouldInclude call to pass both Name and Time values for proper evaluation - Added comprehensive test coverage for various filtering scenarios This ensures that ECS clusters can be properly filtered by name regex patterns, including wildcard patterns and specific string matches. Fixes #900 Co-authored-by: James Kwon <96548424+hongil0316@users.noreply.github.com>
1 parent 3a42fb3 commit 45c7d37

2 files changed

Lines changed: 293 additions & 68 deletions

File tree

aws/resources/ecs_cluster.go

Lines changed: 48 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -22,109 +22,89 @@ const activeEcsClusterStatus string = "ACTIVE"
2222
// For more details on this, please read here: https://docs.aws.amazon.com/cli/latest/reference/ecs/describe-clusters.html#options
2323
const describeClustersRequestBatchSize = 100
2424

25-
// getAllEcsClusters - Returns a string of ECS Cluster ARNs, which uniquely identifies the cluster.
26-
// We need to get all clusters before we can get all services.
25+
// getAllEcsClusters returns all ECS Cluster ARNs.
26+
// Handles pagination until all pages are retrieved.
2727
func (clusters *ECSClusters) getAllEcsClusters() ([]*string, error) {
2828
var clusterArns []string
29-
result, err := clusters.Client.ListClusters(clusters.Context, &ecs.ListClustersInput{})
30-
if err != nil {
31-
return nil, errors.WithStackTrace(err)
32-
}
33-
clusterArns = append(clusterArns, result.ClusterArns...)
29+
nextToken := (*string)(nil)
3430

35-
// Handle pagination: continuously pull the next page if nextToken is set
36-
for aws.ToString(result.NextToken) != "" {
37-
result, err = clusters.Client.ListClusters(clusters.Context, &ecs.ListClustersInput{NextToken: result.NextToken})
31+
for {
32+
resp, err := clusters.Client.ListClusters(clusters.Context, &ecs.ListClustersInput{
33+
NextToken: nextToken,
34+
})
3835
if err != nil {
3936
return nil, errors.WithStackTrace(err)
4037
}
41-
clusterArns = append(clusterArns, result.ClusterArns...)
38+
39+
clusterArns = append(clusterArns, resp.ClusterArns...)
40+
if resp.NextToken == nil || *resp.NextToken == "" {
41+
break
42+
}
43+
nextToken = resp.NextToken
4244
}
4345

4446
return aws.StringSlice(clusterArns), nil
4547
}
4648

47-
// Filter all active ecs clusters
48-
func (clusters *ECSClusters) getAllActiveEcsClusterArns(configObj config.Config) ([]*string, error) {
49+
func (clusters *ECSClusters) getAll(c context.Context, configObj config.Config) ([]*string, error) {
4950
allClusters, err := clusters.getAllEcsClusters()
5051
if err != nil {
51-
logging.Debug("Error getting all ECS clusters")
5252
return nil, errors.WithStackTrace(err)
5353
}
5454

55-
var filteredEcsClusterArns []*string
55+
excludeFirstSeenTag, err := util.GetBoolFromContext(c, util.ExcludeFirstSeenTagKey)
56+
if err != nil {
57+
return nil, errors.WithStackTrace(err)
58+
}
59+
60+
var result []*string
61+
clusterList := aws.ToStringSlice(allClusters)
62+
batches := util.Split(clusterList, describeClustersRequestBatchSize)
5663

57-
batches := util.Split(aws.ToStringSlice(allClusters), describeClustersRequestBatchSize)
5864
for _, batch := range batches {
59-
input := &ecs.DescribeClustersInput{
65+
resp, err := clusters.Client.DescribeClusters(clusters.Context, &ecs.DescribeClustersInput{
6066
Clusters: batch,
67+
})
68+
if err != nil {
69+
return nil, errors.WithStackTrace(err)
6170
}
6271

63-
describedClusters, describeErr := clusters.Client.DescribeClusters(clusters.Context, input)
64-
if describeErr != nil {
65-
logging.Debugf("Error describing ECS clusters from input %s: ", input)
66-
return nil, errors.WithStackTrace(describeErr)
67-
}
68-
69-
for _, cluster := range describedClusters.Clusters {
70-
if shouldIncludeECSCluster(&cluster, configObj) {
71-
filteredEcsClusterArns = append(filteredEcsClusterArns, cluster.ClusterArn)
72+
for _, cluster := range resp.Clusters {
73+
if cluster.Status == nil || aws.ToString(cluster.Status) != activeEcsClusterStatus {
74+
continue
7275
}
73-
}
74-
}
75-
76-
return filteredEcsClusterArns, nil
77-
}
78-
79-
func shouldIncludeECSCluster(cluster *types.Cluster, configObj config.Config) bool {
80-
if cluster == nil {
81-
return false
82-
}
83-
84-
// Filter out invalid state ECS Clusters (will return only `ACTIVE` state clusters)
85-
// `cloud-nuke` needs to tag ECS Clusters it sees for the first time.
86-
// Therefore, to tag a cluster, that cluster must be in the `ACTIVE` state.
87-
logging.Debugf("Status for ECS Cluster %s is %s", aws.ToString(cluster.ClusterArn), aws.ToString(cluster.Status))
88-
if aws.ToString(cluster.Status) != activeEcsClusterStatus {
89-
return false
90-
}
9176

92-
return configObj.ECSCluster.ShouldInclude(config.ResourceValue{Name: cluster.ClusterName})
93-
}
94-
95-
func (clusters *ECSClusters) getAll(c context.Context, configObj config.Config) ([]*string, error) {
96-
clusterArns, err := clusters.getAllActiveEcsClusterArns(configObj)
97-
if err != nil {
98-
logging.Debugf("Error getting all ECS clusters with `ACTIVE` status")
99-
return nil, errors.WithStackTrace(err)
100-
}
77+
if !configObj.ECSCluster.ShouldInclude(config.ResourceValue{Name: cluster.ClusterName}) {
78+
continue
79+
}
10180

102-
excludeFirstSeenTag, err := util.GetBoolFromContext(c, util.ExcludeFirstSeenTagKey)
103-
if err != nil {
104-
return nil, errors.WithStackTrace(err)
105-
}
81+
if excludeFirstSeenTag {
82+
result = append(result, cluster.ClusterArn)
83+
continue
84+
}
10685

107-
var filteredEcsClusters []*string
108-
for _, clusterArn := range clusterArns {
109-
if !excludeFirstSeenTag {
110-
firstSeenTime, err := clusters.getFirstSeenTag(clusterArn)
86+
firstSeenTime, err := clusters.getFirstSeenTag(cluster.ClusterArn)
11187
if err != nil {
112-
logging.Debugf("Error getting the `cloud-nuke-first-seen` tag for ECS cluster with ARN %s", aws.ToString(clusterArn))
11388
return nil, errors.WithStackTrace(err)
11489
}
11590

11691
if firstSeenTime == nil {
117-
err := clusters.setFirstSeenTag(clusterArn, time.Now().UTC())
118-
if err != nil {
119-
logging.Debugf("Error tagging the ECS cluster with ARN %s", aws.ToString(clusterArn))
92+
if err := clusters.setFirstSeenTag(cluster.ClusterArn, time.Now().UTC()); err != nil {
12093
return nil, errors.WithStackTrace(err)
12194
}
122-
} else if configObj.ECSCluster.ShouldInclude(config.ResourceValue{Time: firstSeenTime}) {
123-
filteredEcsClusters = append(filteredEcsClusters, clusterArn)
95+
continue
96+
}
97+
98+
if configObj.ECSCluster.ShouldInclude(config.ResourceValue{
99+
Time: firstSeenTime,
100+
Name: cluster.ClusterName,
101+
}) {
102+
result = append(result, cluster.ClusterArn)
124103
}
125104
}
126105
}
127-
return filteredEcsClusters, nil
106+
107+
return result, nil
128108
}
129109

130110
func (clusters *ECSClusters) stopClusterRunningTasks(clusterArn *string) error {

0 commit comments

Comments
 (0)