mirror of
https://github.com/anchore/syft.git
synced 2026-11-02 07:26:34 +01:00
Add Erlang OTP Application cataloger (#2403)
* Add cataloger for Erlang OTP applications Signed-off-by: Laurent Goderre <laurent.goderre@docker.com> * Add OTP Package type and Purl for ErLang Signed-off-by: Laurent Goderre <laurent.goderre@docker.com> * remove erlang OTP metadata type Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * use OTP purl type Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * restore otp fixture and adjust tests for dir-only results Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Laurent Goderre <laurent.goderre@docker.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
co-authored by
Alex Goodman
parent
3023a5a7bc
commit
d7b9cc70b0
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Package erlang provides a concrete Cataloger implementation relating to packages within the Erlang language ecosystem.
|
||||
Package erlang provides concrete Catalogers implementation relating to packages within the Erlang language ecosystem.
|
||||
*/
|
||||
package erlang
|
||||
|
||||
@@ -13,3 +13,8 @@ func NewRebarLockCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("erlang-rebar-lock-cataloger").
|
||||
WithParserByGlobs(parseRebarLock, "**/rebar.lock")
|
||||
}
|
||||
|
||||
func NewOTPCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("erlang-otp-application-cataloger").
|
||||
WithParserByGlobs(parseOTPApp, "**/*.app")
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
func TestCataloger_Globs(t *testing.T) {
|
||||
func TestCatalogerRebar_Globs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
@@ -30,3 +30,28 @@ func TestCataloger_Globs(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCatalogerOTP_Globs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "obtain OTP resource files",
|
||||
fixture: "test-fixtures/glob-paths",
|
||||
expected: []string{
|
||||
"src/rabbitmq.app",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromDirectory(t, test.fixture).
|
||||
ExpectsResolverContentQueries(test.expected).
|
||||
TestCataloger(t, NewOTPCataloger())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func newPackage(d pkg.ErlangRebarLockEntry, locations ...file.Location) pkg.Package {
|
||||
func newPackageFromRebar(d pkg.ErlangRebarLockEntry, locations ...file.Location) pkg.Package {
|
||||
p := pkg.Package{
|
||||
Name: d.Name,
|
||||
Version: d.Version,
|
||||
Language: pkg.Erlang,
|
||||
Locations: file.NewLocationSet(locations...),
|
||||
PURL: packageURL(d),
|
||||
PURL: packageURLFromRebar(d),
|
||||
Type: pkg.HexPkg,
|
||||
Metadata: d,
|
||||
}
|
||||
@@ -22,7 +22,7 @@ func newPackage(d pkg.ErlangRebarLockEntry, locations ...file.Location) pkg.Pack
|
||||
return p
|
||||
}
|
||||
|
||||
func packageURL(m pkg.ErlangRebarLockEntry) string {
|
||||
func packageURLFromRebar(m pkg.ErlangRebarLockEntry) string {
|
||||
var qualifiers packageurl.Qualifiers
|
||||
|
||||
return packageurl.NewPackageURL(
|
||||
@@ -34,3 +34,31 @@ func packageURL(m pkg.ErlangRebarLockEntry) string {
|
||||
"",
|
||||
).ToString()
|
||||
}
|
||||
|
||||
func newPackageFromOTP(name, version string, locations ...file.Location) pkg.Package {
|
||||
p := pkg.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Language: pkg.Erlang,
|
||||
Locations: file.NewLocationSet(locations...),
|
||||
PURL: packageURLFromOTP(name, version),
|
||||
Type: pkg.ErlangOTPPkg,
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func packageURLFromOTP(name, version string) string {
|
||||
var qualifiers packageurl.Qualifiers
|
||||
|
||||
return packageurl.NewPackageURL(
|
||||
packageurl.TypeOTP,
|
||||
"",
|
||||
name,
|
||||
version,
|
||||
qualifiers,
|
||||
"",
|
||||
).ToString()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package erlang
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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/generic"
|
||||
)
|
||||
|
||||
// parseOTPApp parses a OTP *.app files to a package objects
|
||||
func parseOTPApp(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
doc, err := parseErlang(reader)
|
||||
if err != nil {
|
||||
// there are multiple file formats that use the *.app extension, so it's possible that this is not an OTP app file at all
|
||||
// ... which means we should not return an error here
|
||||
log.WithFields("error", err).Trace("unable to parse Erlang OTP app")
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
var packages []pkg.Package
|
||||
|
||||
root := doc.Get(0)
|
||||
|
||||
name := root.Get(1).String()
|
||||
|
||||
keys := root.Get(2)
|
||||
|
||||
for _, key := range keys.Slice() {
|
||||
if key.Get(0).String() == "vsn" {
|
||||
version := key.Get(1).String()
|
||||
|
||||
p := newPackageFromOTP(
|
||||
name, version,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
)
|
||||
|
||||
packages = append(packages, p)
|
||||
}
|
||||
}
|
||||
|
||||
return packages, nil, nil
|
||||
}
|
||||
|
||||
// integrity check
|
||||
var _ generic.Parser = parseOTPApp
|
||||
@@ -0,0 +1,43 @@
|
||||
package erlang
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"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/internal/pkgtest"
|
||||
)
|
||||
|
||||
func TestParseOTPApplication(t *testing.T) {
|
||||
tests := []struct {
|
||||
fixture string
|
||||
expected []pkg.Package
|
||||
}{
|
||||
{
|
||||
fixture: "test-fixtures/rabbitmq.app",
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "rabbit",
|
||||
Version: "3.12.10",
|
||||
Language: pkg.Erlang,
|
||||
Type: pkg.ErlangOTPPkg,
|
||||
PURL: "pkg:otp/rabbit@3.12.10",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixture, func(t *testing.T) {
|
||||
// TODO: relationships are not under test
|
||||
var expectedRelationships []artifact.Relationship
|
||||
|
||||
for idx := range test.expected {
|
||||
test.expected[idx].Locations = file.NewLocationSet(file.NewLocation(test.fixture))
|
||||
}
|
||||
|
||||
pkgtest.TestFileParser(t, test.fixture, parseOTPApp, test.expected, expectedRelationships)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ func parseRebarLock(_ context.Context, _ file.Resolver, _ *generic.Environment,
|
||||
version = versionNode.Get(2).Get(1).String()
|
||||
}
|
||||
|
||||
p := newPackage(
|
||||
p := newPackageFromRebar(
|
||||
pkg.ErlangRebarLockEntry{
|
||||
Name: name,
|
||||
Version: version,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
bogus erlang file
|
||||
@@ -0,0 +1,18 @@
|
||||
{application, 'rabbit', [
|
||||
{description, "RabbitMQ"},
|
||||
{vsn, "3.12.10"},
|
||||
{id, "v3.12.9-9-g1f61ca8"},
|
||||
{modules, ['amqqueue','background_gc']},
|
||||
{optional_applications, []},
|
||||
{env, [
|
||||
{memory_monitor_interval, 2500},
|
||||
{disk_free_limit, 50000000}, %% 50MB
|
||||
{msg_store_index_module, rabbit_msg_store_ets_index},
|
||||
{backing_queue_module, rabbit_variable_queue},
|
||||
%% 0 ("no limit") would make a better default, but that
|
||||
%% breaks the QPid Java client
|
||||
{frame_max, 131072},
|
||||
%% see rabbitmq-server#1593
|
||||
{channel_max, 2047}
|
||||
]}
|
||||
]}.
|
||||
@@ -80,7 +80,7 @@ func LanguageByName(name string) Language {
|
||||
return Rust
|
||||
case packageurl.TypePub, string(DartPubPkg), string(Dart):
|
||||
return Dart
|
||||
case packageurl.TypeDotnet, packageurl.TypeNuget:
|
||||
case string(Dotnet), ".net", packageurl.TypeNuget:
|
||||
return Dotnet
|
||||
case packageurl.TypeCocoapods, packageurl.TypeSwift, string(CocoapodsPkg), string(SwiftPkg):
|
||||
return Swift
|
||||
@@ -88,7 +88,7 @@ func LanguageByName(name string) Language {
|
||||
return CPP
|
||||
case packageurl.TypeHackage, string(Haskell):
|
||||
return Haskell
|
||||
case packageurl.TypeHex, "beam", "elixir", "erlang":
|
||||
case packageurl.TypeHex, packageurl.TypeOTP, "beam", "elixir", "erlang":
|
||||
// should we support returning multiple languages to support this case?
|
||||
// answer: no. We want this to definitively answer "which language does this package represent?"
|
||||
// which might not be possible in all cases. See for more context: https://github.com/package-url/purl-spec/pull/178
|
||||
|
||||
+9
-2
@@ -18,6 +18,7 @@ const (
|
||||
DartPubPkg Type = "dart-pub"
|
||||
DebPkg Type = "deb"
|
||||
DotnetPkg Type = "dotnet"
|
||||
ErlangOTPPkg Type = "erlang-otp"
|
||||
GemPkg Type = "gem"
|
||||
GithubActionPkg Type = "github-action"
|
||||
GithubActionWorkflowPkg Type = "github-action-workflow"
|
||||
@@ -51,6 +52,7 @@ var AllPkgs = []Type{
|
||||
DartPubPkg,
|
||||
DebPkg,
|
||||
DotnetPkg,
|
||||
ErlangOTPPkg,
|
||||
GemPkg,
|
||||
GithubActionPkg,
|
||||
GithubActionWorkflowPkg,
|
||||
@@ -91,7 +93,9 @@ func (t Type) PackageURLType() string {
|
||||
case DebPkg:
|
||||
return "deb"
|
||||
case DotnetPkg:
|
||||
return packageurl.TypeDotnet
|
||||
return "dotnet"
|
||||
case ErlangOTPPkg:
|
||||
return packageurl.TypeOTP
|
||||
case GemPkg:
|
||||
return packageurl.TypeGem
|
||||
case HexPkg:
|
||||
@@ -146,6 +150,7 @@ func TypeFromPURL(p string) Type {
|
||||
return TypeByName(ptype)
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func TypeByName(name string) Type {
|
||||
switch name {
|
||||
case packageurl.TypeDebian:
|
||||
@@ -172,7 +177,7 @@ func TypeByName(name string) Type {
|
||||
return RustPkg
|
||||
case packageurl.TypePub:
|
||||
return DartPubPkg
|
||||
case packageurl.TypeDotnet:
|
||||
case "dotnet": // here to support legacy use cases
|
||||
return DotnetPkg
|
||||
case packageurl.TypeCocoapods:
|
||||
return CocoapodsPkg
|
||||
@@ -184,6 +189,8 @@ func TypeByName(name string) Type {
|
||||
return PortagePkg
|
||||
case packageurl.TypeHex:
|
||||
return HexPkg
|
||||
case packageurl.TypeOTP:
|
||||
return ErlangOTPPkg
|
||||
case "linux-kernel":
|
||||
return LinuxKernelPkg
|
||||
case "linux-kernel-module":
|
||||
|
||||
@@ -83,6 +83,10 @@ func TestTypeFromPURL(t *testing.T) {
|
||||
purl: "pkg:hex/hpax/hpax@0.1.1",
|
||||
expected: HexPkg,
|
||||
},
|
||||
{
|
||||
purl: "pkg:otp/accept@0.3.5",
|
||||
expected: ErlangOTPPkg,
|
||||
},
|
||||
{
|
||||
purl: "pkg:generic/linux-kernel@5.10.15",
|
||||
expected: LinuxKernelPkg,
|
||||
|
||||
Reference in New Issue
Block a user