Skip to content

Commit

Permalink
Associate node package licenses from node_modules (#1152)
Browse files Browse the repository at this point in the history
  • Loading branch information
kzantow authored Aug 16, 2022
1 parent d1390b3 commit 21eb772
Show file tree
Hide file tree
Showing 9 changed files with 309 additions and 9 deletions.
2 changes: 1 addition & 1 deletion internal/spdxlicense/license.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
// EX: gpl-2.0.0-only ---> GPL-2.0-only
// See the debian link for more details on the spdx license differences

//go:generate go run generate/generate_license_list.go
//go:generate go run ./generate

func ID(id string) (string, bool) {
value, exists := licenseIDs[strings.ToLower(id)]
Expand Down
23 changes: 19 additions & 4 deletions internal/spdxlicense/license_list.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 13 additions & 3 deletions syft/pkg/cataloger/common/generic_cataloger.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ package common
import (
"fmt"

"github.com/anchore/syft/syft/artifact"

"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
Expand All @@ -19,14 +18,18 @@ import (
type GenericCataloger struct {
globParsers map[string]ParserFn
pathParsers map[string]ParserFn
postProcessors []PostProcessFunc
upstreamCataloger string
}

type PostProcessFunc func(resolver source.FileResolver, location source.Location, p *pkg.Package) error

// NewGenericCataloger if provided path-to-parser-function and glob-to-parser-function lookups creates a GenericCataloger
func NewGenericCataloger(pathParsers map[string]ParserFn, globParsers map[string]ParserFn, upstreamCataloger string) *GenericCataloger {
func NewGenericCataloger(pathParsers map[string]ParserFn, globParsers map[string]ParserFn, upstreamCataloger string, postProcessors ...PostProcessFunc) *GenericCataloger {
return &GenericCataloger{
globParsers: globParsers,
pathParsers: pathParsers,
postProcessors: postProcessors,
upstreamCataloger: upstreamCataloger,
}
}
Expand Down Expand Up @@ -69,6 +72,13 @@ func (c *GenericCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package,
continue
}

for _, postProcess := range c.postProcessors {
err = postProcess(resolver, location, p)
if err != nil {
return nil, nil, err
}
}

packages = append(packages, *p)
}

Expand Down
58 changes: 57 additions & 1 deletion syft/pkg/cataloger/javascript/cataloger.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ Package javascript provides a concrete Cataloger implementation for JavaScript e
package javascript

import (
"encoding/json"
"io"
"path"
"strings"

"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/common"
"github.com/anchore/syft/syft/source"
)

// NewJavascriptPackageCataloger returns a new JavaScript cataloger object based on detection of npm based packages.
Expand All @@ -23,5 +31,53 @@ func NewJavascriptLockCataloger() *common.GenericCataloger {
"**/yarn.lock": parseYarnLock,
}

return common.NewGenericCataloger(nil, globParsers, "javascript-lock-cataloger")
return common.NewGenericCataloger(nil, globParsers, "javascript-lock-cataloger", addLicenses)
}

func addLicenses(resolver source.FileResolver, location source.Location, p *pkg.Package) error {
dir := path.Dir(location.RealPath)
pkgPath := []string{dir, "node_modules"}
pkgPath = append(pkgPath, strings.Split(p.Name, "/")...)
pkgPath = append(pkgPath, "package.json")
pkgFile := path.Join(pkgPath...)
locations, err := resolver.FilesByPath(pkgFile)
if err != nil {
log.Debugf("an error occurred attempting to read: %s - %+v", pkgFile, err)
return nil
}

if len(locations) == 0 {
return nil
}

for _, location := range locations {
contentReader, err := resolver.FileContentsByLocation(location)
if err != nil {
log.Debugf("error getting file content reader for %s: %v", pkgFile, err)
return nil
}

contents, err := io.ReadAll(contentReader)
if err != nil {
log.Debugf("error reading file contents for %s: %v", pkgFile, err)
return nil
}

var pkgJSON packageJSON
err = json.Unmarshal(contents, &pkgJSON)
if err != nil {
log.Debugf("error parsing %s: %v", pkgFile, err)
return nil
}

licenses, err := pkgJSON.licensesFromJSON()
if err != nil {
log.Debugf("error getting licenses from %s: %v", pkgFile, err)
return nil
}

p.Licenses = licenses
}

return nil
}
102 changes: 102 additions & 0 deletions syft/pkg/cataloger/javascript/cataloger_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package javascript

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)

func Test_JavascriptCataloger(t *testing.T) {
expected := map[string]pkg.Package{
"@actions/core": {
Name: "@actions/core",
Version: "1.6.0",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Licenses: []string{"MIT"},
},
"wordwrap": {
Name: "wordwrap",
Version: "0.0.3",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"get-stdin": {
Name: "get-stdin",
Version: "5.0.1",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"minimist": {
Name: "minimist",
Version: "0.0.10",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"optimist": {
Name: "optimist",
Version: "0.6.1",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"string-width": {
Name: "string-width",
Version: "2.1.1",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"strip-ansi": {
Name: "strip-ansi",
Version: "4.0.0",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"strip-eof": {
Name: "wordwrap",
Version: "1.0.0",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"ansi-regex": {
Name: "ansi-regex",
Version: "3.0.0",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"is-fullwidth-code-point": {
Name: "is-fullwidth-code-point",
Version: "2.0.0",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"cowsay": {
Name: "cowsay",
Version: "1.4.0",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Licenses: []string{"MIT"},
},
}

s, err := source.NewFromDirectory("test-fixtures/pkg-lock")
require.NoError(t, err)

resolver, err := s.FileResolver(source.AllLayersScope)
require.NoError(t, err)

actual, _, err := NewJavascriptLockCataloger().Catalog(resolver)
if err != nil {
t.Fatalf("failed to parse package-lock.json: %+v", err)
}

var pkgs []*pkg.Package
for _, p := range actual {
p2 := p
pkgs = append(pkgs, &p2)
}

assertPkgsEqual(t, pkgs, expected)
}
6 changes: 6 additions & 0 deletions syft/pkg/cataloger/javascript/parse_package_lock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ func assertPkgsEqual(t *testing.T, actual []*pkg.Package, expected map[string]pk

func TestParsePackageLock(t *testing.T) {
expected := map[string]pkg.Package{
"@actions/core": {
Name: "@actions/core",
Version: "1.6.0",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"wordwrap": {
Name: "wordwrap",
Version: "0.0.3",
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 21eb772

Please sign in to comment.