Skip to content

Commit b6c621d

Browse files
committed
refactor: remove cut off date logic, obselete if tags would use expiry label
Signed-off-by: KevFan <[email protected]>
1 parent bb05f70 commit b6c621d

File tree

2 files changed

+59
-83
lines changed

2 files changed

+59
-83
lines changed

quay/quay_overflow.go

+18-32
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"net/http"
1010
"os"
1111
"regexp"
12-
"time"
1312

1413
"golang.org/x/exp/maps"
1514
"oras.land/oras-go/pkg/registry/remote"
@@ -21,24 +20,19 @@ const (
2120
)
2221

2322
var (
24-
repo *string
25-
baseURL *string
26-
dryRun *bool
2723
accessToken = os.Getenv("ACCESS_TOKEN")
2824
preserveSubstrings = []string{
2925
"latest",
30-
// Preserve release branch images
31-
"release-v*",
32-
// Semver regex - vX.Y.Z(-rc1)
33-
"^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-]+)*))?$",
26+
// Preserve semver release branch or semver tag regex - release-vX.Y.Z(-rc1) or vX.Y.Z(-rc1)
27+
// Based on https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
28+
"^(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-]+)*))?$",
3429
}
3530
)
3631

3732
// Tag represents a tag in the repository.
3833
type Tag struct {
39-
Name string `json:"name"`
40-
LastModified string `json:"last_modified"`
41-
Expiration string `json:"expiration"`
34+
Name string `json:"name"`
35+
Expiration string `json:"expiration"`
4236
}
4337

4438
// TagsResponse represents the structure of the API response that contains tags.
@@ -49,9 +43,9 @@ type TagsResponse struct {
4943
}
5044

5145
func main() {
52-
repo = flag.String("repo", "kuadrant/kuadrant-operator", "Repository name")
53-
baseURL = flag.String("base-url", "https://quay.io/api/v1/repository/", "Base API URL")
54-
dryRun = flag.Bool("dry-run", true, "Dry run")
46+
repo := flag.String("repo", "kuadrant/kuadrant-operator", "Repository name")
47+
baseURL := flag.String("base-url", "https://quay.io/api/v1/repository/", "Base API URL")
48+
dryRun := flag.Bool("dry-run", true, "Dry run")
5549
flag.Parse()
5650

5751
client := &http.Client{}
@@ -63,7 +57,7 @@ func main() {
6357
}
6458

6559
// Fetch tags from the API
66-
tags, err := fetchTags(client)
60+
tags, err := fetchTags(client, baseURL, repo)
6761
if err != nil {
6862
logger.Fatalln("Error fetching tags:", err)
6963
}
@@ -81,7 +75,7 @@ func main() {
8175
if dryRun != nil && *dryRun {
8276
logger.Printf("DRY RUN - Successfully deleted tag: %s\n", tagName)
8377
} else {
84-
if err := deleteTag(client, accessToken, tagName); err != nil {
78+
if err := deleteTag(client, baseURL, repo, accessToken, tagName); err != nil {
8579
logger.Println(err)
8680
continue
8781
}
@@ -99,13 +93,13 @@ func main() {
9993

10094
// fetchTags retrieves the tags from the repository using the Quay.io API.
10195
// https://docs.quay.io/api/swagger/#!/tag/listRepoTags
102-
func fetchTags(client remote.Client) ([]Tag, error) {
96+
func fetchTags(client remote.Client, baseURL, repo *string) ([]Tag, error) {
10397
allTags := make([]Tag, 0)
10498

10599
i := 1
106100

107101
for {
108-
req, err := http.NewRequest("GET", fmt.Sprintf("%s%s/tag/?page=%d&limit=%d", *baseURL, *repo, i, pageLimit), nil)
102+
req, err := http.NewRequest("GET", fmt.Sprintf("%v%v/tag/?page=%d&limit=%d", baseURL, repo, i, pageLimit), nil)
109103
if err != nil {
110104
return nil, fmt.Errorf("error creating request: %w", err)
111105
}
@@ -155,8 +149,8 @@ func fetchTags(client remote.Client) ([]Tag, error) {
155149
// deleteTag sends a DELETE request to remove the specified tag from the repository
156150
// Returns nil if successful, error otherwise
157151
// https://docs.quay.io/api/swagger/#!/tag/deleteFullTag
158-
func deleteTag(client remote.Client, accessToken, tagName string) error {
159-
req, err := http.NewRequest("DELETE", fmt.Sprintf("%s%s/tag/%s", *baseURL, *repo, tagName), nil)
152+
func deleteTag(client remote.Client, baseURL, repo *string, accessToken, tagName string) error {
153+
req, err := http.NewRequest("DELETE", fmt.Sprintf("%v%v/tag/%s", baseURL, repo, tagName), nil)
160154
if err != nil {
161155
return fmt.Errorf("error creating DELETE request: %s", err)
162156
}
@@ -178,11 +172,8 @@ func deleteTag(client remote.Client, accessToken, tagName string) error {
178172

179173
// filterTags takes a slice of tags and preserves string regex and returns two maps: one for tags to delete and one for remaining tags.
180174
func filterTags(tags []Tag, preserveSubstrings []string) (map[string]struct{}, map[string]struct{}, error) {
181-
// Calculate the cutoff time
182-
cutOffTime := time.Now().AddDate(0, 0, 0).Add(0 * time.Hour).Add(-1 * time.Minute)
183-
184175
tagsToDelete := make(map[string]struct{})
185-
perservedTags := make(map[string]struct{})
176+
preservedTags := make(map[string]struct{})
186177

187178
// Compile the regexes for each preserve substring
188179
var preserveRegexes []*regexp.Regexp
@@ -211,17 +202,12 @@ func filterTags(tags []Tag, preserveSubstrings []string) (map[string]struct{}, m
211202
}
212203
}
213204

214-
lastModified, err := time.Parse(time.RFC1123, tag.LastModified)
215-
if err != nil {
216-
return nil, nil, err
217-
}
218-
219-
if lastModified.Before(cutOffTime) && !preserve {
205+
if !preserve {
220206
tagsToDelete[tag.Name] = struct{}{}
221207
} else {
222-
perservedTags[tag.Name] = struct{}{}
208+
preservedTags[tag.Name] = struct{}{}
223209
}
224210
}
225211

226-
return tagsToDelete, perservedTags, nil
212+
return tagsToDelete, preservedTags, nil
227213
}

quay/quay_overflow_test.go

+41-51
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import (
55
"errors"
66
"io"
77
"net/http"
8+
"slices"
89
"strings"
910
"testing"
1011
"time"
1112

12-
"k8s.io/client-go/rest"
13+
"oras.land/oras-go/pkg/registry/remote"
1314
)
1415

16+
var _ remote.Client = &MockHTTPClient{}
17+
1518
type MockHTTPClient struct {
1619
wantErr bool
1720
mutateFn func(res *http.Response)
@@ -30,11 +33,14 @@ func (m MockHTTPClient) Do(_ *http.Request) (*http.Response, error) {
3033
return resp, nil
3134
}
3235

33-
var _ rest.HTTPClient = &MockHTTPClient{}
36+
var (
37+
testBaseUrl = "https://quay.io/api/v1/"
38+
testRepo = "testOrg/kuadrant-operator"
39+
)
3440

3541
func Test_fetchTags(t *testing.T) {
3642
t.Run("test error making request", func(t *testing.T) {
37-
tags, err := fetchTags(&MockHTTPClient{wantErr: true})
43+
tags, err := fetchTags(&MockHTTPClient{wantErr: true}, &testBaseUrl, &testRepo)
3844

3945
if err == nil {
4046
t.Error("error expected")
@@ -53,7 +59,7 @@ func Test_fetchTags(t *testing.T) {
5359
tags, err := fetchTags(&MockHTTPClient{mutateFn: func(res *http.Response) {
5460
res.Status = string(rune(400))
5561
res.Body = io.NopCloser(bytes.NewReader(nil))
56-
}})
62+
}}, &testBaseUrl, &testRepo)
5763

5864
if err == nil {
5965
t.Error("error expected")
@@ -72,7 +78,7 @@ func Test_fetchTags(t *testing.T) {
7278
tags, err := fetchTags(&MockHTTPClient{mutateFn: func(res *http.Response) {
7379
res.Status = string(rune(200))
7480
res.Body = io.NopCloser(bytes.NewReader([]byte("{notTags: error}")))
75-
}})
81+
}}, &testBaseUrl, &testRepo)
7682

7783
if err == nil {
7884
t.Error("error expected")
@@ -90,16 +96,16 @@ func Test_fetchTags(t *testing.T) {
9096
t.Run("test successful response with tags", func(t *testing.T) {
9197
mockJSONResponse := `{
9298
"tags": [
93-
{"name": "v1.0.0", "last_modified": "Mon, 02 Jan 2006 15:04:05 MST"},
94-
{"name": "v1.1.0", "last_modified": "Tue, 03 Jan 2006 15:04:05 MST"},
95-
{"name": "latest", "last_modified": "Wed, 04 Jan 2006 15:04:05 MST"}
99+
{"name": "v1.0.0"},
100+
{"name": "v1.1.0"},
101+
{"name": "latest"}
96102
]
97103
}`
98104

99105
tags, err := fetchTags(&MockHTTPClient{mutateFn: func(res *http.Response) {
100106
res.StatusCode = http.StatusOK
101107
res.Body = io.NopCloser(bytes.NewReader([]byte(mockJSONResponse)))
102-
}})
108+
}}, &testBaseUrl, &testRepo)
103109

104110
if err != nil {
105111
t.Fatalf("unexpected error: %v", err)
@@ -110,15 +116,15 @@ func Test_fetchTags(t *testing.T) {
110116
t.Fatalf("expected 3 tags, got %d", len(tags))
111117
}
112118

113-
expectedTags := map[string]string{
114-
"v1.0.0": "Mon, 02 Jan 2006 15:04:05 MST",
115-
"v1.1.0": "Tue, 03 Jan 2006 15:04:05 MST",
116-
"latest": "Wed, 04 Jan 2006 15:04:05 MST",
119+
expectedTags := []string{
120+
"v1.0.0",
121+
"v1.1.0",
122+
"latest",
117123
}
118124

119125
for _, tag := range tags {
120-
if expectedDate, ok := expectedTags[tag.Name]; !ok || expectedDate != tag.LastModified {
121-
t.Errorf("unexpected tag: got %v, expected %v", tag, expectedTags[tag.Name])
126+
if !slices.Contains(expectedTags, tag.Name) {
127+
t.Errorf("unexpected tag: %v, does not exist in expected tags %v", tag, expectedTags)
122128
}
123129
}
124130
})
@@ -131,7 +137,7 @@ func Test_deleteTag(t *testing.T) {
131137
res.Body = io.NopCloser(bytes.NewReader(nil))
132138
}}
133139

134-
err := deleteTag(client, "fake_access_token", "v1.0.0")
140+
err := deleteTag(client, &testBaseUrl, &testRepo, "fake_access_token", "v1.0.0")
135141

136142
if err != nil {
137143
t.Error("expected successful delete, got error")
@@ -144,7 +150,7 @@ func Test_deleteTag(t *testing.T) {
144150
res.Body = io.NopCloser(bytes.NewReader([]byte("internal server error")))
145151
}}
146152

147-
err := deleteTag(client, "fake_access_token", "v1.0.0")
153+
err := deleteTag(client, &testBaseUrl, &testRepo, "fake_access_token", "v1.0.0")
148154

149155
if err == nil {
150156
t.Error("expected failure, got success")
@@ -154,7 +160,7 @@ func Test_deleteTag(t *testing.T) {
154160
t.Run("test error making delete request", func(t *testing.T) {
155161
client := &MockHTTPClient{wantErr: true}
156162

157-
err := deleteTag(client, "fake_access_token", "v1.0.0")
163+
err := deleteTag(client, &testBaseUrl, &testRepo, "fake_access_token", "v1.0.0")
158164

159165
if err == nil {
160166
t.Error("expected failure, got success")
@@ -165,13 +171,13 @@ func Test_deleteTag(t *testing.T) {
165171
func Test_filterTags(t *testing.T) {
166172
t.Run("test filter tags correctly", func(t *testing.T) {
167173
tags := []Tag{
168-
{Name: "nightly-build", LastModified: time.Now().Add(-24 * time.Hour).Format(time.RFC1123)}, // Old tag, should be deleted
169-
{Name: "v1.1.0", LastModified: time.Now().Format(time.RFC1123)}, // Recent tag, should be kept
170-
{Name: "latest", LastModified: time.Now().Add(-24 * time.Hour).Format(time.RFC1123)}, // Old tag, but name contains preserveSubstring latest
171-
{Name: "release-v1.2.3", LastModified: time.Now().Add(-24 * time.Hour).Format(time.RFC1123)}, // Old tag, but name contains preserveSubstring release-v*
172-
{Name: "v1.0.0", LastModified: time.Now().Add(-24 * time.Hour).Format(time.RFC1123)}, // Old tag, but name contains preserveSubstring semver release
173-
{Name: "v1.2.0-rc1", LastModified: time.Now().Add(-24 * time.Hour).Format(time.RFC1123)}, // Old tag, but name contains preserveSubstring semver release-candidate
174-
{Name: "expiry_set", LastModified: time.Now().Add(-24 * time.Hour).Format(time.RFC1123), Expiration: time.Now().Format(time.RFC1123)}, // Old tag, but already has an expiry set
174+
{Name: "nightly-build"}, // Not a preserved tag, should be deleted
175+
{Name: "latest"}, // Preserved tag, name is latest
176+
{Name: "release-v1.0.0"}, // Preserved tag, name contains preserveSubstring branch release semver, release-v*
177+
{Name: "v1.0.0"}, // Preserved tag, but name contains preserveSubstring tag semver release
178+
{Name: "v1.1.0-rc1"}, // Preserved tag, but name contains preserveSubstring tag semver release-candidate
179+
{Name: "expiry_set", Expiration: time.Now().Format(time.RFC1123)}, // Skipped tag, already has an expiry set
180+
{Name: "release-not-semver"}, // Not a preserved tag, should be deleted
175181
}
176182

177183
tagsToDelete, remainingTags, err := filterTags(tags, preserveSubstrings)
@@ -180,43 +186,39 @@ func Test_filterTags(t *testing.T) {
180186
t.Errorf("unexpected error: %v", err)
181187
}
182188

183-
if len(tagsToDelete) != 1 || len(remainingTags) != 6 {
184-
t.Fatalf("expected 1 tag to delete and 6 remaining, got %d to delete and %d remaining", len(tagsToDelete), len(remainingTags))
189+
if len(tagsToDelete) != 2 || len(remainingTags) != 4 {
190+
t.Fatalf("expected 2 tag to delete and 4 remaining, got %d to delete and %d remaining", len(tagsToDelete), len(remainingTags))
185191
}
186192

187193
if _, ok := tagsToDelete["nightly-build"]; !ok {
188194
t.Error("expected nightly-build to be deleted")
189195
}
190196

191-
if _, ok := remainingTags["v1.1.0"]; !ok {
192-
t.Error("expected v1.1.0 to be kept")
197+
if _, ok := tagsToDelete["release-not-semver"]; !ok {
198+
t.Error("expected nightly-build to be deleted")
193199
}
194200

195201
if _, ok := remainingTags["latest"]; !ok {
196202
t.Error("expected latest to be kept")
197203
}
198204

199-
if _, ok := remainingTags["release-v1.2.3"]; !ok {
200-
t.Error("expected release-v1.2.3 to be kept")
205+
if _, ok := remainingTags["release-v1.0.0"]; !ok {
206+
t.Error("expected release-v1.0.0 to be kept")
201207
}
202208

203209
if _, ok := remainingTags["v1.0.0"]; !ok {
204210
t.Error("expected v1.0.0 to be kept")
205211
}
206212

207-
if _, ok := remainingTags["v1.2.0-rc1"]; !ok {
208-
t.Error("expected v1.2.0-rc1 to be kept")
209-
}
210-
211-
if _, ok := remainingTags["expiry_set"]; !ok {
212-
t.Error("expected expiry_set to be kept")
213+
if _, ok := remainingTags["v1.1.0-rc1"]; !ok {
214+
t.Error("expected v1.1.0-rc1 to be kept")
213215
}
214216
})
215217

216218
t.Run("test filter tags with no deletions", func(t *testing.T) {
217219
tags := []Tag{
218-
{Name: "v1.1.0", LastModified: time.Now().Format(time.RFC1123)}, // Preserved tag, should be kept
219-
{Name: "recent", LastModified: time.Now().Format(time.RFC1123)}, // Recent tag, should be kept
220+
{Name: "v1.1.0"}, // Preserved tag, should be kept
221+
{Name: "latest"}, // Preserved tag, should be kept
220222
}
221223

222224
tagsToDelete, remainingTags, err := filterTags(tags, preserveSubstrings)
@@ -229,16 +231,4 @@ func Test_filterTags(t *testing.T) {
229231
t.Fatalf("expected 0 tags to delete and 2 remaining, got %d to delete and %d remaining", len(tagsToDelete), len(remainingTags))
230232
}
231233
})
232-
233-
t.Run("test error unexpected time format", func(t *testing.T) {
234-
tags := []Tag{
235-
{Name: "v1.1.0", LastModified: time.Now().Format(time.ANSIC)},
236-
}
237-
238-
_, _, err := filterTags(tags, preserveSubstrings)
239-
240-
if err == nil {
241-
t.Fatal("expected error, got success")
242-
}
243-
})
244234
}

0 commit comments

Comments
 (0)