Repository: chainguard-dev/apko (go)

Issue:
failed to read apk key: open /tmp/apko-3984880358/work/local-melange.rsa.pub: no such file or directory
When building locally apko has started to fail this morning.

```
failed to read apk key: open /tmp/apko-3984880358/work/local-melange.rsa.pub: no such file or directory
```

```
--keyring-append /work/local-melange.rsa.pub --repository-append /work/packages
```

full command...
```
docker run --rm -v ${PWD}:/work -w /work cgr.dev/chainguard/apko build latest.apko.yaml foo foo.tar --debug --build-arch amd64 --keyring-append /work/local-melange.rsa.pub --repository-append /work/packages
```

I was able to go back to a previous apko version and things worked again.
```
ghcr.io/chainguard-dev/apko:cf9e6f264bcf38fd8ebbd95fb6a954cce5d16b57
```


Pull request:
read keyfiles from local; read local repositories
Fixes #464 

Issue with two problems was a regression caused by #426 

It was treating keyfiles to read when passed via `--keyring-append` as relative to workdir, which made no sense; when someone types `build --keyring-append /path/to/file`, they mean, "relative to where I am running apko".

This fixes it.

It also was doing the same for `--repository-append`; fixes that too.

Patch:
diff --git a/pkg/apk/impl/implementation.go b/pkg/apk/impl/implementation.go
index 72de336..1998681 100644
--- a/pkg/apk/impl/implementation.go
+++ b/pkg/apk/impl/implementation.go
@@ -338,7 +338,7 @@ func (a *APKImplementation) InitKeyring(keyFiles, extraKeyFiles []string) (err e
 			var data []byte
 			switch asURL.Scheme {
 			case "file":
-				data, err = a.fs.ReadFile(element)
+				data, err = os.ReadFile(element)
 				if err != nil {
 					return fmt.Errorf("failed to read apk key: %w", err)
 				}
@@ -529,20 +529,52 @@ func (a *APKImplementation) installPackage(pkg *repository.RepositoryPackage, ca
 	a.logger.Infof("installing %s (%s)", pkg.Name, pkg.Version)
 
 	u := pkg.Url()
-	client := a.client
-	if client == nil {
-		client = &http.Client{}
-	}
-	res, err := client.Get(u)
+
+	// Normalize the repo as a URI, so that local paths
+	// are translated into file:// URLs, allowing them to be parsed
+	// into a url.URL{}.
+	var (
+		r     io.Reader
+		asURI uri.URI
+	)
+	if strings.HasPrefix(u, "https://") {
+		asURI, _ = uri.Parse(u)
+	} else {
+		asURI = uri.New(u)
+	}
+	asURL, err := url.Parse(string(asURI))
 	if err != nil {
-		return fmt.Errorf("unable to get package apk at %s: %w", u, err)
+		return fmt.Errorf("failed to parse package as URI: %w", err)
 	}
-	defer res.Body.Close()
-	if res.StatusCode != http.StatusOK {
-		return fmt.Errorf("unable to get package apk at %s: %v", u, res.Status)
+
+	switch asURL.Scheme {
+	case "file":
+		f, err := os.Open(u)
+		if err != nil {
+			return fmt.Errorf("failed to read repository package apk %s: %w", u, err)
+		}
+		defer f.Close()
+		r = f
+	case "https":
+		client := a.client
+		if client == nil {
+			client = &http.Client{}
+		}
+		res, err := client.Get(u)
+		if err != nil {
+			return fmt.Errorf("unable to get package apk at %s: %w", u, err)
+		}
+		defer res.Body.Close()
+		if res.StatusCode != http.StatusOK {
+			return fmt.Errorf("unable to get package apk at %s: %v", u, res.Status)
+		}
+		r = res.Body
+	default:
+		return fmt.Errorf("repository scheme %s not supported", asURL.Scheme)
 	}
+
 	// install the apk file
-	expanded, err := expandApk(res.Body)
+	expanded, err := expandApk(r)
 	if err != nil {
 		return fmt.Errorf("unable to expand apk for package %s: %w", pkg.Name, err)
 	}
diff --git a/pkg/apk/impl/repo.go b/pkg/apk/impl/repo.go
index 384e367..bf34398 100644
--- a/pkg/apk/impl/repo.go
+++ b/pkg/apk/impl/repo.go
@@ -22,11 +22,14 @@ import (
 	"fmt"
 	"io"
 	"net/http"
+	"net/url"
+	"os"
 	"path/filepath"
 	"regexp"
 	"strings"
 
 	"gitlab.alpinelinux.org/alpine/go/repository"
+	"go.lsp.dev/uri"
 )
 
 // SetRepositories sets the contents of /etc/apk/repositories file.
@@ -85,16 +88,48 @@ func (a *APKImplementation) getRepositoryIndexes(ignoreSignatures bool) ([]*repo
 	for _, repoURI := range repos {
 		repoBase := fmt.Sprintf("%s/%s", repoURI, arch)
 		u := fmt.Sprintf("%s/%s", repoBase, indexFilename)
-		res, err := http.Get(u) // nolint:gosec // we know what we are doing here
+
+		// Normalize the repo as a URI, so that local paths
+		// are translated into file:// URLs, allowing them to be parsed
+		// into a url.URL{}.
+		var (
+			b     []byte
+			asURI uri.URI
+		)
+		if strings.HasPrefix(u, "https://") {
+			asURI, _ = uri.Parse(u)
+		} else {
+			asURI = uri.New(u)
+		}
+		asURL, err := url.Parse(string(asURI))
 		if err != nil {
-			return nil, fmt.Errorf("unable to get repository index at %s: %w", u, err)
+			return nil, fmt.Errorf("failed to parse repo as URI: %w", err)
 		}
-		defer res.Body.Close()
-		buf := bytes.NewBuffer(nil)
-		if _, err := io.Copy(buf, res.Body); err != nil {
-			return nil, fmt.Errorf("unable to read repository index at %s: %w", u, err)
+
+		switch asURL.Scheme {
+		case "file":
+			b, err = os.ReadFile(u)
+			if err != nil {
+				return nil, fmt.Errorf("failed to read repository %s: %w", u, err)
+			}
+		case "https":
+			client := a.client
+			if client == nil {
+				client = &http.Client{}
+			}
+			res, err := client.Get(asURL.String()) // nolint:gosec // we know what we are doing here
+			if err != nil {
+				return nil, fmt.Errorf("unable to get repository index at %s: %w", u, err)
+			}
+			defer res.Body.Close()
+			buf := bytes.NewBuffer(nil)
+			if _, err := io.Copy(buf, res.Body); err != nil {
+				return nil, fmt.Errorf("unable to read repository index at %s: %w", u, err)
+			}
+			b = buf.Bytes()
+		default:
+			return nil, fmt.Errorf("repository scheme %s not supported", asURL.Scheme)
 		}
-		b := buf.Bytes()
 
 		// validate the signature
 		if !ignoreSignatures {


Test patch:
diff --git a/pkg/apk/impl/implementation_test.go b/pkg/apk/impl/implementation_test.go
index 8d97405..f19c5a2 100644
--- a/pkg/apk/impl/implementation_test.go
+++ b/pkg/apk/impl/implementation_test.go
@@ -124,12 +124,13 @@ func TestInitKeyring(t *testing.T) {
 	a, err := NewAPKImplementation(WithFS(src), WithIgnoreMknodErrors(ignoreMknodErrors))
 	require.NoError(t, err)
 
-	dir := "/foo/bar/keys"
-	err = src.MkdirAll(dir, 0o755)
+	dir, err := os.MkdirTemp("", "apko")
 	require.NoError(t, err)
 
 	keyPath := filepath.Join(dir, "alpine-devel@lists.alpinelinux.org-5e69ca50.rsa.pub")
-	src.WriteFile(keyPath, []byte(testDemoKey), 0o644)
+	err = os.WriteFile(keyPath, []byte(testDemoKey), 0o644)
+	require.NoError(t, err)
+
 	// Add a local file and a remote key
 	keyfiles := []string{
 		keyPath, "https://alpinelinux.org/keys/alpine-devel%40lists.alpinelinux.org-4a6a0840.rsa.pub",
diff --git a/pkg/apk/impl/repo_test.go b/pkg/apk/impl/repo_test.go
index 36d4619..6b35694 100644
--- a/pkg/apk/impl/repo_test.go
+++ b/pkg/apk/impl/repo_test.go
@@ -49,6 +49,22 @@ HwsGXBTVcfXg44RLyL8Lk/2dQxDHNHzAUslJXzPxaHBLmt++2COa2EI1iWlvtznk
 Ok9WP8SOAIj+xdqoiHcC4j72BOVVgiITIJNHrbppZCq6qPR+fgXmXa+sDcGh30m6
 9Wpbr28kLMSHiENCWTdsFij+NQTd5S47H7XTROHnalYDuF1RpS+DpQidT5tUimaT
 JZDr++FjKrnnijbyNF8b98UCAwEAAQ==
+-----END PUBLIC KEY-----`), 0644)
+	require.NoError(t, err, "unable to write key")
+	err = src.WriteFile("etc/apk/keys/alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub", []byte(`
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAutQkua2CAig4VFSJ7v54
+ALyu/J1WB3oni7qwCZD3veURw7HxpNAj9hR+S5N/pNeZgubQvJWyaPuQDm7PTs1+
+tFGiYNfAsiibX6Rv0wci3M+z2XEVAeR9Vzg6v4qoofDyoTbovn2LztaNEjTkB+oK
+tlvpNhg1zhou0jDVYFniEXvzjckxswHVb8cT0OMTKHALyLPrPOJzVtM9C1ew2Nnc
+3848xLiApMu3NBk0JqfcS3Bo5Y2b1FRVBvdt+2gFoKZix1MnZdAEZ8xQzL/a0YS5
+Hd0wj5+EEKHfOd3A75uPa/WQmA+o0cBFfrzm69QDcSJSwGpzWrD1ScH3AK8nWvoj
+v7e9gukK/9yl1b4fQQ00vttwJPSgm9EnfPHLAtgXkRloI27H6/PuLoNvSAMQwuCD
+hQRlyGLPBETKkHeodfLoULjhDi1K2gKJTMhtbnUcAA7nEphkMhPWkBpgFdrH+5z4
+Lxy+3ek0cqcI7K68EtrffU8jtUj9LFTUC8dERaIBs7NgQ/LfDbDfGh9g6qVj1hZl
+k9aaIPTm/xsi8v3u+0qaq7KzIBc9s59JOoA8TlpOaYdVgSQhHHLBaahOuAigH+VI
+isbC9vmqsThF2QdDtQt37keuqoda2E6sL7PUvIyVXDRfwX7uMDjlzTxHTymvq2Ck
+htBqojBnThmjJQFgZXocHG8CAwEAAQ==
 -----END PUBLIC KEY-----`), 0644)
 	require.NoError(t, err, "unable to write key")