Skip to content

Commit d66c920

Browse files
committed
Includes quay_overflow.go file and quay_overflow_test.go
Signed-off-by: dlaw4608 <[email protected]>
1 parent ecdb180 commit d66c920

File tree

6 files changed

+439
-68
lines changed

6 files changed

+439
-68
lines changed

.github/workflows/build-images-base.yaml

+50-66
Original file line numberDiff line numberDiff line change
@@ -85,31 +85,26 @@ jobs:
8585
steps:
8686
- name: Check out code
8787
uses: actions/checkout@v3
88-
- name: Install qemu dependency
89-
run: |
90-
sudo apt-get update
91-
sudo apt-get install -y qemu-user-static
92-
- name: Build Image
93-
id: build-image
94-
uses: redhat-actions/buildah-build@v2
95-
with:
96-
image: ${{ env.OPERATOR_NAME }}
97-
tags: ${{ env.IMG_TAGS }}
98-
platforms: linux/amd64,linux/arm64
99-
dockerfiles: |
100-
./Dockerfile
101-
- name: Push Image
102-
if: ${{ !env.ACT }}
103-
id: push-to-quay
104-
uses: redhat-actions/push-to-registry@v2
88+
- name: Set up Docker Buildx
89+
uses: docker/setup-buildx-action@v3
90+
- name: Login to container registry
91+
uses: docker/login-action@v2
10592
with:
106-
image: ${{ steps.build-image.outputs.image }}
107-
tags: ${{ steps.build-image.outputs.tags }}
108-
registry: ${{ env.IMG_REGISTRY_HOST }}/${{ env.IMG_REGISTRY_ORG }}
10993
username: ${{ secrets.IMG_REGISTRY_USERNAME }}
11094
password: ${{ secrets.IMG_REGISTRY_TOKEN }}
95+
registry: ${{ env.IMG_REGISTRY_HOST }}
96+
- name: Build and Push Image
97+
id: build-image
98+
uses: docker/build-push-action@v5
99+
with:
100+
context: .
101+
file: ./Dockerfile
102+
platforms: linux/amd64,linux/arm64
103+
push: true
104+
tags: |
105+
${{ env.IMG_REGISTRY_HOST }}/${{ env.IMG_REGISTRY_ORG }}/${{ env.OPERATOR_NAME }}:${{ env.IMG_TAGS }}
111106
- name: Print Image URL
112-
run: echo "Image pushed to ${{ steps.push-to-quay.outputs.registry-paths }}"
107+
run: echo "Image pushed to ${{ env.IMG_REGISTRY_HOST }}/${{ env.IMG_REGISTRY_ORG }}/${{ env.OPERATOR_NAME }}:${{ env.IMG_TAGS }}"
113108

114109
build-bundle:
115110
needs: build
@@ -123,10 +118,6 @@ jobs:
123118
id: go
124119
- name: Check out code
125120
uses: actions/checkout@v3
126-
- name: Install qemu dependency
127-
run: |
128-
sudo apt-get update
129-
sudo apt-get install -y qemu-user-static
130121
- name: Run make bundle
131122
id: make-bundle
132123
run: |
@@ -138,27 +129,26 @@ jobs:
138129
WASM_SHIM_VERSION=${{ inputs.wasmShimVersion }} \
139130
REPLACES_VERSION=${{ inputs.replacesVersion }} \
140131
CHANNELS=${{ inputs.channels }}
141-
- name: Build Image
142-
id: build-image
143-
uses: redhat-actions/buildah-build@v2
132+
- name: Set up Docker Buildx
133+
uses: docker/setup-buildx-action@v3
134+
- name: Login to container registry
135+
uses: docker/login-action@v2
144136
with:
145-
image: ${{ env.OPERATOR_NAME }}-bundle
146-
tags: ${{ env.IMG_TAGS }}
147-
platforms: linux/amd64,linux/arm64
148-
dockerfiles: |
149-
./bundle.Dockerfile
150-
- name: Push Image
151-
if: ${{ !env.ACT }}
152-
id: push-to-quay
153-
uses: redhat-actions/push-to-registry@v2
154-
with:
155-
image: ${{ steps.build-image.outputs.image }}
156-
tags: ${{ steps.build-image.outputs.tags }}
157-
registry: ${{ env.IMG_REGISTRY_HOST }}/${{ env.IMG_REGISTRY_ORG }}
158137
username: ${{ secrets.IMG_REGISTRY_USERNAME }}
159138
password: ${{ secrets.IMG_REGISTRY_TOKEN }}
160-
- name: Print Image URL
161-
run: echo "Image pushed to ${{ steps.push-to-quay.outputs.registry-paths }}"
139+
registry: ${{ env.IMG_REGISTRY_HOST }}
140+
- name: Build and Push Bundle Image
141+
id: build-bundle-image
142+
uses: docker/build-push-action@v5
143+
with:
144+
context: .
145+
file: ./bundle.Dockerfile
146+
platforms: linux/amd64,linux/arm64
147+
push: true
148+
tags: |
149+
${{ env.IMG_REGISTRY_HOST }}/${{ env.IMG_REGISTRY_ORG }}/${{ env.OPERATOR_NAME }}-bundle:${{ env.IMG_TAGS }}
150+
- name: Print Bundle Image URL
151+
run: echo "Bundle image pushed to ${{ env.IMG_REGISTRY_HOST }}/${{ env.IMG_REGISTRY_ORG }}/${{ env.OPERATOR_NAME }}-bundle:${{ env.IMG_TAGS }}"
162152

163153
build-catalog:
164154
name: Build Catalog
@@ -182,29 +172,23 @@ jobs:
182172
WASM_SHIM_VERSION=${{ inputs.wasmShimVersion }} \
183173
REPLACES_VERSION=${{ inputs.replacesVersion }} \
184174
CHANNELS=${{ inputs.channels }}
185-
- name: Install qemu dependency
186-
run: |
187-
sudo apt-get update
188-
sudo apt-get install -y qemu-user-static
189-
- name: Build Image
190-
id: build-image
191-
uses: redhat-actions/buildah-build@v2
175+
- name: Set up Docker Buildx
176+
uses: docker/setup-buildx-action@v3
177+
- name: Login to container registry
178+
uses: docker/login-action@v2
192179
with:
193-
image: ${{ env.OPERATOR_NAME }}-catalog
194-
tags: ${{ env.IMG_TAGS }}
195-
platforms: linux/amd64,linux/arm64
196-
context: ./catalog
197-
dockerfiles: |
198-
./catalog/kuadrant-operator-catalog.Dockerfile
199-
- name: Push Image
200-
if: ${{ !env.ACT }}
201-
id: push-to-quay
202-
uses: redhat-actions/push-to-registry@v2
203-
with:
204-
image: ${{ steps.build-image.outputs.image }}
205-
tags: ${{ steps.build-image.outputs.tags }}
206-
registry: ${{ env.IMG_REGISTRY_HOST }}/${{ env.IMG_REGISTRY_ORG }}
207180
username: ${{ secrets.IMG_REGISTRY_USERNAME }}
208181
password: ${{ secrets.IMG_REGISTRY_TOKEN }}
209-
- name: Print Image URL
210-
run: echo "Image pushed to ${{ steps.push-to-quay.outputs.registry-paths }}"
182+
registry: ${{ env.IMG_REGISTRY_HOST }}
183+
- name: Build and Push Catalog Image
184+
id: build-catalog-image
185+
uses: docker/build-push-action@v5
186+
with:
187+
context: ./catalog
188+
file: ./catalog/kuadrant-operator-catalog.Dockerfile
189+
platforms: linux/amd64,linux/arm64
190+
push: true
191+
tags: |
192+
${{ env.IMG_REGISTRY_HOST }}/${{ env.IMG_REGISTRY_ORG }}/${{ env.OPERATOR_NAME }}-catalog:${{ env.IMG_TAGS }}
193+
- name: Print Catalog Image URL
194+
run: echo "Catalog image pushed to ${{ env.IMG_REGISTRY_HOST }}/${{ env.IMG_REGISTRY_ORG }}/${{ env.OPERATOR_NAME }}-catalog:${{ env.IMG_TAGS }}"

Dockerfile

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Build the manager binary
2-
FROM golang:1.22 as builder
2+
FROM --platform=$BUILDPLATFORM golang:1.22 as builder
33

44
WORKDIR /workspace
55
# Copy the Go Modules manifests
@@ -15,8 +15,11 @@ COPY api/ api/
1515
COPY controllers/ controllers/
1616
COPY pkg/ pkg/
1717

18+
# Set environment variables for cross-compilation
19+
ARG TARGETARCH
20+
1821
# Build
19-
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o manager main.go
22+
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -a -o manager main.go
2023

2124
# Use distroless as minimal base image to package the manager binary
2225
# Refer to https://github.com/GoogleContainerTools/distroless for more details

go.mod

+4
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ require (
123123
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
124124
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
125125
github.com/pkg/errors v0.9.1 // indirect
126+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
126127
github.com/prometheus/client_model v0.5.0 // indirect
127128
github.com/prometheus/common v0.48.0 // indirect
128129
github.com/prometheus/procfs v0.12.0 // indirect
@@ -134,9 +135,12 @@ require (
134135
github.com/spf13/cast v1.6.0 // indirect
135136
github.com/spf13/cobra v1.8.0 // indirect
136137
github.com/spf13/pflag v1.0.5 // indirect
138+
github.com/stretchr/objx v0.5.2 // indirect
139+
github.com/stretchr/testify v1.9.0 // indirect
137140
github.com/tidwall/gjson v1.14.0 // indirect
138141
github.com/tidwall/match v1.1.1 // indirect
139142
github.com/tidwall/pretty v1.2.0 // indirect
143+
github.com/vitorsalgado/mocha/v3 v3.0.2 // indirect
140144
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
141145
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
142146
github.com/xeipuuv/gojsonschema v1.2.0 // indirect

go.sum

+4
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
400400
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
401401
github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0=
402402
github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0=
403+
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
404+
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
403405
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
404406
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
405407
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@@ -413,6 +415,8 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
413415
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
414416
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
415417
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
418+
github.com/vitorsalgado/mocha/v3 v3.0.2 h1:uTx/+7kZvTWddXzoF34vUQTa3OL9OE+f5fPjD2XCMoY=
419+
github.com/vitorsalgado/mocha/v3 v3.0.2/go.mod h1:ZMpyjuNfWPqLP2v7ztaaLJwOcyl4jmmHVQCEoDsFD0Q=
416420
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
417421
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
418422
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=

quay/quay_overflow.go

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package main
2+
3+
import (
4+
"encoding/base64"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"os"
10+
"strings"
11+
"time"
12+
13+
"k8s.io/client-go/rest"
14+
)
15+
16+
const (
17+
repo = "kuadrant/kuadrant-operator"
18+
baseURL = "https://quay.io/api/v1/repository/"
19+
)
20+
21+
var (
22+
robotPass = os.Getenv("ROBOT_PASS")
23+
robotUser = os.Getenv("ROBOT_USER")
24+
accessToken = os.Getenv("ACCESS_TOKEN")
25+
preserveSubstring = "latest" // Example Tag name that wont be deleted i.e relevant tags
26+
)
27+
28+
// Tag represents a tag in the repository.
29+
type Tag struct {
30+
Name string `json:"name"`
31+
LastModified string `json:"last_modified"`
32+
}
33+
34+
// TagsResponse represents the structure of the API response that contains tags.
35+
type TagsResponse struct {
36+
Tags []Tag `json:"tags"`
37+
}
38+
39+
func main() {
40+
client := &http.Client{}
41+
42+
// Fetch tags from the API
43+
tags, err := fetchTags(client)
44+
if err != nil {
45+
fmt.Println("Error fetching tags:", err)
46+
return
47+
}
48+
49+
// Use filterTags to get tags to delete and remaining tags
50+
tagsToDelete, remainingTags := filterTags(tags, preserveSubstring)
51+
52+
// Delete tags and update remainingTags
53+
for tagName := range tagsToDelete {
54+
if deleteTag(client, accessToken, tagName) {
55+
delete(remainingTags, tagName) // Remove deleted tag from remainingTags
56+
}
57+
}
58+
59+
// Print remaining tags
60+
fmt.Println("Remaining tags:")
61+
for tag := range remainingTags {
62+
fmt.Println(tag)
63+
}
64+
}
65+
66+
// fetchTags retrieves the tags from the repository using the Quay.io API.
67+
func fetchTags(client rest.HTTPClient) ([]Tag, error) {
68+
// TODO - DO you want to seperate out builidng the request to a function to unit test?
69+
// TODO - Is adding the headers even needed to fetch tags for a public repo?
70+
req, err := http.NewRequest("GET", baseURL+repo+"/tag", nil)
71+
if err != nil {
72+
return nil, fmt.Errorf("error creating request: %w", err)
73+
}
74+
75+
// Prioritize Bearer token for authorization
76+
if accessToken != "" {
77+
req.Header.Add("Authorization", "Bearer "+accessToken)
78+
} else {
79+
// Fallback to Basic Authentication if no access token
80+
auth := base64.StdEncoding.EncodeToString([]byte(robotUser + ":" + robotPass))
81+
req.Header.Add("Authorization", "Basic "+auth)
82+
}
83+
84+
// Execute the request
85+
resp, err := client.Do(req)
86+
if err != nil {
87+
return nil, fmt.Errorf("error making request: %w", err)
88+
}
89+
defer resp.Body.Close()
90+
91+
// Handle possible non-200 status codes
92+
if resp.StatusCode != http.StatusOK {
93+
body, _ := io.ReadAll(resp.Body)
94+
return nil, fmt.Errorf("error: received status code %d\nBody: %s", resp.StatusCode, string(body))
95+
}
96+
97+
// Read the response body
98+
body, err := io.ReadAll(resp.Body)
99+
if err != nil {
100+
return nil, fmt.Errorf("error reading response body: %w", err)
101+
}
102+
103+
// Parse the JSON response
104+
var tagsResp TagsResponse
105+
if err := json.Unmarshal(body, &tagsResp); err != nil {
106+
return nil, fmt.Errorf("error unmarshalling response: %w", err)
107+
}
108+
109+
return tagsResp.Tags, nil
110+
}
111+
112+
// filterTags takes a slice of tags and returns two maps: one for tags to delete and one for remaining tags.
113+
func filterTags(tags []Tag, preserveSubstring string) (map[string]struct{}, map[string]struct{}) {
114+
// Calculate the cutoff time
115+
cutOffTime := time.Now().AddDate(0, 0, 0).Add(0 * time.Hour).Add(-1 * time.Minute)
116+
117+
tagsToDelete := make(map[string]struct{})
118+
remainingTags := make(map[string]struct{})
119+
120+
for _, tag := range tags {
121+
// Parse the LastModified timestamp
122+
lastModified, err := time.Parse(time.RFC1123, tag.LastModified)
123+
if err != nil {
124+
fmt.Println("Error parsing time:", err)
125+
continue
126+
}
127+
128+
// Check if tag should be deleted
129+
if lastModified.Before(cutOffTime) && !containsSubstring(tag.Name, preserveSubstring) {
130+
tagsToDelete[tag.Name] = struct{}{}
131+
} else {
132+
remainingTags[tag.Name] = struct{}{}
133+
}
134+
}
135+
136+
return tagsToDelete, remainingTags
137+
}
138+
139+
func containsSubstring(tagName, substring string) bool {
140+
return strings.Contains(tagName, substring)
141+
}
142+
143+
// deleteTag sends a DELETE request to remove the specified tag from the repository
144+
// Returns true if successful, false otherwise
145+
func deleteTag(client rest.HTTPClient, accessToken, tagName string) bool {
146+
req, err := http.NewRequest("DELETE", baseURL+repo+"/tag/"+tagName, nil)
147+
if err != nil {
148+
fmt.Println("Error creating DELETE request:", err)
149+
return false
150+
}
151+
req.Header.Add("Authorization", "Bearer "+accessToken)
152+
153+
resp, err := client.Do(req)
154+
if err != nil {
155+
fmt.Println("Error deleting tag:", err)
156+
return false
157+
}
158+
defer resp.Body.Close()
159+
160+
if resp.StatusCode == http.StatusNoContent {
161+
fmt.Printf("Successfully deleted tag: %s\n", tagName)
162+
return true
163+
} else {
164+
body, _ := io.ReadAll(resp.Body)
165+
fmt.Printf("Failed to delete tag %s: Status code %d\nBody: %s\n", tagName, resp.StatusCode, string(body))
166+
return false
167+
}
168+
}

0 commit comments

Comments
 (0)