Skip to content

Commit

Permalink
Add cataloger for Erlang OTP applications
Browse files Browse the repository at this point in the history
Signed-off-by: Laurent Goderre <[email protected]>
  • Loading branch information
LaurentGoderre committed Dec 7, 2023
1 parent 6fb153e commit b3535e9
Show file tree
Hide file tree
Showing 14 changed files with 200 additions and 6 deletions.
1 change: 1 addition & 0 deletions syft/internal/packagemetadata/generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func AllTypes() []any {
pkg.DotnetPortableExecutableEntry{},
pkg.DpkgDBEntry{},
pkg.ElixirMixLockEntry{},
pkg.ErlangOTPApplication{},
pkg.ErlangRebarLockEntry{},
pkg.GolangBinaryBuildinfoEntry{},
pkg.GolangModuleEntry{},
Expand Down
1 change: 1 addition & 0 deletions syft/internal/packagemetadata/names.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ var jsonTypes = makeJSONTypes(
jsonNames(pkg.PythonPipfileLockEntry{}, "python-pipfile-lock-entry", "PythonPipfileLockMetadata"),
jsonNames(pkg.PythonRequirementsEntry{}, "python-pip-requirements-entry", "PythonRequirementsMetadata"),
jsonNames(pkg.ErlangRebarLockEntry{}, "erlang-rebar-lock-entry", "RebarLockMetadataType"),
jsonNames(pkg.ErlangOTPApplication{}, "erlang-otp-application", "OTPApplicationMetadataType"),
jsonNames(pkg.RDescription{}, "r-description", "RDescriptionFileMetadataType"),
jsonNames(pkg.RpmDBEntry{}, "rpm-db-entry", "RpmMetadata", "RpmdbMetadata"),
jsonNamesWithoutLookup(pkg.RpmArchive{}, "rpm-archive", "RpmMetadata"), // the legacy value is split into two types, where the other is preferred
Expand Down
11 changes: 11 additions & 0 deletions syft/internal/packagemetadata/names_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,11 @@ func TestReflectTypeFromJSONName_LegacyValues(t *testing.T) {
input: "RebarLockMetadataType",
expected: reflect.TypeOf(pkg.ErlangRebarLockEntry{}),
},
{
name: "map pkg.ErlangOTPApplication struct type",
input: "OTPApplicationMetadataType",
expected: reflect.TypeOf(pkg.ErlangOTPApplication{}),
},
{
name: "map pkg.RDescription struct type",
input: "RDescriptionFileMetadataType",
Expand Down Expand Up @@ -444,6 +449,12 @@ func Test_JSONName_JSONLegacyName(t *testing.T) {
expectedJSONName: "erlang-rebar-lock-entry",
expectedLegacyName: "RebarLockMetadataType",
},
{
name: "OTPApplicationMetadata",
metadata: pkg.ErlangOTPApplication{},
expectedJSONName: "erlang-otp-application",
expectedLegacyName: "OTPApplicationMetadataType",
},
{
name: "RDescriptionFileMetadata",
metadata: pkg.RDescription{},
Expand Down
3 changes: 3 additions & 0 deletions syft/pkg/cataloger/cataloger.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func ImageCatalogers(cfg Config) []pkg.Cataloger {
cpp.NewConanInfoCataloger(),
debian.NewDBCataloger(),
dotnet.NewDotnetPortableExecutableCataloger(),
erlang.NewOTPCataloger(),
golang.NewGoModuleBinaryCataloger(cfg.Golang),
java.NewArchiveCataloger(cfg.JavaConfig()),
java.NewNativeImageCataloger(),
Expand Down Expand Up @@ -76,6 +77,7 @@ func DirectoryCatalogers(cfg Config) []pkg.Cataloger {
dotnet.NewDotnetPortableExecutableCataloger(),
elixir.NewMixLockCataloger(),
erlang.NewRebarLockCataloger(),
erlang.NewOTPCataloger(),
githubactions.NewActionUsageCataloger(),
githubactions.NewWorkflowUsageCataloger(),
golang.NewGoModuleFileCataloger(cfg.Golang),
Expand Down Expand Up @@ -115,6 +117,7 @@ func AllCatalogers(cfg Config) []pkg.Cataloger {
dotnet.NewDotnetPortableExecutableCataloger(),
elixir.NewMixLockCataloger(),
erlang.NewRebarLockCataloger(),
erlang.NewOTPCataloger(),
githubactions.NewActionUsageCataloger(),
githubactions.NewWorkflowUsageCataloger(),
golang.NewGoModuleFileCataloger(cfg.Golang),
Expand Down
1 change: 1 addition & 0 deletions syft/pkg/cataloger/cataloger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func Test_filterCatalogers(t *testing.T) {
"dotnet-deps-cataloger",
"elixir-mix-lock-cataloger",
"erlang-rebar-lock-cataloger",
"erlang-otp-application-cataloger",
"go-module-file-cataloger",
"go-module-binary-cataloger",
"haskell-cataloger",
Expand Down
7 changes: 6 additions & 1 deletion syft/pkg/cataloger/erlang/cataloger.go
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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")
}
27 changes: 26 additions & 1 deletion syft/pkg/cataloger/erlang/cataloger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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())
})
}
}
35 changes: 32 additions & 3 deletions syft/pkg/cataloger/erlang/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand All @@ -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(
Expand All @@ -34,3 +34,32 @@ func packageURL(m pkg.ErlangRebarLockEntry) string {
"",
).ToString()
}

func newPackageFromOTP(d pkg.ErlangOTPApplication, locations ...file.Location) pkg.Package {
p := pkg.Package{
Name: d.Name,
Version: d.Version,
Language: pkg.Erlang,
Locations: file.NewLocationSet(locations...),
PURL: packageURLFromOTP(d),
Type: pkg.UnknownPkg,
Metadata: d,
}

p.SetID()

return p
}

func packageURLFromOTP(m pkg.ErlangOTPApplication) string {
var qualifiers packageurl.Qualifiers

return packageurl.NewPackageURL(
packageurl.TypeGeneric,
"",
m.Name,
m.Version,
qualifiers,
"",
).ToString()
}
47 changes: 47 additions & 0 deletions syft/pkg/cataloger/erlang/parse_otp_app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package erlang

import (
"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"
)

// parseRebarLock parses a rebar.lock and returns the discovered Elixir packages.
//
//nolint:funlen
func parseOTPApp(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
doc, err := parseErlang(reader)
if err != nil {
return nil, nil, err
}

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(
pkg.ErlangOTPApplication{
Name: name,
Version: version,
},
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
)

packages = append(packages, p)
}
}

return packages, nil, nil
}

// integrity check
var _ generic.Parser = parseOTPApp
47 changes: 47 additions & 0 deletions syft/pkg/cataloger/erlang/parse_otp_app_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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.UnknownPkg,
PURL: "pkg:generic/[email protected]",
Metadata: pkg.ErlangOTPApplication{
Name: "rabbit",
Version: "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)
})
}
}
2 changes: 1 addition & 1 deletion syft/pkg/cataloger/erlang/parse_rebar_lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func parseRebarLock(_ file.Resolver, _ *generic.Environment, reader file.Locatio
version = versionNode.Get(2).Get(1).String()
}

p := newPackage(
p := newPackageFromRebar(
pkg.ErlangRebarLockEntry{
Name: name,
Version: version,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bogus erlang file
18 changes: 18 additions & 0 deletions syft/pkg/cataloger/erlang/test-fixtures/rabbitmq.app
Original file line number Diff line number Diff line change
@@ -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}
]}
]}.
5 changes: 5 additions & 0 deletions syft/pkg/erlang.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ type ErlangRebarLockEntry struct {
PkgHash string `mapstructure:"pkgHash" json:"pkgHash"`
PkgHashExt string `mapstructure:"pkgHashExt" json:"pkgHashExt"`
}

type ErlangOTPApplication struct {
Name string `mapstructure:"name" json:"name"`
Version string `mapstructure:"version" json:"version"`
}

0 comments on commit b3535e9

Please sign in to comment.