Skip to content

Commit

Permalink
Includes quay_overflow.go file and quay_overflow_test.go
Browse files Browse the repository at this point in the history
Signed-off-by: dlaw4608 <[email protected]>
  • Loading branch information
dlaw4608 committed Aug 29, 2024
1 parent ddf59ad commit e6d4980
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 24 deletions.
14 changes: 9 additions & 5 deletions quay/quay_overflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,20 @@ import (
"os"
"strings"
"time"

"k8s.io/client-go/rest"
)

const (
repo = "test_org_123/kuadrant-operator"
repo = "kuadrant/kuadrant-operator"
baseURL = "https://quay.io/api/v1/repository/"
)

var (
robotPass = os.Getenv("ROBOT_PASS")
robotUser = os.Getenv("ROBOT_USER")
accessToken = os.Getenv("ACCESS_TOKEN")
preserveSubstring = "danlaw345" // Example Tag name that wont be deleted i.e relevant tags
preserveSubstring = "latest" // Example Tag name that wont be deleted i.e relevant tags
)

// Tag represents a tag in the repository.
Expand Down Expand Up @@ -62,7 +64,9 @@ func main() {
}

// fetchTags retrieves the tags from the repository using the Quay.io API.
func fetchTags(client *http.Client) ([]Tag, error) {
func fetchTags(client rest.HTTPClient) ([]Tag, error) {
// TODO - DO you want to seperate out builidng the request to a function to unit test?
// TODO - Is adding the headers even needed to fetch tags for a public repo?
req, err := http.NewRequest("GET", baseURL+repo+"/tag", nil)
if err != nil {
return nil, fmt.Errorf("error creating request: %w", err)
Expand Down Expand Up @@ -138,7 +142,7 @@ func containsSubstring(tagName, substring string) bool {

// deleteTag sends a DELETE request to remove the specified tag from the repository
// Returns true if successful, false otherwise
func deleteTag(client *http.Client, accessToken, tagName string) bool {
func deleteTag(client rest.HTTPClient, accessToken, tagName string) bool {
req, err := http.NewRequest("DELETE", baseURL+repo+"/tag/"+tagName, nil)
if err != nil {
fmt.Println("Error creating DELETE request:", err)
Expand All @@ -161,4 +165,4 @@ func deleteTag(client *http.Client, accessToken, tagName string) bool {
fmt.Printf("Failed to delete tag %s: Status code %d\nBody: %s\n", tagName, resp.StatusCode, string(body))
return false
}
}
}
208 changes: 208 additions & 0 deletions quay/quay_overflow_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package main

import (
"bytes"
"errors"
"io"
"net/http"
"strings"
"testing"
"time"

"k8s.io/client-go/rest"
)

type MockHTTPClient struct {
wantErr bool
mutateFn func(res *http.Response)
}

func (m MockHTTPClient) Do(request *http.Request) (*http.Response, error) {
if m.wantErr {
return nil, errors.New("oops")
}

resp := &http.Response{}
if m.mutateFn != nil {
m.mutateFn(resp)
}

return resp, nil
}

var _ rest.HTTPClient = &MockHTTPClient{}

func Test_fetchTags(t *testing.T) {
t.Run("test error making request", func(t *testing.T) {
tags, err := fetchTags(&MockHTTPClient{wantErr: true})

if err == nil {
t.Error("error expected")
}

if err.Error() != "error making request: oops" {
t.Errorf("error expected, got %s", err.Error())
}

if tags != nil {
t.Error("expected nil tags")
}
})

t.Run("test error for non-200 status codes", func(t *testing.T) {
tags, err := fetchTags(&MockHTTPClient{mutateFn: func(res *http.Response) {
res.Status = string(rune(400))
res.Body = io.NopCloser(bytes.NewReader(nil))
}})

if err == nil {
t.Error("error expected")
}

if strings.Contains(err.Error(), "tags, error: received status code 400") {
t.Errorf("error expected, got %s", err.Error())
}

if tags != nil {
t.Error("expected nil tags")
}
})

t.Run("test error parsing json", func(t *testing.T) {
tags, err := fetchTags(&MockHTTPClient{mutateFn: func(res *http.Response) {
res.Status = string(rune(200))
res.Body = io.NopCloser(bytes.NewReader([]byte("{notTags: error}")))
}})

if err == nil {
t.Error("error expected")
}

if strings.Contains(err.Error(), "error unmarshalling response:") {
t.Errorf("error expected, got %s", err.Error())
}

if tags != nil {
t.Error("expected nil tags")
}
})

t.Run("test successful response with tags", func(t *testing.T) {
mockJSONResponse := `{
"tags": [
{"name": "v1.0.0", "last_modified": "Mon, 02 Jan 2006 15:04:05 MST"},
{"name": "v1.1.0", "last_modified": "Tue, 03 Jan 2006 15:04:05 MST"},
{"name": "latest", "last_modified": "Wed, 04 Jan 2006 15:04:05 MST"}
]
}`

tags, err := fetchTags(&MockHTTPClient{mutateFn: func(res *http.Response) {
res.StatusCode = http.StatusOK
res.Body = io.NopCloser(bytes.NewReader([]byte(mockJSONResponse)))
}})

if err != nil {
t.Fatalf("unexpected error: %v", err)
}

// Validate the returned tags
if len(tags) != 3 {
t.Fatalf("expected 3 tags, got %d", len(tags))
}

expectedTags := map[string]string{
"v1.0.0": "Mon, 02 Jan 2006 15:04:05 MST",
"v1.1.0": "Tue, 03 Jan 2006 15:04:05 MST",
"latest": "Wed, 04 Jan 2006 15:04:05 MST",
}

for _, tag := range tags {
if expectedDate, ok := expectedTags[tag.Name]; !ok || expectedDate != tag.LastModified {
t.Errorf("unexpected tag: got %v, expected %v", tag, expectedTags[tag.Name])
}
}
})
}

func Test_deleteTag(t *testing.T) {
t.Run("test successful delete", func(t *testing.T) {
client := &MockHTTPClient{mutateFn: func(res *http.Response) {
res.StatusCode = http.StatusNoContent
res.Body = io.NopCloser(bytes.NewReader(nil))
}}

success := deleteTag(client, "fake_access_token", "v1.0.0")

if !success {
t.Error("expected successful delete, got failure")
}
})

t.Run("test delete with error response", func(t *testing.T) {
client := &MockHTTPClient{mutateFn: func(res *http.Response) {
res.StatusCode = http.StatusInternalServerError
res.Body = io.NopCloser(bytes.NewReader([]byte("internal server error")))
}}

success := deleteTag(client, "fake_access_token", "v1.0.0")

if success {
t.Error("expected failure, got success")
}
})

t.Run("test error making delete request", func(t *testing.T) {
client := &MockHTTPClient{wantErr: true}

success := deleteTag(client, "fake_access_token", "v1.0.0")

if success {
t.Error("expected failure, got success")
}
})
}

func Test_filterTags(t *testing.T) {
t.Run("test filter tags correctly", func(t *testing.T) {
tags := []Tag{
{"nightly-build", time.Now().Add(-24 * time.Hour).Format(time.RFC1123)}, // Old tag, should be deleted
{"v1.1.0", time.Now().Format(time.RFC1123)}, // Recent tag, should be kept
{"latest", time.Now().Add(-24 * time.Hour).Format(time.RFC1123)}, // Old tag, but name contains preserveSubstring
}

preserveSubstring := "latest"

tagsToDelete, remainingTags := filterTags(tags, preserveSubstring)

if len(tagsToDelete) != 1 || len(remainingTags) != 2 {
t.Fatalf("expected 1 tag to delete and 2 remaining, got %d to delete and %d remaining", len(tagsToDelete), len(remainingTags))
}

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

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

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

t.Run("test filter tags with no deletions", func(t *testing.T) {
tags := []Tag{
{"v1.1.0", time.Now().Format(time.RFC1123)}, // Recent tag, should be kept
{"latest", time.Now().Format(time.RFC1123)}, // Recent tag, should be kept
}

preserveSubstring := "latest"

tagsToDelete, remainingTags := filterTags(tags, preserveSubstring)

if len(tagsToDelete) != 0 || len(remainingTags) != 2 {
t.Fatalf("expected 0 tags to delete and 2 remaining, got %d to delete and %d remaining", len(tagsToDelete), len(remainingTags))
}
})
}
19 changes: 0 additions & 19 deletions quay/test/mirror_repo.sh

This file was deleted.

0 comments on commit e6d4980

Please sign in to comment.