read relative etc/apk/repositories for alpine version when no OS provided (#1615)

Signed-off-by: Avi Deitcher <avi@deitcher.net>
This commit is contained in:
Avi Deitcher 2023-03-02 20:04:56 +02:00 committed by GitHub
parent 5f90d03718
commit 01230aa766
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 122 additions and 2 deletions

View File

@ -24,6 +24,7 @@ func TestCataloger_Globs(t *testing.T) {
pkgtest.NewCatalogTester(). pkgtest.NewCatalogTester().
FromDirectory(t, test.fixture). FromDirectory(t, test.fixture).
ExpectsResolverContentQueries(test.expected). ExpectsResolverContentQueries(test.expected).
IgnoreUnfulfilledPathResponses("etc/apk/repositories").
TestCataloger(t, NewApkdbCataloger()) TestCataloger(t, NewApkdbCataloger())
}) })
} }

View File

@ -3,7 +3,9 @@ package apkdb
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"io"
"path" "path"
"regexp"
"strconv" "strconv"
"strings" "strings"
@ -20,11 +22,15 @@ import (
// integrity check // integrity check
var _ generic.Parser = parseApkDB var _ generic.Parser = parseApkDB
var (
repoRegex = regexp.MustCompile(`(?m)^https://.*\.alpinelinux\.org/alpine/v([^/]+)/([a-zA-Z0-9_]+)$`)
)
// parseApkDB parses packages from a given APK installed DB file. For more // parseApkDB parses packages from a given APK installed DB file. For more
// information on specific fields, see https://wiki.alpinelinux.org/wiki/Apk_spec. // information on specific fields, see https://wiki.alpinelinux.org/wiki/Apk_spec.
// //
//nolint:funlen //nolint:funlen,gocognit
func parseApkDB(_ source.FileResolver, env *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { func parseApkDB(resolver source.FileResolver, env *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
scanner := bufio.NewScanner(reader) scanner := bufio.NewScanner(reader)
var apks []pkg.ApkMetadata var apks []pkg.ApkMetadata
@ -101,6 +107,19 @@ func parseApkDB(_ source.FileResolver, env *generic.Environment, reader source.L
if env != nil { if env != nil {
r = env.LinuxRelease r = env.LinuxRelease
} }
// this is somewhat ugly, but better than completely failing when we can't find the release,
// e.g. embedded deeper in the tree, like containers or chroots.
// but we now have no way of handling different repository sources. On the other hand,
// we never could before this. At least now, we can handle some.
// This should get fixed with https://gitlab.alpinelinux.org/alpine/apk-tools/-/issues/10875
if r == nil {
// find the repositories file from the relative directory of the DB file
releases := findReleases(resolver, reader.Location.RealPath)
if len(releases) > 0 {
r = &releases[0]
}
}
pkgs := make([]pkg.Package, 0, len(apks)) pkgs := make([]pkg.Package, 0, len(apks))
for _, apk := range apks { for _, apk := range apks {
@ -110,6 +129,58 @@ func parseApkDB(_ source.FileResolver, env *generic.Environment, reader source.L
return pkgs, discoverPackageDependencies(pkgs), nil return pkgs, discoverPackageDependencies(pkgs), nil
} }
func findReleases(resolver source.FileResolver, dbPath string) []linux.Release {
if resolver == nil {
return nil
}
reposLocation := path.Clean(path.Join(path.Dir(dbPath), "../../../etc/apk/repositories"))
locations, err := resolver.FilesByPath(reposLocation)
if err != nil {
log.Tracef("unable to find APK repositories file %q: %+v", reposLocation, err)
return nil
}
if len(locations) == 0 {
return nil
}
location := locations[0]
reposReader, err := resolver.FileContentsByLocation(location)
if err != nil {
log.Tracef("unable to fetch contents for APK repositories file %q: %+v", reposLocation, err)
return nil
}
return parseReleasesFromAPKRepository(source.LocationReadCloser{
Location: location,
ReadCloser: reposReader,
})
}
func parseReleasesFromAPKRepository(reader source.LocationReadCloser) []linux.Release {
var releases []linux.Release
reposB, err := io.ReadAll(reader)
if err != nil {
log.Tracef("unable to read APK repositories file %q: %+v", reader.Location.RealPath, err)
return nil
}
parts := repoRegex.FindAllStringSubmatch(string(reposB), -1)
for _, part := range parts {
if len(part) >= 3 {
releases = append(releases, linux.Release{
Name: "Alpine Linux",
ID: "alpine",
VersionID: part[1],
})
}
}
return releases
}
func parseApkField(line string) *apkField { func parseApkField(line string) *apkField {
parts := strings.SplitN(line, ":", 2) parts := strings.SplitN(line, ":", 2)
if len(parts) != 2 { if len(parts) != 2 {

View File

@ -1,8 +1,10 @@
package apkdb package apkdb
import ( import (
"io"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
@ -1186,3 +1188,49 @@ func Test_stripVersionSpecifier(t *testing.T) {
}) })
} }
} }
func TestParseReleasesFromAPKRepository(t *testing.T) {
tests := []struct {
repos string
want []linux.Release
desc string
}{
{
"https://foo.alpinelinux.org/alpine/v3.14/main",
[]linux.Release{
{Name: "Alpine Linux", ID: "alpine", VersionID: "3.14"},
},
"single repo",
},
{
`https://foo.alpinelinux.org/alpine/v3.14/main
https://foo.alpinelinux.org/alpine/v3.14/community`,
[]linux.Release{
{Name: "Alpine Linux", ID: "alpine", VersionID: "3.14"},
{Name: "Alpine Linux", ID: "alpine", VersionID: "3.14"},
},
"multiple repos",
},
{
``,
nil,
"empty",
},
{
`https://foo.bar.org/alpine/v3.14/main
https://foo.them.org/alpine/v3.14/community`,
nil,
"invalid repos",
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
reposReader := io.NopCloser(strings.NewReader(tt.repos))
got := parseReleasesFromAPKRepository(source.LocationReadCloser{
Location: source.NewLocation("test"),
ReadCloser: reposReader,
})
assert.Equal(t, tt.want, got)
})
}
}