diff --git a/internal/capabilities/packages/asdf.yaml b/internal/capabilities/packages/asdf.yaml new file mode 100644 index 00000000000..d1856790969 --- /dev/null +++ b/internal/capabilities/packages/asdf.yaml @@ -0,0 +1,15 @@ +# Cataloger capabilities. See ../README.md for documentation. + +catalogers: + - ecosystem: other # MANUAL + name: asdf-cataloger # AUTO-GENERATED + type: generic # AUTO-GENERATED + source: # AUTO-GENERATED + file: syft/pkg/cataloger/asdf/cataloger.go + function: NewInstalledFileCataloger + selectors: # AUTO-GENERATED + - asdf + - directory + - image + - installed + - package diff --git a/internal/task/package_tasks.go b/internal/task/package_tasks.go index 12e12422b05..b88fc7d83de 100644 --- a/internal/task/package_tasks.go +++ b/internal/task/package_tasks.go @@ -6,6 +6,7 @@ import ( "github.com/anchore/syft/syft/pkg/cataloger/ai" "github.com/anchore/syft/syft/pkg/cataloger/alpine" "github.com/anchore/syft/syft/pkg/cataloger/arch" + "github.com/anchore/syft/syft/pkg/cataloger/asdf" "github.com/anchore/syft/syft/pkg/cataloger/binary" bitnamiSbomCataloger "github.com/anchore/syft/syft/pkg/cataloger/bitnami" "github.com/anchore/syft/syft/pkg/cataloger/conda" @@ -154,6 +155,7 @@ func DefaultPackageTaskFactories() Factories { newSimplePackageTaskFactory(lua.NewPackageCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "lua"), // other package catalogers /////////////////////////////////////////////////////////////////////////// + newSimplePackageTaskFactory(asdf.NewInstalledFileCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "asdf"), newPackageTaskFactory( func(cfg CatalogingFactoryConfig) pkg.Cataloger { return binary.NewClassifierCataloger(cfg.PackagesConfig.Binary) diff --git a/syft/pkg/cataloger/asdf/cataloger.go b/syft/pkg/cataloger/asdf/cataloger.go new file mode 100644 index 00000000000..b68d6b58e70 --- /dev/null +++ b/syft/pkg/cataloger/asdf/cataloger.go @@ -0,0 +1,12 @@ +package asdf + +import ( + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/generic" +) + +// NewInstalledFileCataloger returns a new cataloger for installed asdf-managed files +func NewInstalledFileCataloger() pkg.Cataloger { + return generic.NewCataloger("asdf-cataloger"). + WithParserByGlobs(parseAsdfInstallations, asdfInstallGlob) +} diff --git a/syft/pkg/cataloger/asdf/parser.go b/syft/pkg/cataloger/asdf/parser.go new file mode 100644 index 00000000000..885e4c0c202 --- /dev/null +++ b/syft/pkg/cataloger/asdf/parser.go @@ -0,0 +1,64 @@ +package asdf + +import ( + "context" + "strings" + + "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/common/cpe" + "github.com/anchore/syft/syft/pkg/cataloger/generic" + "github.com/anchore/syft/syft/pkg/cataloger/internal/licenses" +) + +const asdfCataloger = "asdf" +const asdfInstallGlob = "**/.asdf/installs/*/*/bin/*" + +// parseAsdfInstallations parses asdf version manager installations +func parseAsdfInstallations(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + name := "" + version := "" + + root := -1 + parts := strings.Split(reader.AccessPath, "/") + for i, part := range parts { + if part == "installs" { + root = i + break + } + } + + if root < 0 { + return nil, nil, nil + } + + if len(parts) > root+2 { + name = parts[root+1] + version = parts[root+2] + } + + if name == "" || version == "" { + log.Debug("no name or version found at %v", reader.AccessPath) + return nil, nil, nil + } + + p := pkg.Package{ + Name: name, + Version: version, + FoundBy: asdfCataloger, + Locations: file.NewLocationSet(reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), + Licenses: pkg.NewLicenseSet(licenses.FindRelativeToLocations(ctx, resolver, reader.Location)...), + Language: "", + Type: pkg.BinaryPkg, + CPEs: nil, + PURL: "", + Metadata: nil, + } + + p.CPEs = cpe.Generate(p) + p.SetID() + + return []pkg.Package{p}, nil, nil +} diff --git a/syft/pkg/cataloger/asdf/parser_test.go b/syft/pkg/cataloger/asdf/parser_test.go new file mode 100644 index 00000000000..915f5b75436 --- /dev/null +++ b/syft/pkg/cataloger/asdf/parser_test.go @@ -0,0 +1,23 @@ +package asdf + +import ( + "testing" + + "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" + "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/source/directorysource" + "github.com/stretchr/testify/require" +) + +func Test_AsdfCataloger(t *testing.T) { + src, err := directorysource.NewFromPath("test-fixtures") + require.NoError(t, err) + + res, err := src.FileResolver(source.SquashedScope) + require.NoError(t, err) + + pkgtest.NewCatalogTester(). + ExpectsPackageStrings([]string{"curl @ 5.2.1 (.asdf/installs/curl/5.2.1/bin/curl)"}). + WithResolver(res). + TestCataloger(t, NewInstalledFileCataloger()) +} diff --git a/syft/pkg/cataloger/asdf/test-fixtures/.asdf/installs/curl/5.2.1/bin/curl b/syft/pkg/cataloger/asdf/test-fixtures/.asdf/installs/curl/5.2.1/bin/curl new file mode 100644 index 00000000000..9268d7420fa --- /dev/null +++ b/syft/pkg/cataloger/asdf/test-fixtures/.asdf/installs/curl/5.2.1/bin/curl @@ -0,0 +1 @@ +executable \ No newline at end of file