Skip to content

Commit

Permalink
Add Erlang OTP Application cataloger (#2403)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
LaurentGoderre and wagoodman authored Feb 2, 2024
1 parent 3023a5a commit d7b9cc7
Show file tree
Hide file tree
Showing 19 changed files with 223 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,14 @@ var dirOnlyTestCases = []testCase{
"unicode_util_compat": "0.7.0",
},
},
{
name: "find ErLang OTP applications",
pkgType: pkg.ErlangOTPPkg,
pkgLanguage: pkg.Erlang,
pkgInfo: map[string]string{
"accept": "0.3.5",
},
},
{
name: "find swift package manager packages",
pkgType: pkg.SwiftPkg,
Expand Down
3 changes: 2 additions & 1 deletion cmd/syft/internal/test/integration/catalog_packages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ func TestPkgCoverageImage(t *testing.T) {
definedLanguages.Remove(pkg.Swift.String())
definedLanguages.Remove(pkg.CPP.String())
definedLanguages.Remove(pkg.Haskell.String())
definedLanguages.Remove(pkg.Erlang.String())
definedLanguages.Remove(pkg.Elixir.String())
definedLanguages.Remove(pkg.Erlang.String())

observedPkgs := strset.New()
definedPkgs := strset.New()
Expand All @@ -71,6 +71,7 @@ func TestPkgCoverageImage(t *testing.T) {
definedPkgs.Remove(string(pkg.GoModulePkg))
definedPkgs.Remove(string(pkg.RustPkg))
definedPkgs.Remove(string(pkg.DartPubPkg))
definedPkgs.Remove(string(pkg.ErlangOTPPkg))
definedPkgs.Remove(string(pkg.CocoapodsPkg))
definedPkgs.Remove(string(pkg.ConanPkg))
definedPkgs.Remove(string(pkg.HackagePkg))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{application,accept,
[{description,"Accept header(s) for Erlang/Elixir"},
{vsn,"0.3.5"},
{registered,[]},
{applications,[kernel,stdlib]},
{env,[]},
{modules, ['accept_encoding_header','accept_header','accept_neg','accept_parser']},
{maintainers,["Ilya Khaprov"]},
{licenses,["MIT"]},
{links,[{"Github","https://github.com/deadtrickster/accept"}]}]}.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ require (
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501
github.com/anchore/packageurl-go v0.1.1-0.20240202171727-877e1747d426
github.com/anchore/stereoscope v0.0.2-0.20240201224129-37291e81936d
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
// we are hinting brotli to latest due to warning when installing archiver v3:
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0v
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ=
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZVsCYMrIZBpFxwV26CbsuoEh5muXD5I1Ods=
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 h1:AV7qjwMcM4r8wFhJq3jLRztew3ywIyPTRapl2T1s9o8=
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4=
github.com/anchore/packageurl-go v0.1.1-0.20240202171727-877e1747d426 h1:agoiZchSf1Nnnos1azwIg5hk5Ao9TzZNBD9++AChGEg=
github.com/anchore/packageurl-go v0.1.1-0.20240202171727-877e1747d426/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4=
github.com/anchore/stereoscope v0.0.2-0.20240201224129-37291e81936d h1:v+kf6J76l5nWvdvxptgyLXWr45G8CGVScL4AAISi3nI=
github.com/anchore/stereoscope v0.0.2-0.20240201224129-37291e81936d/go.mod h1:uydT2ful8TY7Hr1WH1V1ZecSq/2TqXpAsGkMiy7lxD0=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
Expand Down
1 change: 1 addition & 0 deletions internal/task/package_tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func DefaultPackageTaskFactories() PackageTaskFactories {
newSimplePackageTaskFactory(dotnet.NewDotnetDepsCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dotnet", "c#"),
newSimplePackageTaskFactory(elixir.NewMixLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "elixir"),
newSimplePackageTaskFactory(erlang.NewRebarLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "erlang"),
newSimplePackageTaskFactory(erlang.NewOTPCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "erlang", "otp"),
newSimplePackageTaskFactory(haskell.NewHackageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "haskell", "hackage", "cabal"),
newPackageTaskFactory(
func(cfg CatalogingFactoryConfig) pkg.Cataloger {
Expand Down
2 changes: 2 additions & 0 deletions syft/format/internal/spdxutil/helpers/source_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ func SourceInfo(p pkg.Package) string {
answer = "acquired package info from cabal or stack manifest files"
case pkg.HexPkg:
answer = "acquired package info from rebar3 or mix manifest file"
case pkg.ErlangOTPPkg:
answer = "acquired package info from ErLang application resource file"
case pkg.LinuxKernelPkg:
answer = "acquired package info from linux kernel archive"
case pkg.LinuxKernelModulePkg:
Expand Down
8 changes: 8 additions & 0 deletions syft/format/internal/spdxutil/helpers/source_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,14 @@ func Test_SourceInfo(t *testing.T) {
"from rebar3 or mix manifest file",
},
},
{
input: pkg.Package{
Type: pkg.ErlangOTPPkg,
},
expected: []string{
"from ErLang application resource file",
},
},
{
input: pkg.Package{
Type: pkg.LinuxKernelPkg,
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())
})
}
}
34 changes: 31 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,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()
}
48 changes: 48 additions & 0 deletions syft/pkg/cataloger/erlang/parse_otp_app.go
Original file line number Diff line number Diff line change
@@ -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
43 changes: 43 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,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)
})
}
}
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 @@ -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,
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}
]}
]}.
4 changes: 2 additions & 2 deletions syft/pkg/language.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,15 @@ 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
case packageurl.TypeConan, string(CPP):
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
Expand Down
Loading

0 comments on commit d7b9cc7

Please sign in to comment.