From 636d87f1c66181d4f1ff146595ddc157a033f723 Mon Sep 17 00:00:00 2001 From: Dan Luhring Date: Wed, 24 Jul 2024 13:36:14 -0400 Subject: [PATCH] begin a new APK client (#1218) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The idea here is to orient remote APK interactions around a **client**, in order to give consumers the ability to handle dependency injection properly (such as by injecting an HTTP client). And the ultimate goal is to re-use this client whenever we need to access remote indexes and packages. It'd be great if we can refine this API enough where we can reuse this in all the places we need to interact with APK repositories, to replace the many disparate implementations of this logic we have in our tools today. I haven't gone as far as to refactor other code in apko to use this client, but I think that's a reasonable next step. So far this implementation saves me from writing it again in an internal tool. Feedback welcome. 😃 cc: @imjasonh --------- Signed-off-by: Dan Luhring --- pkg/apk/client/client.go | 66 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 pkg/apk/client/client.go diff --git a/pkg/apk/client/client.go b/pkg/apk/client/client.go new file mode 100644 index 00000000..dfb73ffd --- /dev/null +++ b/pkg/apk/client/client.go @@ -0,0 +1,66 @@ +package client + +import ( + "context" + "fmt" + "net/http" + "net/url" + + "chainguard.dev/apko/pkg/apk/apk" + "chainguard.dev/apko/pkg/apk/auth" +) + +const ( + WolfiAPKRepo = "https://packages.wolfi.dev/os" + ChainguardEnterpriseAPKRepo = "https://packages.cgr.dev/os" + ChainguardExtrasAPKRepo = "https://packages.cgr.dev/extras" +) + +const ( + Aarch64Arch = "aarch64" + X86_64Arch = "x86_64" +) + +// Client is a client for interacting with an APK package repository. +type Client struct { + httpClient *http.Client +} + +// New creates a new Client, suitable for accessing remote APK indexes and +// packages. +func New(httpClient *http.Client) *Client { + if httpClient == nil { + httpClient = http.DefaultClient + } + return &Client{httpClient: httpClient} +} + +// GetRemoteIndex retrieves the index of APK packages from the specified remote +// repository. +func (c Client) GetRemoteIndex(ctx context.Context, apkRepo, arch string) (*apk.APKIndex, error) { + indexURL := apk.IndexURL(apkRepo, arch) + + u, err := url.Parse(indexURL) + if err != nil { + return nil, fmt.Errorf("parsing %q: %w", indexURL, err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) + if err != nil { + return nil, fmt.Errorf("GET %q: %w", u.Redacted(), err) + } + if err := auth.DefaultAuthenticators.AddAuth(ctx, req); err != nil { + return nil, fmt.Errorf("error adding auth: %w", err) + } + + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("GET %q: %w", u.Redacted(), err) + } + defer resp.Body.Close() + if resp.StatusCode >= 400 { + return nil, fmt.Errorf("GET %q: status %d: %s", u.Redacted(), resp.StatusCode, resp.Status) + } + + return apk.IndexFromArchive(resp.Body) +}