Skip to content

Commit

Permalink
port haskell cataloger to new generic cataloger pattern (anchore#1290)
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Goodman <[email protected]>

Signed-off-by: Alex Goodman <[email protected]>
  • Loading branch information
wagoodman authored Oct 27, 2022
1 parent 97efd92 commit d47f0de
Show file tree
Hide file tree
Showing 10 changed files with 247 additions and 330 deletions.
18 changes: 10 additions & 8 deletions syft/pkg/cataloger/haskell/cataloger.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package haskell

import (
"github.com/anchore/syft/syft/pkg/cataloger/common"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)

// TODO: it seems that the stack.yaml/stack.lock/cabal.project.freeze have different purposes and could have different installation intentions
// (some describe intent and are meant to be used by a tool to resolve more dependencies while others describe the actual installed state).
// This hints at splittin these into multiple catalogers, but for now we'll keep them together.

// NewHackageCataloger returns a new Haskell cataloger object.
func NewHackageCataloger() *common.GenericCataloger {
globParsers := map[string]common.ParserFn{
"**/stack.yaml": parseStackYaml,
"**/stack.yaml.lock": parseStackLock,
"**/cabal.project.freeze": parseCabalFreeze,
}
return common.NewGenericCataloger(nil, globParsers, "hackage-cataloger")
func NewHackageCataloger() *generic.Cataloger {
return generic.NewCataloger("haskell-cataloger").
WithParserByGlobs(parseStackYaml, "**/stack.yaml").
WithParserByGlobs(parseStackLock, "**/stack.yaml.lock").
WithParserByGlobs(parseCabalFreeze, "**/cabal.project.freeze")
}
40 changes: 40 additions & 0 deletions syft/pkg/cataloger/haskell/package.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package haskell

import (
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)

func newPackage(name, version string, m *pkg.HackageMetadata, locations ...source.Location) pkg.Package {
p := pkg.Package{
Name: name,
Version: version,
Locations: source.NewLocationSet(locations...),
PURL: packageURL(name, version),
Language: pkg.Haskell,
Type: pkg.HackagePkg,
}

if m != nil {
p.MetadataType = pkg.HackageMetadataType
p.Metadata = *m
}

p.SetID()

return p
}

func packageURL(name, version string) string {
var qualifiers packageurl.Qualifiers

return packageurl.NewPackageURL(
packageurl.TypeHackage,
"",
name,
version,
qualifiers,
"",
).ToString()
}
26 changes: 8 additions & 18 deletions syft/pkg/cataloger/haskell/parse_cabal_freeze.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ import (

"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/common"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
"github.com/anchore/syft/syft/source"
)

// integrity check
var _ common.ParserFn = parseCabalFreeze
var _ generic.Parser = parseCabalFreeze

// parseCabalFreeze is a parser function for cabal.project.freeze contents, returning all packages discovered.
func parseCabalFreeze(_ string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) {
func parseCabalFreeze(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
r := bufio.NewReader(reader)
pkgs := []*pkg.Package{}
var pkgs []pkg.Package
for {
line, err := r.ReadString('\n')
switch {
Expand All @@ -35,19 +35,9 @@ func parseCabalFreeze(_ string, reader io.Reader) ([]*pkg.Package, []artifact.Re
line = strings.TrimSpace(line)
startPkgEncoding, endPkgEncoding := strings.Index(line, "any.")+4, strings.Index(line, ",")
line = line[startPkgEncoding:endPkgEncoding]
splits := strings.Split(line, " ==")
fields := strings.Split(line, " ==")

pkgName, pkgVersion := splits[0], splits[1]
pkgs = append(pkgs, &pkg.Package{
Name: pkgName,
Version: pkgVersion,
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: pkgName,
Version: pkgVersion,
},
})
pkgName, pkgVersion := fields[0], fields[1]
pkgs = append(pkgs, newPackage(pkgName, pkgVersion, nil, reader.Location))
}
}
193 changes: 76 additions & 117 deletions syft/pkg/cataloger/haskell/parse_cabal_freeze_test.go
Original file line number Diff line number Diff line change
@@ -1,152 +1,111 @@
package haskell

import (
"os"
"testing"

"github.com/go-test/deep"

"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
"github.com/anchore/syft/syft/source"
)

func TestParseCabalFreeze(t *testing.T) {
expected := []*pkg.Package{
fixture := "test-fixtures/cabal.project.freeze"
locationSet := source.NewLocationSet(source.NewLocation(fixture))

expectedPkgs := []pkg.Package{
{
Name: "Cabal",
Version: "3.2.1.0",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "Cabal",
Version: "3.2.1.0",
},
Name: "Cabal",
Version: "3.2.1.0",
PURL: "pkg:hackage/[email protected]",
Locations: locationSet,
Language: pkg.Haskell,
Type: pkg.HackagePkg,
},
{
Name: "Diff",
Version: "0.4.1",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "Diff",
Version: "0.4.1",
},
Name: "Diff",
Version: "0.4.1",
PURL: "pkg:hackage/[email protected]",
Locations: locationSet,
Language: pkg.Haskell,
Type: pkg.HackagePkg,
},
{
Name: "HTTP",
Version: "4000.3.16",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "HTTP",
Version: "4000.3.16",
},
Name: "HTTP",
Version: "4000.3.16",
PURL: "pkg:hackage/[email protected]",
Locations: locationSet,
Language: pkg.Haskell,
Type: pkg.HackagePkg,
},
{
Name: "HUnit",
Version: "1.6.2.0",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "HUnit",
Version: "1.6.2.0",
},
Name: "HUnit",
Version: "1.6.2.0",
PURL: "pkg:hackage/[email protected]",
Locations: locationSet,
Language: pkg.Haskell,
Type: pkg.HackagePkg,
},
{
Name: "OneTuple",
Version: "0.3.1",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "OneTuple",
Version: "0.3.1",
},
Name: "OneTuple",
Version: "0.3.1",
PURL: "pkg:hackage/[email protected]",
Locations: locationSet,
Language: pkg.Haskell,
Type: pkg.HackagePkg,
},
{
Name: "Only",
Version: "0.1",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "Only",
Version: "0.1",
},
Name: "Only",
Version: "0.1",
PURL: "pkg:hackage/[email protected]",
Locations: locationSet,
Language: pkg.Haskell,
Type: pkg.HackagePkg,
},
{
Name: "PyF",
Version: "0.10.2.0",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "PyF",
Version: "0.10.2.0",
},
Name: "PyF",
Version: "0.10.2.0",
PURL: "pkg:hackage/[email protected]",
Locations: locationSet,
Language: pkg.Haskell,
Type: pkg.HackagePkg,
},
{
Name: "QuickCheck",
Version: "2.14.2",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "QuickCheck",
Version: "2.14.2",
},
Name: "QuickCheck",
Version: "2.14.2",
PURL: "pkg:hackage/[email protected]",
Locations: locationSet,
Language: pkg.Haskell,
Type: pkg.HackagePkg,
},
{
Name: "RSA",
Version: "2.4.1",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "RSA",
Version: "2.4.1",
},
Name: "RSA",
Version: "2.4.1",
PURL: "pkg:hackage/[email protected]",
Locations: locationSet,
Language: pkg.Haskell,
Type: pkg.HackagePkg,
},
{
Name: "SHA",
Version: "1.6.4.4",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "SHA",
Version: "1.6.4.4",
},
Name: "SHA",
Version: "1.6.4.4",
PURL: "pkg:hackage/[email protected]",
Locations: locationSet,
Language: pkg.Haskell,
Type: pkg.HackagePkg,
},
{
Name: "Spock",
Version: "0.14.0.0",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "Spock",
Version: "0.14.0.0",
},
Name: "Spock",
Version: "0.14.0.0",
PURL: "pkg:hackage/[email protected]",
Locations: locationSet,
Language: pkg.Haskell,
Type: pkg.HackagePkg,
},
}

fixture, err := os.Open("test-fixtures/cabal.project.freeze")
if err != nil {
t.Fatalf("failed to open fixture: %+v", err)
}

// TODO: no relationships are under test yet
actual, _, err := parseCabalFreeze(fixture.Name(), fixture)
if err != nil {
t.Error(err)
}
// TODO: relationships are not under test yet
var expectedRelationships []artifact.Relationship

differences := deep.Equal(expected, actual)
if differences != nil {
t.Errorf("returned package list differed from expectation: %+v", differences)
}
pkgtest.TestFileParser(t, fixture, parseCabalFreeze, expectedPkgs, expectedRelationships)
}
Loading

0 comments on commit d47f0de

Please sign in to comment.