@@ -13,22 +13,23 @@ import (
13
13
"time"
14
14
15
15
"golang.org/x/exp/maps"
16
- "oras.land/oras-go/pkg/registry/remote"
17
16
)
18
17
19
18
const (
20
19
// Max number of entries returned as specified in Quay API docs for listing tags
21
- pageLimit = 100
20
+ pageLimit = 100
21
+ accessTokenEnvKey = "ACCESS_TOKEN"
22
22
)
23
23
24
24
var (
25
- accessToken = os .Getenv ("ACCESS_TOKEN" )
25
+ accessToken = os .Getenv (accessTokenEnvKey )
26
26
preserveSubstrings = []string {
27
27
"latest" ,
28
28
// Preserve semver release branch or semver tag regex - release-vX.Y.Z(-rc1) or vX.Y.Z(-rc1)
29
29
// Based on https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
30
30
"^(v|release-v)(?P<major>0|[1-9]\\ d*)\\ .(?P<minor>0|[1-9]\\ d*)\\ .(?P<patch>0|[1-9]\\ d*)(?:-(?P<prerelease>(?:0|[1-9]\\ d*|\\ d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\ .(?:0|[1-9]\\ d*|\\ d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\ +(?P<buildmetadata>[0-9a-zA-Z-]+(?:\\ .[0-9a-zA-Z-]+)*))?$" ,
31
31
}
32
+ client = & http.Client {Timeout : 5 * time .Second }
32
33
)
33
34
34
35
// Tag represents a tag in the repository.
@@ -46,12 +47,11 @@ type TagsResponse struct {
46
47
47
48
func main () {
48
49
repo := flag .String ("repo" , "kuadrant/kuadrant-operator" , "Repository name" )
49
- baseURL := flag .String ("base-url" , "https://quay.io/api/v1/repository/ " , "Base API URL" )
50
+ baseURL := flag .String ("base-url" , "https://quay.io/api/v1/repository" , "Base API URL" )
50
51
dryRun := flag .Bool ("dry-run" , true , "Dry run" )
51
52
batchSize := flag .Int ("batch-size" , 50 , "Batch size for deletion. API calls might get rate limited at large values" )
52
53
flag .Parse ()
53
54
54
- client := & http.Client {}
55
55
logger := log .New (os .Stdout , "INFO: " , log .Ldate | log .Ltime )
56
56
57
57
if accessToken == "" {
@@ -62,7 +62,7 @@ func main() {
62
62
63
63
// Fetch tags from the API
64
64
logger .Println ("Fetching tags from Quay" )
65
- tags , err := fetchTags (client , baseURL , repo )
65
+ tags , err := fetchTags (baseURL , repo , accessToken )
66
66
if err != nil {
67
67
logger .Fatalln ("Error fetching tags:" , err )
68
68
}
@@ -90,18 +90,18 @@ func main() {
90
90
go func (tagName string ) {
91
91
defer wg .Done ()
92
92
93
- if dryRun != nil && * dryRun {
93
+ if * dryRun {
94
94
logger .Printf ("DRY RUN - Successfully deleted tag: %s\n " , tagName )
95
95
} else {
96
- if err := deleteTag (client , baseURL , repo , accessToken , tagName ); err != nil {
96
+ if err := deleteTag (baseURL , repo , accessToken , tagName ); err != nil {
97
97
logger .Println (err )
98
- } else {
99
- logger .Printf ("Successfully deleted tag: %s\n " , tagName )
100
98
}
99
+
100
+ logger .Printf ("Successfully deleted tag: %s\n " , tagName )
101
101
}
102
102
}(tagName )
103
103
104
- delete (tagsToDelete , tagName ) // Remove deleted tag from remainingTags
104
+ delete (tagsToDelete , tagName ) // Remove deleted tag from tagsToDelete
105
105
i ++
106
106
}
107
107
@@ -115,7 +115,7 @@ func main() {
115
115
116
116
// fetchTags retrieves the tags from the repository using the Quay.io API.
117
117
// https://docs.quay.io/api/swagger/#!/tag/listRepoTags
118
- func fetchTags (client remote. Client , baseURL , repo * string ) ([]Tag , error ) {
118
+ func fetchTags (baseURL , repo * string , accessToken string ) ([]Tag , error ) {
119
119
if baseURL == nil || repo == nil {
120
120
return nil , fmt .Errorf ("baseURL or repo required" )
121
121
}
@@ -124,14 +124,14 @@ func fetchTags(client remote.Client, baseURL, repo *string) ([]Tag, error) {
124
124
i := 1
125
125
126
126
for {
127
- url := fmt .Sprintf ("%s%s/tag/?page=%d&limit=%d" , * baseURL , * repo , i , pageLimit )
127
+ url := fmt .Sprintf ("%s/ %s/tag/?page=%d&limit=%d" , * baseURL , * repo , i , pageLimit )
128
128
req , err := http .NewRequest ("GET" , url , nil )
129
129
if err != nil {
130
130
return nil , fmt .Errorf ("error creating request: %w" , err )
131
131
}
132
132
133
133
// Required for private repos
134
- req .Header .Add ("Authorization" , "Bearer " + accessToken )
134
+ req .Header .Add ("Authorization" , fmt . Sprintf ( "Bearer %s" , accessToken ) )
135
135
136
136
// Execute the request
137
137
resp , err := client .Do (req )
@@ -142,7 +142,10 @@ func fetchTags(client remote.Client, baseURL, repo *string) ([]Tag, error) {
142
142
143
143
// Handle possible non-200 status codes
144
144
if resp .StatusCode != http .StatusOK {
145
- body , _ := io .ReadAll (resp .Body )
145
+ body , err := io .ReadAll (resp .Body )
146
+ if err != nil {
147
+ return nil , fmt .Errorf ("error reading response body: %w" , err )
148
+ }
146
149
return nil , fmt .Errorf ("error: received status code %d\n Body: %s" , resp .StatusCode , string (body ))
147
150
}
148
151
@@ -161,7 +164,7 @@ func fetchTags(client remote.Client, baseURL, repo *string) ([]Tag, error) {
161
164
allTags = append (allTags , tagsResp .Tags ... )
162
165
163
166
if tagsResp .HasAdditional {
164
- i += 1
167
+ i ++
165
168
continue
166
169
}
167
170
@@ -175,40 +178,43 @@ func fetchTags(client remote.Client, baseURL, repo *string) ([]Tag, error) {
175
178
// deleteTag sends a DELETE request to remove the specified tag from the repository
176
179
// Returns nil if successful, error otherwise
177
180
// https://docs.quay.io/api/swagger/#!/tag/deleteFullTag
178
- func deleteTag (client remote. Client , baseURL , repo * string , accessToken , tagName string ) error {
181
+ func deleteTag (baseURL , repo * string , accessToken , tagName string ) error {
179
182
if baseURL == nil || repo == nil {
180
183
return fmt .Errorf ("baseURL or repo required" )
181
184
}
182
185
183
- url := fmt .Sprintf ("%s%s/tag/%s" , * baseURL , * repo , tagName )
186
+ url := fmt .Sprintf ("%s/ %s/tag/%s" , * baseURL , * repo , tagName )
184
187
185
188
req , err := http .NewRequest ("DELETE" , url , nil )
186
189
if err != nil {
187
- return fmt .Errorf ("error creating DELETE request: %s " , err )
190
+ return fmt .Errorf ("error creating DELETE request: %w " , err )
188
191
}
189
- req .Header .Add ("Authorization" , "Bearer " + accessToken )
192
+ req .Header .Add ("Authorization" , fmt . Sprintf ( "Bearer %s" , accessToken ) )
190
193
191
194
resp , err := client .Do (req )
192
195
if err != nil {
193
- return fmt .Errorf ("error deleting tag: %s " , err )
196
+ return fmt .Errorf ("error deleting tag: %w " , err )
194
197
}
195
198
defer resp .Body .Close ()
196
199
197
200
if resp .StatusCode == http .StatusNoContent {
198
201
return nil
199
202
}
200
203
201
- body , _ := io .ReadAll (resp .Body )
202
- return fmt .Errorf ("Failed to delete tag %s: Status code %d\n Body: %s\n " , tagName , resp .StatusCode , string (body ))
204
+ body , err := io .ReadAll (resp .Body )
205
+ if err != nil {
206
+ return fmt .Errorf ("error reading response body: %w" , err )
207
+ }
208
+ return fmt .Errorf ("failed to delete tag %s: Status code %d Body: %s" , tagName , resp .StatusCode , string (body ))
203
209
}
204
210
205
- // filterTags takes a slice of tags and preserves string regex and returns two maps: one for tags to delete and one for remaining tags.
211
+ // filterTags takes a slice of tags and preserves string regex and returns two maps: one for tags to delete and one for preserved tags.
206
212
func filterTags (tags []Tag , preserveSubstrings []string ) (map [string ]struct {}, map [string ]struct {}, error ) {
207
213
tagsToDelete := make (map [string ]struct {})
208
214
preservedTags := make (map [string ]struct {})
209
215
210
216
// Compile the regexes for each preserve substring
211
- var preserveRegexes []* regexp.Regexp
217
+ preserveRegexes := make ( []* regexp.Regexp , 0 , len ( preserveSubstrings ))
212
218
for _ , substr := range preserveSubstrings {
213
219
regex , err := regexp .Compile (substr )
214
220
if err != nil {
@@ -219,7 +225,7 @@ func filterTags(tags []Tag, preserveSubstrings []string) (map[string]struct{}, m
219
225
220
226
for _ , tag := range tags {
221
227
// Tags that have an expiration set are ignored as they could be historical tags that have already expired
222
- // i.e. when an existing tag is updated, the previous tag of the same name is expired and is returned when listing
228
+ // i.e. when an existing tag is updated, the previous tag of the same name is expired and is still returned when listing
223
229
// the tags
224
230
if tag .Expiration != "" {
225
231
continue
0 commit comments