Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/Cabal@3.2.1.0",
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/Diff@0.4.1",
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/HTTP@4000.3.16",
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/HUnit@1.6.2.0",
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/OneTuple@0.3.1",
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/Only@0.1",
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/PyF@0.10.2.0",
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/QuickCheck@2.14.2",
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/RSA@2.4.1",
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/SHA@1.6.4.4",
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/Spock@0.14.0.0",
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