@@ -40,9 +40,10 @@ def describe_all_ecr_images(repository: str) -> List[dict]:
40
40
return images
41
41
42
42
43
- def filter_images_matching_tag (images : List [dict ]) -> List [dict ]:
44
- """Filter list for images containing the target pattern"""
45
- images_matching_tag = []
43
+ def filter_tags_to_delete (images : List [dict ]) -> List [dict ]:
44
+ """Filter the image list to only delete tags matching the pattern, signatures, or untagged images."""
45
+ filtered_images = []
46
+ untagged_count = 0
46
47
for image_detail in images :
47
48
if "imageTags" in image_detail :
48
49
for tag in image_detail ["imageTags" ]:
@@ -54,8 +55,24 @@ def filter_images_matching_tag(images: List[dict]) -> List[dict]:
54
55
# Note that if the operator ever gets to major version 6, some tags can unintentionally match '_6'
55
56
# It is an easy and relatively reliable way of identifying our test images tags
56
57
if "_6" in tag or ".sig" in tag or contains_timestamped_tag (tag ):
57
- images_matching_tag .append ({"imageTag" : tag , "imagePushedAt" : image_detail ["imagePushedAt" ]})
58
- return images_matching_tag
58
+ filtered_images .append (
59
+ {
60
+ "imageTag" : tag ,
61
+ "imagePushedAt" : image_detail ["imagePushedAt" ],
62
+ "imageDigest" : image_detail ["imageDigest" ],
63
+ }
64
+ )
65
+ else :
66
+ filtered_images .append (
67
+ {
68
+ "imageTag" : "" ,
69
+ "imagePushedAt" : image_detail ["imagePushedAt" ],
70
+ "imageDigest" : image_detail ["imageDigest" ],
71
+ }
72
+ )
73
+ untagged_count += 1
74
+ print (f"found { untagged_count } untagged images" )
75
+ return filtered_images
59
76
60
77
61
78
# match 107.0.0.8502-1-b20241125T000000Z-arm64
@@ -70,11 +87,22 @@ def get_images_with_dates(repository: str) -> List[dict]:
70
87
"""Retrieve the list of patch images, corresponding to the regex, with push dates"""
71
88
ecr_images = describe_all_ecr_images (repository )
72
89
print (f"Found { len (ecr_images )} images in repository { repository } " )
73
- images_matching_tag = filter_images_matching_tag (ecr_images )
90
+ images_matching_tag = filter_tags_to_delete (ecr_images )
74
91
75
92
return images_matching_tag
76
93
77
94
95
+ def batch_delete_images (repository : str , images : List [dict ]) -> None :
96
+ print (f"Deleting { len (images )} images in repository { repository } " )
97
+ digests_to_delete = [{"imageDigest" : image ["imageDigest" ]} for image in images ]
98
+ # batch_delete_image only support a maximum of 100 images at a time
99
+ for i in range (0 , len (digests_to_delete ), 100 ):
100
+ batch = digests_to_delete [i : i + 100 ]
101
+ print (f"Deleting batch { i // 100 + 1 } with { len (batch )} images..." )
102
+ ecr_client .batch_delete_image (repositoryName = repository , registryId = REGISTRY_ID , imageIds = batch )
103
+ print (f"Deleted images" )
104
+
105
+
78
106
def delete_image (repository : str , image_tag : str ) -> None :
79
107
ecr_client .batch_delete_image (repositoryName = repository , registryId = REGISTRY_ID , imageIds = [{"imageTag" : image_tag }])
80
108
print (f"Deleted image with tag: { image_tag } " )
@@ -92,26 +120,28 @@ def delete_images(
92
120
# Process the images, deleting those older than the threshold
93
121
delete_count = 0
94
122
age_threshold_timedelta = timedelta (days = age_threshold )
123
+ images_to_delete = []
95
124
for image in images_with_dates :
96
125
tag = image ["imageTag" ]
97
126
push_date = image ["imagePushedAt" ]
98
127
image_age = current_time - push_date
99
128
100
- log_message_base = f"Image { tag } , was pushed at { push_date .isoformat ()} "
129
+ log_message_base = f"Image { tag if tag else 'UNTAGGED' } was pushed at { push_date .isoformat ()} "
101
130
delete_message = "should be cleaned up" if dry_run else "deleting..."
102
131
if image_age > age_threshold_timedelta :
103
132
print (f"{ log_message_base } , older than { age_threshold } day(s), { delete_message } " )
104
- if not dry_run :
105
- delete_image (repository , tag )
133
+ images_to_delete .append (image )
106
134
delete_count += 1
107
135
else :
108
136
print (f"{ log_message_base } , not older than { age_threshold } day(s)" )
137
+ if not dry_run :
138
+ batch_delete_images (repository , images_to_delete )
109
139
deleted_message = "need to be cleaned up" if dry_run else "deleted"
110
140
print (f"{ delete_count } images { deleted_message } " )
111
141
112
142
113
143
def cleanup_repository (repository : str , age_threshold : int = DEFAULT_AGE_THRESHOLD_DAYS , dry_run : bool = False ):
114
- print (f"Cleaning up images older than { DEFAULT_AGE_THRESHOLD_DAYS } day(s) from repository { repository } " )
144
+ print (f"Cleaning up images older than { age_threshold } day(s) from repository { repository } " )
115
145
print ("Getting list of images..." )
116
146
images_with_dates = get_images_with_dates (repository )
117
147
print (f"Images matching the pattern: { len (images_with_dates )} " )
0 commit comments