Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor and Simplify Codebase for Harbor Satellite #30

Merged
merged 8 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions config.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Wether to us the built-in Zot registry or not
bring_own_registry = true
bring_own_registry = false

# URL of own registry
own_registry_adr = "127.0.0.1:5000"

# URL of remote registry OR local file path
# url_or_file = "https://demo.goharbor.io/v2/myproject/album-server"
url_or_file = "http://localhost:5001/v2/library/busybox"
url_or_file = "https://demo.goharbor.io/v2/myproject/album-server"
# url_or_file = "http://localhost:5001/v2/library/busybox"

# For testing purposes :
# https://demo.goharbor.io/v2/myproject/album-server
Expand Down
3 changes: 2 additions & 1 deletion internal/store/file-fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type ImageData struct {
Repositories []Repository `json:"repositories"`
}

func (f *FileImageList) Type() string {
func (f *FileImageList) SourceType() string {
return "File"
}

Expand Down Expand Up @@ -73,5 +73,6 @@ func (client *FileImageList) List(ctx context.Context) ([]Image, error) {
}

func (client *FileImageList) GetDigest(ctx context.Context, tag string) (string, error) {
// TODO: Implement GetDigest for FileImageList
return "Not implemented yet", nil
}
115 changes: 74 additions & 41 deletions internal/store/http-fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,95 +14,128 @@ import (
"github.com/google/go-containerregistry/pkg/crane"
)

type RemoteImageList struct {
// RemoteImageSource represents a source of images from a remote URL.
type RemoteImageSource struct {
BaseURL string
}

// TagListResponse represents the JSON structure for the tags list response.
type TagListResponse struct {
Name string `json:"name"`
Tags []string `json:"tags"`
}

func RemoteImageListFetcher(url string) *RemoteImageList {
return &RemoteImageList{
BaseURL: url,
}
// NewRemoteImageSource creates a new RemoteImageSource instance.
func NewRemoteImageSource(url string) *RemoteImageSource {
return &RemoteImageSource{BaseURL: url}
}

func (r *RemoteImageList) Type() string {
// SourceType returns the type of the image source as a string.
func (r *RemoteImageSource) SourceType() string {
return "Remote"
}

func (client *RemoteImageList) List(ctx context.Context) ([]Image, error) {
// Construct the URL for fetching tags
url := client.BaseURL + "/tags/list"
// FetchImages retrieves a list of images from the remote repository.
func (r *RemoteImageSource) List(ctx context.Context) ([]Image, error) {
url := r.BaseURL + "/tags/list"
authHeader, err := createAuthHeader()
if err != nil {
return nil, fmt.Errorf("error creating auth header: %w", err)
}

body, err := fetchResponseBody(url, authHeader)
if err != nil {
return nil, fmt.Errorf("error fetching tags list from %s: %w", url, err)
}

images, err := parseTagsResponse(body)
if err != nil {
return nil, fmt.Errorf("error parsing tags response from %s: %w", url, err)
}

fmt.Println("Fetched", len(images), "images:", images)
return images, nil
}

// FetchDigest fetches the digest for a specific image tag.
func (r *RemoteImageSource) GetDigest(ctx context.Context, tag string) (string, error) {
imageRef := fmt.Sprintf("%s:%s", r.BaseURL, tag)
imageRef = cleanImageReference(imageRef)

// Encode credentials for Basic Authentication
digest, err := fetchImageDigest(imageRef)
if err != nil {
return "", fmt.Errorf("error fetching digest for %s: %w", imageRef, err)
}

return digest, nil
}
bupd marked this conversation as resolved.
Show resolved Hide resolved

// createAuthHeader generates the authorization header for HTTP requests.
func createAuthHeader() (string, error) {
username := os.Getenv("HARBOR_USERNAME")
password := os.Getenv("HARBOR_PASSWORD")
if username == "" || password == "" {
return "", fmt.Errorf("environment variables HARBOR_USERNAME or HARBOR_PASSWORD not set")
}
auth := base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
return "Basic " + auth, nil
}

// Create a new HTTP request
// fetchResponseBody makes an HTTP GET request and returns the response body.
func fetchResponseBody(url, authHeader string) ([]byte, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
return nil, fmt.Errorf("failed to create request for %s: %w", url, err)
}
req.Header.Set("Authorization", authHeader)

// Set the Authorization header
req.Header.Set("Authorization", "Basic "+auth)

// Send the request
httpClient := &http.Client{}
resp, err := httpClient.Do(req)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to fetch tags: %w", err)
return nil, fmt.Errorf("failed to fetch response from %s: %w", url, err)
}
defer resp.Body.Close()

// Read the response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
return nil, fmt.Errorf("failed to read response body from %s: %w", url, err)
}

// Unmarshal the JSON response
var tagListResponse TagListResponse
if err := json.Unmarshal(body, &tagListResponse); err != nil {
return body, nil
}

// parseTagsResponse unmarshals the tags list response and constructs image references.
func parseTagsResponse(body []byte) ([]Image, error) {
var tagList TagListResponse
if err := json.Unmarshal(body, &tagList); err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON response: %w", err)
}

// Prepare a slice to store the images
var images []Image

// Iterate over the tags and construct the image references
for _, tag := range tagListResponse.Tags {
images = append(images, Image{
Name: fmt.Sprintf("%s:%s", tagListResponse.Name, tag),
})
for _, tag := range tagList.Tags {
images = append(images, Image{Name: fmt.Sprintf("%s:%s", tagList.Name, tag)})
}
fmt.Println("Fetched", len(images), "images :", images)

return images, nil
bupd marked this conversation as resolved.
Show resolved Hide resolved
}

func (client *RemoteImageList) GetDigest(ctx context.Context, tag string) (string, error) {
// Construct the image reference
imageRef := fmt.Sprintf("%s:%s", client.BaseURL, tag)
// Remove extra characters from the URL
// cleanImageReference cleans up the image reference string.
func cleanImageReference(imageRef string) string {
imageRef = imageRef[strings.Index(imageRef, "//")+2:]
imageRef = strings.ReplaceAll(imageRef, "/v2", "")
return strings.ReplaceAll(imageRef, "/v2", "")
}

// Encode credentials for Basic Authentication
// fetchImageDigest retrieves the digest for an image reference.
func fetchImageDigest(imageRef string) (string, error) {
username := os.Getenv("HARBOR_USERNAME")
password := os.Getenv("HARBOR_PASSWORD")

// Use crane.Digest to get the digest of the image
digest, err := crane.Digest(imageRef, crane.WithAuth(&authn.Basic{
Username: username,
Password: password,
}), crane.Insecure)
if err != nil {
fmt.Printf("failed to fetch digest for %s: %v\n", imageRef, err)
return "", nil
return "", fmt.Errorf("failed to fetch digest for %s: %w", imageRef, err)
}

return digest, nil
Expand Down
Loading