@@ -8,8 +8,11 @@ package main
8
8
9
9
import (
10
10
"context"
11
+ "errors"
11
12
"fmt"
12
13
"log"
14
+ "sort"
15
+ "strings"
13
16
"time"
14
17
15
18
"github.com/aws/aws-sdk-go-v2/aws"
@@ -22,18 +25,113 @@ import (
22
25
)
23
26
24
27
func main () {
25
- err := cleanAMI ()
28
+ err := cleanAMIs ()
26
29
if err != nil {
27
30
log .Fatalf ("errors cleaning %v" , err )
28
31
}
29
32
}
30
33
31
- func cleanAMI () error {
34
+ // takes a list of AMIs and sorts them by creation date (youngest to oldest)
35
+ func sortAMIsByCreationDate (amiList []types.Image , errList * []error ) []types.Image {
36
+ sort .Slice (amiList , func (i , j int ) bool {
37
+ if amiList [i ].CreationDate != nil && amiList [j ].CreationDate != nil {
38
+ iCreationDate , iErr := smithyTime .ParseDateTime (* amiList [i ].CreationDate )
39
+ jCreationDate , jErr := smithyTime .ParseDateTime (* amiList [j ].CreationDate )
40
+
41
+ if err := errors .Join (iErr , jErr ); err != nil && errList != nil {
42
+ * errList = append (* errList , err )
43
+ return false
44
+ }
45
+
46
+ return iCreationDate .After (jCreationDate )
47
+ } else {
48
+ return false
49
+ }
50
+ })
51
+
52
+ return amiList
53
+ }
54
+
55
+ // given a slice of AMIs, deregisters them one by one
56
+ func deregisterAMIs (ctx context.Context , ec2client * ec2.Client , images []types.Image , errList * []error ) {
57
+ for _ , image := range images {
58
+ if image .Name != nil && image .ImageId != nil && image .CreationDate != nil {
59
+ log .Printf ("Try to delete ami %v tags %v image id %v image creation date raw %v" , * image .Name , image .Tags , * image .ImageId , * image .CreationDate )
60
+ deregisterImageInput := & ec2.DeregisterImageInput {ImageId : image .ImageId }
61
+ _ , err := ec2client .DeregisterImage (ctx , deregisterImageInput )
62
+
63
+ if err != nil && errList != nil {
64
+ log .Printf ("Error while deregistering ami %v" , * image .Name )
65
+ * errList = append (* errList , err )
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ // given a map of macos version/architecture to a list of corresponding AMIs, deregister AMIs that are no longer needed
72
+ func cleanMacAMIs (ctx context.Context , ec2client * ec2.Client , macosImageAmiMap map [string ][]types.Image , expirationDate time.Time , errList * []error ) {
73
+ for name , amiList := range macosImageAmiMap {
74
+ // don't delete an ami if it's the only one for that version/architecture
75
+ if len (amiList ) == 1 {
76
+ continue
77
+ }
78
+
79
+ // Sort AMIs by creation date (youngest to oldest)
80
+ amiList = sortAMIsByCreationDate (amiList , errList )
81
+
82
+ // find the youngest AMI in the list
83
+ youngestCreationDate , err := smithyTime .ParseDateTime (aws .ToString (amiList [0 ].CreationDate ))
84
+
85
+ if err != nil && errList != nil {
86
+ * errList = append (* errList , err )
87
+ continue
88
+ }
89
+
90
+ if expirationDate .After (youngestCreationDate ) {
91
+ // If the youngest AMI is over 60 days old, we keep one (the youngest) and can delete the rest
92
+ log .Printf ("Youngest AMI for %s is over 60 days old. Deleting all but the youngest." , name )
93
+ deregisterAMIs (ctx , ec2client , amiList [1 :], errList )
94
+ } else {
95
+ // If the youngest AMI is under 60 days old, keep incrementing until we find AMIs older than 60 days and delete them
96
+ for index , ami := range amiList {
97
+ creationDate , err := smithyTime .ParseDateTime (aws .ToString (ami .CreationDate ))
98
+ if err != nil && errList != nil {
99
+ * errList = append (* errList , err )
100
+ continue
101
+ }
102
+ if expirationDate .After (creationDate ) {
103
+ // once you find the first AMI that's over 60 days old, delete the ones that follow
104
+ deregisterAMIs (ctx , ec2client , amiList [index :], errList )
105
+ break
106
+ }
107
+ }
108
+ }
109
+ }
110
+ }
111
+
112
+ // given a single non macos image, determine its age and deregister if needed
113
+ func cleanNonMacAMIs (ctx context.Context , ec2client * ec2.Client , image types.Image , expirationDate time.Time , errList * []error ) {
114
+ creationDate , err := smithyTime .ParseDateTime (aws .ToString (image .CreationDate ))
115
+ if err != nil && errList != nil {
116
+ * errList = append (* errList , err )
117
+ return
118
+ }
119
+
120
+ if expirationDate .After (creationDate ) {
121
+ deregisterAMIs (ctx , ec2client , []types.Image {image }, errList )
122
+ }
123
+ }
124
+
125
+ func cleanAMIs () error {
32
126
log .Print ("Begin to clean EC2 AMI" )
33
127
128
+ // sets expiration date to 60 days in the past
34
129
expirationDate := time .Now ().UTC ().Add (clean .KeepDurationSixtyDay )
35
- cxt := context .Background ()
36
- defaultConfig , err := config .LoadDefaultConfig (cxt )
130
+ log .Printf ("Expiration date set as %v" , expirationDate )
131
+
132
+ // load default config
133
+ ctx := context .Background ()
134
+ defaultConfig , err := config .LoadDefaultConfig (ctx )
37
135
if err != nil {
38
136
return err
39
137
}
@@ -46,30 +144,28 @@ func cleanAMI() error {
46
144
47
145
//get instances to delete
48
146
describeImagesInput := ec2.DescribeImagesInput {Filters : []types.Filter {nameFilter }}
49
- describeImagesOutput , err := ec2client .DescribeImages (cxt , & describeImagesInput )
147
+ describeImagesOutput , err := ec2client .DescribeImages (ctx , & describeImagesInput )
50
148
if err != nil {
51
149
return err
52
150
}
53
151
54
152
var errList []error
153
+ // stores a list of AMIs per each macos version/architecture
154
+ macosImageAmiMap := make (map [string ][]types.Image )
155
+
55
156
for _ , image := range describeImagesOutput .Images {
56
- creationDate , err := smithyTime .ParseDateTime (* image .CreationDate )
57
- if err != nil {
58
- errList = append (errList , err )
59
- continue
60
- }
61
- log .Printf ("image name %v image id %v experation date %v creation date parsed %v image creation date raw %v" ,
62
- * image .Name , * image .ImageId , creationDate , expirationDate , * image .CreationDate )
63
- if expirationDate .After (creationDate ) {
64
- log .Printf ("Try to delete ami %s tags %v launch-date %s" , * image .Name , image .Tags , * image .CreationDate )
65
- deregisterImageInput := ec2.DeregisterImageInput {ImageId : image .ImageId }
66
- _ , err := ec2client .DeregisterImage (cxt , & deregisterImageInput )
67
- if err != nil {
68
- errList = append (errList , err )
69
- }
157
+ if image .Name != nil && strings .HasPrefix (* image .Name , "cloudwatch-agent-integration-test-mac" ) {
158
+ // mac image - add it to the map and do nothing else for now
159
+ macosImageAmiMap [* image .Name ] = append (macosImageAmiMap [* image .Name ], image )
160
+ } else {
161
+ // non mac image - clean it if it's older than 60 days
162
+ cleanNonMacAMIs (ctx , ec2client , image , expirationDate , & errList )
70
163
}
71
164
}
72
165
166
+ // handle the mac AMIs
167
+ cleanMacAMIs (ctx , ec2client , macosImageAmiMap , expirationDate , & errList )
168
+
73
169
if len (errList ) != 0 {
74
170
return fmt .Errorf ("%v" , errList )
75
171
}
0 commit comments