mirror of
https://github.com/anchore/syft.git
synced 2026-10-15 15:47:23 +02:00
Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecf11f0e3d | ||
|
|
628c2e44a5 | ||
|
|
aba0de6e65 | ||
|
|
cb0278f3bc | ||
|
|
b519340b86 | ||
|
|
2356539ebe | ||
|
|
1537e73083 | ||
|
|
52adfcbd44 | ||
|
|
a3f0d659da | ||
|
|
8a0fa5d3ad | ||
|
|
253faf5652 | ||
|
|
69d2b1ba3c | ||
|
|
b08a11e46d | ||
|
|
fef951c29b | ||
|
|
a906b9a03a |
@@ -37,6 +37,10 @@ ifeq "$(strip $(VERSION))" ""
|
||||
override VERSION = $(shell git describe --always --tags --dirty)
|
||||
endif
|
||||
|
||||
# used to generate the changelog from the second to last tag to the current tag (used in the release pipeline when the release tag is in place)
|
||||
LAST_TAG := $(shell git describe --abbrev=0 --tags $(shell git rev-list --tags --max-count=1))
|
||||
SECOND_TO_LAST_TAG := $(shell git describe --abbrev=0 --tags $(shell git rev-list --tags --skip=1 --max-count=1))
|
||||
|
||||
## Variable assertions
|
||||
|
||||
ifndef TEMPDIR
|
||||
@@ -99,7 +103,7 @@ bootstrap-tools: $(TEMPDIR)
|
||||
GO111MODULE=off GOBIN=$(shell realpath $(TEMPDIR)) go get -u golang.org/x/perf/cmd/benchstat
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(TEMPDIR)/ v1.42.1
|
||||
curl -sSfL https://raw.githubusercontent.com/wagoodman/go-bouncer/master/bouncer.sh | sh -s -- -b $(TEMPDIR)/ v0.2.0
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/chronicle/main/install.sh | sh -s -- -b $(TEMPDIR)/ v0.3.0
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/chronicle/main/install.sh | sh -s -- -b $(TEMPDIR)/ v0.2.0-beta
|
||||
.github/scripts/goreleaser-install.sh -b $(TEMPDIR)/ v0.177.0
|
||||
|
||||
.PHONY: bootstrap-go
|
||||
@@ -190,11 +194,6 @@ fixtures:
|
||||
generate-json-schema: ## Generate a new json schema
|
||||
cd schema/json && go run generate.go
|
||||
|
||||
.PHONY: generate-license-list
|
||||
generate-license-list: ## Generate an updated spdx license list
|
||||
go generate ./internal/spdxlicense/...
|
||||
gofmt -s -w ./internal/spdxlicense
|
||||
|
||||
.PHONY: build
|
||||
build: $(SNAPSHOTDIR) ## Build release snapshot binaries and packages
|
||||
|
||||
@@ -253,19 +252,27 @@ cli: $(SNAPSHOTDIR) ## Run CLI tests
|
||||
SYFT_BINARY_LOCATION='$(SNAPSHOT_CMD)' \
|
||||
go test -count=1 -v ./test/cli
|
||||
|
||||
.PHONY: changelog
|
||||
changelog: clean-changelog CHANGELOG.md
|
||||
.PHONY: changelog-release
|
||||
changelog-release:
|
||||
$(TEMPDIR)/chronicle --since-tag $(SECOND_TO_LAST_TAG) --until-tag $(LAST_TAG) -vv > CHANGELOG.md
|
||||
|
||||
@printf '\n$(BOLD)$(CYAN)Release $(VERSION) Changelog$(RESET)\n\n'
|
||||
@cat CHANGELOG.md
|
||||
|
||||
.PHONY: changelog-unreleased
|
||||
changelog-unreleased: ## show the current changelog that will be produced on the next release (note: requires GITHUB_TOKEN set)
|
||||
$(TEMPDIR)/chronicle --since-tag $(LAST_TAG) -vv > CHANGELOG.md
|
||||
|
||||
@printf '\n$(BOLD)$(CYAN)Unreleased Changes (closed PRs and issues will not be in the final changelog)$(RESET)\n'
|
||||
|
||||
@docker run -it --rm \
|
||||
-v $(shell pwd)/CHANGELOG.md:/CHANGELOG.md \
|
||||
rawkode/mdv \
|
||||
-t 748.5989 \
|
||||
/CHANGELOG.md
|
||||
|
||||
CHANGELOG.md:
|
||||
$(TEMPDIR)/chronicle -vv > CHANGELOG.md
|
||||
|
||||
.PHONY: release
|
||||
release: clean-dist CHANGELOG.md ## Build and publish final binaries and packages. Intended to be run only on macOS.
|
||||
release: clean-dist changelog-release ## Build and publish final binaries and packages. Intended to be run only on macOS.
|
||||
$(call title,Publishing release artifacts)
|
||||
|
||||
# Prepare for macOS-specific signing process
|
||||
@@ -306,13 +313,9 @@ clean-snapshot:
|
||||
rm -rf $(SNAPSHOTDIR) $(TEMPDIR)/goreleaser.yaml
|
||||
|
||||
.PHONY: clean-dist
|
||||
clean-dist: clean-changelog
|
||||
clean-dist:
|
||||
rm -rf $(DISTDIR) $(TEMPDIR)/goreleaser.yaml
|
||||
|
||||
.PHONY: clean-changelog
|
||||
clean-changelog:
|
||||
rm -f CHANGELOG.md
|
||||
|
||||
clean-test-image-cache: clean-test-image-tar-cache clean-test-image-docker-cache
|
||||
|
||||
.PHONY: clear-test-image-tar-cache
|
||||
|
||||
+16
-11
@@ -14,8 +14,10 @@ import (
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/ui"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/event"
|
||||
"github.com/anchore/syft/syft/format"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/pkg/profile"
|
||||
@@ -253,25 +255,26 @@ func packagesExecWorker(userInput string) <-chan error {
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
catalog, d, err := syft.CatalogPackages(src, appConfig.Package.Cataloger.ScopeOpt)
|
||||
catalog, relationships, d, err := syft.CatalogPackages(src, appConfig.Package.Cataloger.ScopeOpt)
|
||||
if err != nil {
|
||||
errs <- fmt.Errorf("failed to catalog input: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
if appConfig.Anchore.Host != "" {
|
||||
if err := runPackageSbomUpload(src, src.Metadata, catalog, d); err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
sbomResult := sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
PackageCatalog: catalog,
|
||||
Distro: d,
|
||||
},
|
||||
Source: src.Metadata,
|
||||
}
|
||||
|
||||
if appConfig.Anchore.Host != "" {
|
||||
if err := runPackageSbomUpload(src, sbomResult); err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
Relationships: relationships,
|
||||
Source: src.Metadata,
|
||||
}
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
@@ -282,7 +285,7 @@ func packagesExecWorker(userInput string) <-chan error {
|
||||
return errs
|
||||
}
|
||||
|
||||
func runPackageSbomUpload(src *source.Source, s sbom.SBOM) error {
|
||||
func runPackageSbomUpload(src *source.Source, s source.Metadata, catalog *pkg.Catalog, d *distro.Distro) error {
|
||||
log.Infof("uploading results to %s", appConfig.Anchore.Host)
|
||||
|
||||
if src.Metadata.Scheme != source.ImageScheme {
|
||||
@@ -317,7 +320,9 @@ func runPackageSbomUpload(src *source.Source, s sbom.SBOM) error {
|
||||
|
||||
importCfg := anchore.ImportConfig{
|
||||
ImageMetadata: src.Image.Metadata,
|
||||
SBOM: s,
|
||||
SourceMetadata: s,
|
||||
Catalog: catalog,
|
||||
Distro: d,
|
||||
Dockerfile: dockerfileContents,
|
||||
OverwriteExistingUpload: appConfig.Anchore.OverwriteExistingImage,
|
||||
Timeout: appConfig.Anchore.ImportTimeout,
|
||||
|
||||
+47
-13
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
|
||||
"github.com/anchore/stereoscope"
|
||||
@@ -88,7 +90,6 @@ func powerUserExec(_ *cobra.Command, args []string) error {
|
||||
ui.Select(isVerbose(), appConfig.Quiet, reporter)...,
|
||||
)
|
||||
}
|
||||
|
||||
func powerUserExecWorker(userInput string) <-chan error {
|
||||
errs := make(chan error)
|
||||
go func() {
|
||||
@@ -109,28 +110,61 @@ func powerUserExecWorker(userInput string) <-chan error {
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
analysisResults := sbom.SBOM{
|
||||
s := sbom.SBOM{
|
||||
Source: src.Metadata,
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
var results []<-chan artifact.Relationship
|
||||
for _, task := range tasks {
|
||||
wg.Add(1)
|
||||
go func(task powerUserTask) {
|
||||
defer wg.Done()
|
||||
if err = task(&analysisResults.Artifacts, src); err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
}(task)
|
||||
c := make(chan artifact.Relationship)
|
||||
results = append(results, c)
|
||||
|
||||
go runTask(task, &s.Artifacts, src, c, errs)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
for relationship := range mergeResults(results...) {
|
||||
s.Relationships = append(s.Relationships, relationship)
|
||||
}
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.PresenterReady,
|
||||
Value: poweruser.NewJSONPresenter(analysisResults, *appConfig),
|
||||
Value: poweruser.NewJSONPresenter(s, *appConfig),
|
||||
})
|
||||
}()
|
||||
return errs
|
||||
}
|
||||
|
||||
func runTask(t powerUserTask, a *sbom.Artifacts, src *source.Source, c chan<- artifact.Relationship, errs chan<- error) {
|
||||
defer close(c)
|
||||
|
||||
relationships, err := t(a, src)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
|
||||
for _, relationship := range relationships {
|
||||
c <- relationship
|
||||
}
|
||||
}
|
||||
|
||||
func mergeResults(cs ...<-chan artifact.Relationship) <-chan artifact.Relationship {
|
||||
var wg sync.WaitGroup
|
||||
var results = make(chan artifact.Relationship)
|
||||
|
||||
wg.Add(len(cs))
|
||||
for _, c := range cs {
|
||||
go func(c <-chan artifact.Relationship) {
|
||||
for n := range c {
|
||||
results <- n
|
||||
}
|
||||
wg.Done()
|
||||
}(c)
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(results)
|
||||
}()
|
||||
return results
|
||||
}
|
||||
|
||||
+27
-25
@@ -4,6 +4,8 @@ import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
|
||||
"github.com/anchore/syft/syft"
|
||||
@@ -11,7 +13,7 @@ import (
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
type powerUserTask func(*sbom.Artifacts, *source.Source) error
|
||||
type powerUserTask func(*sbom.Artifacts, *source.Source) ([]artifact.Relationship, error)
|
||||
|
||||
func powerUserTasks() ([]powerUserTask, error) {
|
||||
var tasks []powerUserTask
|
||||
@@ -43,16 +45,16 @@ func catalogPackagesTask() (powerUserTask, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
task := func(results *sbom.Artifacts, src *source.Source) error {
|
||||
packageCatalog, theDistro, err := syft.CatalogPackages(src, appConfig.Package.Cataloger.ScopeOpt)
|
||||
task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
|
||||
packageCatalog, relationships, theDistro, err := syft.CatalogPackages(src, appConfig.Package.Cataloger.ScopeOpt)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results.PackageCatalog = packageCatalog
|
||||
results.Distro = theDistro
|
||||
|
||||
return nil
|
||||
return relationships, nil
|
||||
}
|
||||
|
||||
return task, nil
|
||||
@@ -65,18 +67,18 @@ func catalogFileMetadataTask() (powerUserTask, error) {
|
||||
|
||||
metadataCataloger := file.NewMetadataCataloger()
|
||||
|
||||
task := func(results *sbom.Artifacts, src *source.Source) error {
|
||||
task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
|
||||
resolver, err := src.FileResolver(appConfig.FileMetadata.Cataloger.ScopeOpt)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := metadataCataloger.Catalog(resolver)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
results.FileMetadata = result
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return task, nil
|
||||
@@ -111,18 +113,18 @@ func catalogFileDigestsTask() (powerUserTask, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
task := func(results *sbom.Artifacts, src *source.Source) error {
|
||||
task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
|
||||
resolver, err := src.FileResolver(appConfig.FileMetadata.Cataloger.ScopeOpt)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := digestsCataloger.Catalog(resolver)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
results.FileDigests = result
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return task, nil
|
||||
@@ -143,18 +145,18 @@ func catalogSecretsTask() (powerUserTask, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
task := func(results *sbom.Artifacts, src *source.Source) error {
|
||||
task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
|
||||
resolver, err := src.FileResolver(appConfig.Secrets.Cataloger.ScopeOpt)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := secretsCataloger.Catalog(resolver)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
results.Secrets = result
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return task, nil
|
||||
@@ -171,18 +173,18 @@ func catalogFileClassificationsTask() (powerUserTask, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
task := func(results *sbom.Artifacts, src *source.Source) error {
|
||||
task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
|
||||
resolver, err := src.FileResolver(appConfig.FileClassification.Cataloger.ScopeOpt)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := classifierCataloger.Catalog(resolver)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
results.FileClassifications = result
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return task, nil
|
||||
@@ -198,18 +200,18 @@ func catalogContentsTask() (powerUserTask, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
task := func(results *sbom.Artifacts, src *source.Source) error {
|
||||
task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
|
||||
resolver, err := src.FileResolver(appConfig.FileContents.Cataloger.ScopeOpt)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := contentsCataloger.Catalog(resolver)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
results.FileContents = result
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return task, nil
|
||||
|
||||
@@ -24,8 +24,9 @@ require (
|
||||
github.com/gookit/color v1.2.7
|
||||
github.com/hashicorp/go-multierror v1.1.0
|
||||
github.com/hashicorp/go-version v1.2.0
|
||||
github.com/jinzhu/copier v0.3.2
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/hashstructure v1.1.0
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/mitchellh/mapstructure v1.3.1
|
||||
github.com/olekukonko/tablewriter v0.0.4
|
||||
github.com/pelletier/go-toml v1.8.1
|
||||
|
||||
@@ -454,6 +454,8 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
|
||||
github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s=
|
||||
github.com/jinzhu/copier v0.3.2 h1:QdBOCbaouLDYaIPFfi1bKv5F5tPpeTwXe4sD0jqtz5w=
|
||||
github.com/jinzhu/copier v0.3.2/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=
|
||||
github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0=
|
||||
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
@@ -544,8 +546,8 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
|
||||
github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0=
|
||||
github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
|
||||
@@ -6,19 +6,24 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
|
||||
"github.com/anchore/client-go/pkg/external"
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/event"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/antihax/optional"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
"github.com/wagoodman/go-progress"
|
||||
)
|
||||
|
||||
type ImportConfig struct {
|
||||
ImageMetadata image.Metadata
|
||||
SBOM sbom.SBOM
|
||||
SourceMetadata source.Metadata
|
||||
Catalog *pkg.Catalog
|
||||
Distro *distro.Distro
|
||||
Dockerfile []byte
|
||||
OverwriteExistingUpload bool
|
||||
Timeout uint
|
||||
@@ -68,19 +73,19 @@ func (c *Client) Import(ctx context.Context, cfg ImportConfig) error {
|
||||
prog.N++
|
||||
sessionID := startOperation.Uuid
|
||||
|
||||
packageDigest, err := importPackageSBOM(authedCtx, c.client.ImportsApi, sessionID, cfg.SBOM, stage)
|
||||
packageDigest, err := importPackageSBOM(authedCtx, c.client.ImportsApi, sessionID, cfg.SourceMetadata, cfg.Catalog, cfg.Distro, stage)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to import Package SBOM: %w", err)
|
||||
}
|
||||
prog.N++
|
||||
|
||||
manifestDigest, err := importManifest(authedCtx, c.client.ImportsApi, sessionID, cfg.SBOM.Source.ImageMetadata.RawManifest, stage)
|
||||
manifestDigest, err := importManifest(authedCtx, c.client.ImportsApi, sessionID, cfg.ImageMetadata.RawManifest, stage)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to import Manifest: %w", err)
|
||||
}
|
||||
prog.N++
|
||||
|
||||
configDigest, err := importConfig(authedCtx, c.client.ImportsApi, sessionID, cfg.SBOM.Source.ImageMetadata.RawConfig, stage)
|
||||
configDigest, err := importConfig(authedCtx, c.client.ImportsApi, sessionID, cfg.ImageMetadata.RawConfig, stage)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to import Config: %w", err)
|
||||
}
|
||||
|
||||
@@ -14,17 +14,30 @@ import (
|
||||
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
|
||||
"github.com/anchore/client-go/pkg/external"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
type packageSBOMImportAPI interface {
|
||||
ImportImagePackages(context.Context, string, external.ImagePackageManifest) (external.ImageImportContentResponse, *http.Response, error)
|
||||
}
|
||||
|
||||
func packageSbomModel(s sbom.SBOM) (*external.ImagePackageManifest, error) {
|
||||
func packageSbomModel(srcMetadata source.Metadata, catalog *pkg.Catalog, d *distro.Distro) (*external.ImagePackageManifest, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
// TODO: once the top-level API is refactored and SBOMs are the unit of work, then this function will be passed an SBOM and there would be no more need to create an SBOM object here.
|
||||
s := sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
PackageCatalog: catalog,
|
||||
Distro: d,
|
||||
},
|
||||
Source: srcMetadata,
|
||||
}
|
||||
|
||||
err := syftjson.Format().Presenter(s).Present(&buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to serialize results: %w", err)
|
||||
@@ -39,11 +52,11 @@ func packageSbomModel(s sbom.SBOM) (*external.ImagePackageManifest, error) {
|
||||
return &model, nil
|
||||
}
|
||||
|
||||
func importPackageSBOM(ctx context.Context, api packageSBOMImportAPI, sessionID string, s sbom.SBOM, stage *progress.Stage) (string, error) {
|
||||
func importPackageSBOM(ctx context.Context, api packageSBOMImportAPI, sessionID string, s source.Metadata, catalog *pkg.Catalog, d *distro.Distro, stage *progress.Stage) (string, error) {
|
||||
log.Debug("importing package SBOM")
|
||||
stage.Current = "package SBOM"
|
||||
|
||||
model, err := packageSbomModel(s)
|
||||
model, err := packageSbomModel(s, catalog, d)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to create PackageSBOM model: %w", err)
|
||||
}
|
||||
|
||||
@@ -74,15 +74,7 @@ func TestPackageSbomToModel(t *testing.T) {
|
||||
|
||||
c := pkg.NewCatalog(p)
|
||||
|
||||
sbomResult := sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
PackageCatalog: c,
|
||||
Distro: &d,
|
||||
},
|
||||
Source: m,
|
||||
}
|
||||
|
||||
model, err := packageSbomModel(sbomResult)
|
||||
model, err := packageSbomModel(m, c, &d)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate model from source material: %+v", err)
|
||||
}
|
||||
@@ -205,15 +197,7 @@ func TestPackageSbomImport(t *testing.T) {
|
||||
|
||||
d, _ := distro.NewDistro(distro.CentOS, "8.0", "")
|
||||
|
||||
sbomResult := sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
PackageCatalog: catalog,
|
||||
Distro: &d,
|
||||
},
|
||||
Source: m,
|
||||
}
|
||||
|
||||
theModel, err := packageSbomModel(sbomResult)
|
||||
theModel, err := packageSbomModel(m, catalog, &d)
|
||||
if err != nil {
|
||||
t.Fatalf("could not get sbom model: %+v", err)
|
||||
}
|
||||
@@ -252,7 +236,7 @@ func TestPackageSbomImport(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
digest, err := importPackageSBOM(context.TODO(), test.api, sessionID, sbomResult, &progress.Stage{})
|
||||
digest, err := importPackageSBOM(context.TODO(), test.api, sessionID, m, catalog, &d, &progress.Stage{})
|
||||
|
||||
// validate error handling
|
||||
if err != nil && !test.expectsError {
|
||||
|
||||
@@ -2,7 +2,7 @@ package spdxhelpers
|
||||
|
||||
import "github.com/anchore/syft/syft/pkg"
|
||||
|
||||
func Description(p *pkg.Package) string {
|
||||
func Description(p pkg.Package) string {
|
||||
if hasMetadata(p) {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkMetadata:
|
||||
@@ -14,10 +14,6 @@ func Description(p *pkg.Package) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func packageExists(p *pkg.Package) bool {
|
||||
return p != nil
|
||||
}
|
||||
|
||||
func hasMetadata(p *pkg.Package) bool {
|
||||
return packageExists(p) && p.Metadata != nil
|
||||
func hasMetadata(p pkg.Package) bool {
|
||||
return p.Metadata != nil
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func Test_Description(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, Description(&test.input))
|
||||
assert.Equal(t, test.expected, Description(test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func DocumentName(srcMetadata source.Metadata) string {
|
||||
switch srcMetadata.Scheme {
|
||||
case source.ImageScheme:
|
||||
return cleanSPDXName(srcMetadata.ImageMetadata.UserInput)
|
||||
case source.DirectoryScheme:
|
||||
return cleanSPDXName(srcMetadata.Path)
|
||||
}
|
||||
|
||||
// TODO: is this alright?
|
||||
return uuid.Must(uuid.NewRandom()).String()
|
||||
}
|
||||
|
||||
func cleanSPDXName(name string) string {
|
||||
// remove # according to specification
|
||||
name = strings.ReplaceAll(name, "#", "-")
|
||||
|
||||
// remove : for url construction
|
||||
name = strings.ReplaceAll(name, ":", "-")
|
||||
|
||||
// clean relative pathing
|
||||
return path.Clean(name)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const SyftDocumentNamespace = "https://anchore.com/syft"
|
||||
|
||||
func DocumentNamespace(name string, srcMetadata source.Metadata) string {
|
||||
input := "unknown-source-type"
|
||||
switch srcMetadata.Scheme {
|
||||
case source.ImageScheme:
|
||||
input = "image"
|
||||
case source.DirectoryScheme:
|
||||
input = "dir"
|
||||
}
|
||||
|
||||
uniqueID := uuid.Must(uuid.NewRandom())
|
||||
identifier := path.Join(input, uniqueID.String())
|
||||
if name != "." {
|
||||
identifier = path.Join(input, fmt.Sprintf("%s-%s", name, uniqueID.String()))
|
||||
}
|
||||
|
||||
return path.Join(SyftDocumentNamespace, identifier)
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package spdxhelpers
|
||||
|
||||
import "github.com/anchore/syft/syft/pkg"
|
||||
|
||||
func DownloadLocation(p *pkg.Package) string {
|
||||
func DownloadLocation(p pkg.Package) string {
|
||||
// 3.7: Package Download Location
|
||||
// Cardinality: mandatory, one
|
||||
// NONE if there is no download location whatsoever.
|
||||
|
||||
@@ -48,7 +48,7 @@ func Test_DownloadLocation(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, DownloadLocation(&test.input))
|
||||
assert.Equal(t, test.expected, DownloadLocation(test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,9 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func ExternalRefs(p *pkg.Package) (externalRefs []model.ExternalRef) {
|
||||
func ExternalRefs(p pkg.Package) (externalRefs []model.ExternalRef) {
|
||||
externalRefs = make([]model.ExternalRef, 0)
|
||||
|
||||
if !packageExists(p) {
|
||||
return externalRefs
|
||||
}
|
||||
|
||||
for _, c := range p.CPEs {
|
||||
externalRefs = append(externalRefs, model.ExternalRef{
|
||||
ReferenceCategory: model.SecurityReferenceCategory,
|
||||
|
||||
@@ -39,7 +39,7 @@ func Test_ExternalRefs(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.ElementsMatch(t, test.expected, ExternalRefs(&test.input))
|
||||
assert.ElementsMatch(t, test.expected, ExternalRefs(test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func Files(packageSpdxID string, p *pkg.Package) (files []model.File, fileIDs []string, relationships []model.Relationship) {
|
||||
func Files(packageSpdxID string, p pkg.Package) (files []model.File, fileIDs []string, relationships []model.Relationship) {
|
||||
files = make([]model.File, 0)
|
||||
fileIDs = make([]string, 0)
|
||||
relationships = make([]model.Relationship, 0)
|
||||
|
||||
@@ -2,7 +2,7 @@ package spdxhelpers
|
||||
|
||||
import "github.com/anchore/syft/syft/pkg"
|
||||
|
||||
func Homepage(p *pkg.Package) string {
|
||||
func Homepage(p pkg.Package) string {
|
||||
if hasMetadata(p) {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.GemMetadata:
|
||||
|
||||
@@ -50,7 +50,7 @@ func Test_Homepage(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, Homepage(&test.input))
|
||||
assert.Equal(t, test.expected, Homepage(test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func License(p *pkg.Package) string {
|
||||
func License(p pkg.Package) string {
|
||||
// source: https://spdx.github.io/spdx-spec/3-package-information/#313-concluded-license
|
||||
// The options to populate this field are limited to:
|
||||
// A valid SPDX License Expression as defined in Appendix IV;
|
||||
@@ -17,7 +17,7 @@ func License(p *pkg.Package) string {
|
||||
// (ii) the SPDX file creator has made no attempt to determine this field; or
|
||||
// (iii) the SPDX file creator has intentionally provided no information (no meaning should be implied by doing so).
|
||||
|
||||
if !packageExists(p) || len(p.Licenses) == 0 {
|
||||
if len(p.Licenses) == 0 {
|
||||
return "NONE"
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ func Test_License(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, License(&test.input))
|
||||
assert.Equal(t, test.expected, License(test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ func Test_Originator(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, Originator(&test.input))
|
||||
assert.Equal(t, test.expected, Originator(test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func Originator(p *pkg.Package) string {
|
||||
func Originator(p pkg.Package) string {
|
||||
if hasMetadata(p) {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkMetadata:
|
||||
|
||||
@@ -6,11 +6,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func SourceInfo(p *pkg.Package) string {
|
||||
if !packageExists(p) {
|
||||
return ""
|
||||
}
|
||||
|
||||
func SourceInfo(p pkg.Package) string {
|
||||
answer := ""
|
||||
switch p.Type {
|
||||
case pkg.RpmPkg:
|
||||
@@ -31,8 +27,6 @@ func SourceInfo(p *pkg.Package) string {
|
||||
answer = "acquired package info from go module information"
|
||||
case pkg.RustPkg:
|
||||
answer = "acquired package info from rust cargo manifest"
|
||||
case pkg.PhpComposerPkg:
|
||||
answer = "acquired package info from PHP composer manifest"
|
||||
default:
|
||||
answer = "acquired package info from the following paths"
|
||||
}
|
||||
|
||||
@@ -124,14 +124,6 @@ func Test_SourceInfo(t *testing.T) {
|
||||
"from rust cargo manifest",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.PhpComposerPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from PHP composer manifest",
|
||||
},
|
||||
},
|
||||
}
|
||||
var pkgTypes []pkg.Type
|
||||
for _, test := range tests {
|
||||
@@ -139,7 +131,7 @@ func Test_SourceInfo(t *testing.T) {
|
||||
if test.input.Type != "" {
|
||||
pkgTypes = append(pkgTypes, test.input.Type)
|
||||
}
|
||||
actual := SourceInfo(&test.input)
|
||||
actual := SourceInfo(test.input)
|
||||
for _, expected := range test.expected {
|
||||
assert.Contains(t, actual, expected)
|
||||
}
|
||||
|
||||
@@ -133,7 +133,6 @@ func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) {
|
||||
|
||||
// populate catalog with test data
|
||||
catalog.Add(pkg.Package{
|
||||
ID: "package-1-id",
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
Locations: []source.Location{
|
||||
@@ -154,7 +153,6 @@ func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) {
|
||||
},
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
ID: "package-2-id",
|
||||
Name: "package-2",
|
||||
Version: "2.0.1",
|
||||
Locations: []source.Location{
|
||||
@@ -197,7 +195,6 @@ func newDirectoryCatalog() *pkg.Catalog {
|
||||
|
||||
// populate catalog with test data
|
||||
catalog.Add(pkg.Package{
|
||||
ID: "package-1-id",
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
Type: pkg.PythonPkg,
|
||||
@@ -223,7 +220,6 @@ func newDirectoryCatalog() *pkg.Catalog {
|
||||
},
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
ID: "package-2-id",
|
||||
Name: "package-2",
|
||||
Version: "2.0.1",
|
||||
Type: pkg.DebPkg,
|
||||
|
||||
@@ -33,7 +33,7 @@ func toFormatModel(s sbom.SBOM) model.Document {
|
||||
return doc
|
||||
}
|
||||
|
||||
func toComponent(p *pkg.Package) model.Component {
|
||||
func toComponent(p pkg.Package) model.Component {
|
||||
return model.Component{
|
||||
Type: "library", // TODO: this is not accurate
|
||||
Name: p.Name,
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package spdx22json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/spdx22json/model"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
func decoder(reader io.Reader) (*sbom.SBOM, error) {
|
||||
dec := json.NewDecoder(reader)
|
||||
|
||||
var doc model.Document
|
||||
err := dec.Decode(&doc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode syft-json: %w", err)
|
||||
}
|
||||
|
||||
return toSyftModel(doc)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package spdx22json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/common/testutils"
|
||||
"github.com/go-test/deep"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEncodeDecodeCycle(t *testing.T) {
|
||||
testImage := "image-simple"
|
||||
originalSBOM := testutils.ImageInput(t, testImage)
|
||||
|
||||
var buf bytes.Buffer
|
||||
assert.NoError(t, encoder(&buf, originalSBOM))
|
||||
|
||||
actualSBOM, err := decoder(bytes.NewReader(buf.Bytes()))
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, d := range deep.Equal(originalSBOM.Source, actualSBOM.Source) {
|
||||
t.Errorf("metadata difference: %+v", d)
|
||||
}
|
||||
|
||||
actualPackages := actualSBOM.Artifacts.PackageCatalog.Sorted()
|
||||
for idx, p := range originalSBOM.Artifacts.PackageCatalog.Sorted() {
|
||||
if !assert.Equal(t, p.Name, actualPackages[idx].Name) {
|
||||
t.Errorf("different package at idx=%d: %s vs %s", idx, p.Name, actualPackages[idx].Name)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, d := range deep.Equal(p, actualPackages[idx]) {
|
||||
if strings.Contains(d, ".VirtualPath: ") {
|
||||
// location.Virtual path is not exposed in the json output
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(d, "<nil slice> != []") {
|
||||
// semantically the same
|
||||
continue
|
||||
}
|
||||
t.Errorf("package difference (%s): %+v", p.Name, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,6 @@ import (
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
const anchoreNamespace = "https://anchore.com/syft"
|
||||
|
||||
func encoder(output io.Writer, s sbom.SBOM) error {
|
||||
doc := toFormatModel(s)
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@ package spdx22json
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
@@ -14,13 +12,11 @@ import (
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
"github.com/anchore/syft/internal/version"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// toFormatModel creates and populates a new JSON document struct that follows the SPDX 2.2 spec from the given cataloging results.
|
||||
func toFormatModel(s sbom.SBOM) model.Document {
|
||||
name := documentName(s.Source)
|
||||
name := spdxhelpers.DocumentName(s.Source)
|
||||
packages, files, relationships := extractFromCatalog(s.Artifacts.PackageCatalog)
|
||||
|
||||
return model.Document{
|
||||
@@ -39,43 +35,13 @@ func toFormatModel(s sbom.SBOM) model.Document {
|
||||
LicenseListVersion: spdxlicense.Version,
|
||||
},
|
||||
DataLicense: "CC0-1.0",
|
||||
DocumentNamespace: documentNamespace(name, s.Source),
|
||||
DocumentNamespace: spdxhelpers.DocumentNamespace(name, s.Source),
|
||||
Packages: packages,
|
||||
Files: files,
|
||||
Relationships: relationships,
|
||||
}
|
||||
}
|
||||
|
||||
func documentName(srcMetadata source.Metadata) string {
|
||||
switch srcMetadata.Scheme {
|
||||
case source.ImageScheme:
|
||||
return cleanSPDXName(srcMetadata.ImageMetadata.UserInput)
|
||||
case source.DirectoryScheme:
|
||||
return cleanSPDXName(srcMetadata.Path)
|
||||
}
|
||||
|
||||
// TODO: is this alright?
|
||||
return uuid.Must(uuid.NewRandom()).String()
|
||||
}
|
||||
|
||||
func documentNamespace(name string, srcMetadata source.Metadata) string {
|
||||
input := "unknown-source-type"
|
||||
switch srcMetadata.Scheme {
|
||||
case source.ImageScheme:
|
||||
input = "image"
|
||||
case source.DirectoryScheme:
|
||||
input = "dir"
|
||||
}
|
||||
|
||||
uniqueID := uuid.Must(uuid.NewRandom())
|
||||
identifier := path.Join(input, uniqueID.String())
|
||||
if name != "." {
|
||||
identifier = path.Join(input, fmt.Sprintf("%s-%s", name, uniqueID.String()))
|
||||
}
|
||||
|
||||
return path.Join(anchoreNamespace, identifier)
|
||||
}
|
||||
|
||||
func extractFromCatalog(catalog *pkg.Catalog) ([]model.Package, []model.File, []model.Relationship) {
|
||||
packages := make([]model.Package, 0)
|
||||
relationships := make([]model.Relationship, 0)
|
||||
@@ -115,14 +81,3 @@ func extractFromCatalog(catalog *pkg.Catalog) ([]model.Package, []model.File, []
|
||||
|
||||
return packages, files, relationships
|
||||
}
|
||||
|
||||
func cleanSPDXName(name string) string {
|
||||
// remove # according to specification
|
||||
name = strings.ReplaceAll(name, "#", "-")
|
||||
|
||||
// remove : for url construction
|
||||
name = strings.ReplaceAll(name, ":", "-")
|
||||
|
||||
// clean relative pathing
|
||||
return path.Clean(name)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package spdx22json
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/internal/formats/spdx22json/model"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
func toSyftModel(doc model.Document) (*sbom.SBOM, error) {
|
||||
|
||||
}
|
||||
@@ -256,7 +256,7 @@ func toFormatPackages(catalog *pkg.Catalog) map[spdx.ElementID]*spdx.Package2_2
|
||||
return results
|
||||
}
|
||||
|
||||
func formatSPDXExternalRefs(p *pkg.Package) (refs []*spdx.PackageExternalReference2_2) {
|
||||
func formatSPDXExternalRefs(p pkg.Package) (refs []*spdx.PackageExternalReference2_2) {
|
||||
for _, ref := range spdxhelpers.ExternalRefs(p) {
|
||||
refs = append(refs, &spdx.PackageExternalReference2_2{
|
||||
Category: string(ref.ReferenceCategory),
|
||||
|
||||
@@ -31,11 +31,7 @@ func TestEncodeDecodeCycle(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
|
||||
// ids will never be equal
|
||||
p.ID = ""
|
||||
actualPackages[idx].ID = ""
|
||||
|
||||
for _, d := range deep.Equal(*p, *actualPackages[idx]) {
|
||||
for _, d := range deep.Equal(p, actualPackages[idx]) {
|
||||
if strings.Contains(d, ".VirtualPath: ") {
|
||||
// location.Virtual path is not exposed in the json output
|
||||
continue
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "package-1-id",
|
||||
"id": "cbf4f3077fc7deee",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "python",
|
||||
@@ -36,7 +36,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "package-2-id",
|
||||
"id": "1a39aadd9705c2b9",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "package-1-id",
|
||||
"id": "d1d433485a31ed07",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "python",
|
||||
@@ -32,7 +32,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "package-2-id",
|
||||
"id": "2db629ca48fa6786",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
|
||||
@@ -3,6 +3,8 @@ package syftjson
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
@@ -23,7 +25,7 @@ func ToFormatModel(s sbom.SBOM, applicationConfig interface{}) model.Document {
|
||||
|
||||
return model.Document{
|
||||
Artifacts: toPackageModels(s.Artifacts.PackageCatalog),
|
||||
ArtifactRelationships: toRelationshipModel(pkg.NewRelationships(s.Artifacts.PackageCatalog)),
|
||||
ArtifactRelationships: toRelationshipModel(s.Relationships),
|
||||
Source: src,
|
||||
Distro: toDistroModel(s.Artifacts.Distro),
|
||||
Descriptor: model.Descriptor{
|
||||
@@ -50,7 +52,7 @@ func toPackageModels(catalog *pkg.Catalog) []model.Package {
|
||||
}
|
||||
|
||||
// toPackageModel crates a new Package from the given pkg.Package.
|
||||
func toPackageModel(p *pkg.Package) model.Package {
|
||||
func toPackageModel(p pkg.Package) model.Package {
|
||||
var cpes = make([]string, len(p.CPEs))
|
||||
for i, c := range p.CPEs {
|
||||
cpes[i] = c.BindToFmtString()
|
||||
@@ -69,7 +71,7 @@ func toPackageModel(p *pkg.Package) model.Package {
|
||||
|
||||
return model.Package{
|
||||
PackageBasicData: model.PackageBasicData{
|
||||
ID: string(p.ID),
|
||||
ID: string(p.ID()),
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
Type: p.Type,
|
||||
@@ -87,14 +89,14 @@ func toPackageModel(p *pkg.Package) model.Package {
|
||||
}
|
||||
}
|
||||
|
||||
func toRelationshipModel(relationships []pkg.Relationship) []model.Relationship {
|
||||
func toRelationshipModel(relationships []artifact.Relationship) []model.Relationship {
|
||||
result := make([]model.Relationship, len(relationships))
|
||||
for i, r := range relationships {
|
||||
result[i] = model.Relationship{
|
||||
Parent: string(r.Parent),
|
||||
Child: string(r.Child),
|
||||
Parent: string(r.From.ID()),
|
||||
Child: string(r.To.ID()),
|
||||
Type: string(r.Type),
|
||||
Metadata: r.Metadata,
|
||||
Metadata: r.Data,
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -61,7 +61,6 @@ func toSyftPackage(p model.Package) pkg.Package {
|
||||
}
|
||||
|
||||
return pkg.Package{
|
||||
ID: pkg.ID(p.ID),
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
FoundBy: p.FoundBy,
|
||||
|
||||
@@ -33,7 +33,6 @@ func TestJSONPresenter(t *testing.T) {
|
||||
catalog := pkg.NewCatalog()
|
||||
|
||||
catalog.Add(pkg.Package{
|
||||
ID: "package-1-id",
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
Locations: []source.Location{
|
||||
@@ -57,7 +56,6 @@ func TestJSONPresenter(t *testing.T) {
|
||||
},
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
ID: "package-2-id",
|
||||
Name: "package-2",
|
||||
Version: "2.0.1",
|
||||
Locations: []source.Location{
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
],
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "package-1-id",
|
||||
"id": "b84dfe0eb2c5670f",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "python",
|
||||
@@ -102,7 +102,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "package-2-id",
|
||||
"id": "6619226d6979963f",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
// This file was generated by robots at 2021-11-15 14:32:22.081911 -0500 EST m=+0.159302107
|
||||
// This file was generated by robots at 2021-09-29 23:42:29.616023 -0400 EDT m=+0.184689484
|
||||
// using data from https://spdx.org/licenses/licenses.json
|
||||
package spdxlicense
|
||||
|
||||
const Version = "3.15"
|
||||
const Version = "3.14"
|
||||
|
||||
var licenseIDs = map[string]string{
|
||||
"0bsd": "0BSD",
|
||||
@@ -367,12 +367,6 @@ var licenseIDs = map[string]string{
|
||||
"cnri-jython": "CNRI-Jython",
|
||||
"cnri-python": "CNRI-Python",
|
||||
"cnri-python-gpl-compatible": "CNRI-Python-GPL-Compatible",
|
||||
"coil-1": "COIL-1.0",
|
||||
"coil-1.0": "COIL-1.0",
|
||||
"coil-1.0.0": "COIL-1.0",
|
||||
"community-spec-1": "Community-Spec-1.0",
|
||||
"community-spec-1.0": "Community-Spec-1.0",
|
||||
"community-spec-1.0.0": "Community-Spec-1.0",
|
||||
"condor-1": "Condor-1.1",
|
||||
"condor-1.1": "Condor-1.1",
|
||||
"condor-1.1.0": "Condor-1.1",
|
||||
@@ -446,7 +440,6 @@ var licenseIDs = map[string]string{
|
||||
"eupl-1.2.0": "EUPL-1.2",
|
||||
"eurosym": "Eurosym",
|
||||
"fair": "Fair",
|
||||
"fdk-aac": "FDK-AAC",
|
||||
"frameworx-1": "Frameworx-1.0",
|
||||
"frameworx-1.0": "Frameworx-1.0",
|
||||
"frameworx-1.0.0": "Frameworx-1.0",
|
||||
@@ -658,7 +651,6 @@ var licenseIDs = map[string]string{
|
||||
"liliq-rplus-1": "LiLiQ-Rplus-1.1",
|
||||
"liliq-rplus-1.1": "LiLiQ-Rplus-1.1",
|
||||
"liliq-rplus-1.1.0": "LiLiQ-Rplus-1.1",
|
||||
"linux-man-pages-copyleft": "Linux-man-pages-copyleft",
|
||||
"linux-openib": "Linux-OpenIB",
|
||||
"lpl-1": "LPL-1.0",
|
||||
"lpl-1.0": "LPL-1.0",
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
(registered 2021-11-05, last updated 2021-11-05)
|
||||
|
||||
Media type name: application
|
||||
Media subtype name: vnd.syft+json
|
||||
|
||||
Required parameters: N/A
|
||||
|
||||
Optional parameters:
|
||||
version
|
||||
|
||||
The version parameter refers to the Syft specification version in use.
|
||||
|
||||
version = 1*DIGIT "." 1*DIGIT "." 1*DIGIT
|
||||
|
||||
Encoding considerations: binary
|
||||
This media type has all of the same encoding considerations of
|
||||
application/json as described in [RFC8259]
|
||||
|
||||
|
||||
Security considerations:
|
||||
This media type has all of the same security
|
||||
considerations of application/json as described in [RFC8259].
|
||||
|
||||
Depending on the operational context of the device or software being described by the SBOM there may be additional security requirements. These may include but are not limited to, encryption at rest, encryption in transit, and restrictions on the transmission of the SBOM to 3rd parties. These additional requirements are considered out of scope for the specification. They will typically be enforced by contract or copyright terms.
|
||||
|
||||
|
||||
Interoperability considerations:
|
||||
This media type has the same interoperability considerations of application/json as described in [RFC8259].
|
||||
|
||||
Published specification:
|
||||
The specification can be found on the main Syft GitHub repository under the schema directory https://github.com/anchore/syft/blob/main/schema/json
|
||||
|
||||
Applications which use this media:
|
||||
This media type is used to specify a software bill of materials.
|
||||
It will be used by tools that produce SBOMs either during the software build process or as a result of software composition analysis.
|
||||
|
||||
It will also be used by tools that consume SBOMs for software
|
||||
supply chain, component, supplier, license, and vulnerability
|
||||
analysis.
|
||||
|
||||
Fragment identifier considerations:
|
||||
N/A
|
||||
|
||||
Restrictions on usage:
|
||||
N/A
|
||||
|
||||
Provisional registration? (standards tree only):
|
||||
N/A
|
||||
|
||||
Additional information:
|
||||
|
||||
1. Deprecated alias names for this type: N/A
|
||||
2. Magic number(s): N/A
|
||||
3. File extension(s): .syft.json
|
||||
4. Macintosh file type code: N/A
|
||||
5. Object Identifiers: N/A
|
||||
|
||||
General Comments:
|
||||
|
||||
|
||||
Person to contact for further information:
|
||||
|
||||
1. Name: Dan Luhring
|
||||
2. Email: dan.luhring@anchore.com
|
||||
|
||||
Intended usage: Common
|
||||
The Syft SBOM format is an open-source software bill of materials specification. It is intended to be exchanged between different parties of the software supply chain.
|
||||
|
||||
|
||||
Author/Change controller: Dan Luhring, on behalf of Anchore
|
||||
@@ -0,0 +1,26 @@
|
||||
package artifact
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/hashstructure/v2"
|
||||
)
|
||||
|
||||
// ID represents a unique value for each package added to a package catalog.
|
||||
type ID string
|
||||
|
||||
type Identifiable interface {
|
||||
ID() ID
|
||||
}
|
||||
|
||||
func IDFromHash(obj interface{}) (ID, error) {
|
||||
f, err := hashstructure.Hash(obj, hashstructure.FormatV2, &hashstructure.HashOptions{
|
||||
ZeroNil: true,
|
||||
SlicesAsSets: true,
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not build ID for object=%+v: %+v", obj, err)
|
||||
}
|
||||
|
||||
return ID(fmt.Sprintf("%x", f)), nil
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package artifact
|
||||
|
||||
const (
|
||||
// OwnershipByFileOverlapRelationship indicates that the parent package claims ownership of a child package since
|
||||
// the parent metadata indicates overlap with a location that a cataloger found the child package by. This is
|
||||
// by definition a package-to-package relationship and is created only after all package cataloging has been completed.
|
||||
OwnershipByFileOverlapRelationship RelationshipType = "ownership-by-file-overlap"
|
||||
)
|
||||
|
||||
type RelationshipType string
|
||||
|
||||
type Relationship struct {
|
||||
From Identifiable `json:"from"`
|
||||
To Identifiable `json:"to"`
|
||||
Type RelationshipType `json:"type"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
@@ -123,11 +123,6 @@ func TestIdentifyDistro(t *testing.T) {
|
||||
Type: Mariner,
|
||||
Version: "1.0.0",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/rockylinux",
|
||||
Type: RockyLinux,
|
||||
Version: "8.4.0",
|
||||
},
|
||||
}
|
||||
|
||||
observedDistros := internal.NewStringSet()
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
NAME="Rocky Linux"
|
||||
VERSION="8.4 (Green Obsidian)"
|
||||
ID="rocky"
|
||||
ID_LIKE="rhel fedora"
|
||||
VERSION_ID="8.4"
|
||||
PLATFORM_ID="platform:el8"
|
||||
PRETTY_NAME="Rocky Linux 8.4 (Green Obsidian)"
|
||||
ANSI_COLOR="0;32"
|
||||
CPE_NAME="cpe:/o:rocky:rocky:8.4:GA"
|
||||
HOME_URL="https://rockylinux.org/"
|
||||
BUG_REPORT_URL="https://bugs.rockylinux.org/"
|
||||
ROCKY_SUPPORT_PRODUCT="Rocky Linux"
|
||||
ROCKY_SUPPORT_PRODUCT_VERSION="8"
|
||||
@@ -21,7 +21,6 @@ const (
|
||||
Photon Type = "photon"
|
||||
Windows Type = "windows"
|
||||
Mariner Type = "mariner"
|
||||
RockyLinux Type = "rockylinux"
|
||||
)
|
||||
|
||||
// All contains all Linux distribution options
|
||||
@@ -41,7 +40,6 @@ var All = []Type{
|
||||
Photon,
|
||||
Windows,
|
||||
Mariner,
|
||||
RockyLinux,
|
||||
}
|
||||
|
||||
// IDMapping connects a distro ID like "ubuntu" to a Distro type
|
||||
@@ -61,7 +59,6 @@ var IDMapping = map[string]Type{
|
||||
"photon": Photon,
|
||||
"windows": Windows,
|
||||
"mariner": Mariner,
|
||||
"rocky": RockyLinux,
|
||||
}
|
||||
|
||||
// String returns the string representation of the given Linux distribution.
|
||||
|
||||
+8
-6
@@ -19,6 +19,8 @@ package syft
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
@@ -32,10 +34,10 @@ import (
|
||||
// CatalogPackages takes an inventory of packages from the given image from a particular perspective
|
||||
// (e.g. squashed source, all-layers source). Returns the discovered set of packages, the identified Linux
|
||||
// distribution, and the source object used to wrap the data source.
|
||||
func CatalogPackages(src *source.Source, scope source.Scope) (*pkg.Catalog, *distro.Distro, error) {
|
||||
func CatalogPackages(src *source.Source, scope source.Scope) (*pkg.Catalog, []artifact.Relationship, *distro.Distro, error) {
|
||||
resolver, err := src.FileResolver(scope)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to determine resolver while cataloging packages: %w", err)
|
||||
return nil, nil, nil, fmt.Errorf("unable to determine resolver while cataloging packages: %w", err)
|
||||
}
|
||||
|
||||
// find the distro
|
||||
@@ -59,15 +61,15 @@ func CatalogPackages(src *source.Source, scope source.Scope) (*pkg.Catalog, *dis
|
||||
log.Info("cataloging directory")
|
||||
catalogers = cataloger.DirectoryCatalogers()
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unable to determine cataloger set from scheme=%+v", src.Metadata.Scheme)
|
||||
return nil, nil, nil, fmt.Errorf("unable to determine cataloger set from scheme=%+v", src.Metadata.Scheme)
|
||||
}
|
||||
|
||||
catalog, err := cataloger.Catalog(resolver, theDistro, catalogers...)
|
||||
catalog, relationships, err := cataloger.Catalog(resolver, theDistro, catalogers...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return catalog, theDistro, nil
|
||||
return catalog, relationships, theDistro, nil
|
||||
}
|
||||
|
||||
// SetLogger sets the logger object used for all syft logging calls.
|
||||
|
||||
+31
-66
@@ -4,6 +4,9 @@ import (
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/jinzhu/copier"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
@@ -11,18 +14,18 @@ import (
|
||||
|
||||
// Catalog represents a collection of Packages.
|
||||
type Catalog struct {
|
||||
byID map[ID]*Package
|
||||
idsByType map[Type][]ID
|
||||
idsByPath map[string][]ID // note: this is real path or virtual path
|
||||
byID map[artifact.ID]Package
|
||||
idsByType map[Type][]artifact.ID
|
||||
idsByPath map[string][]artifact.ID // note: this is real path or virtual path
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// NewCatalog returns a new empty Catalog
|
||||
func NewCatalog(pkgs ...Package) *Catalog {
|
||||
catalog := Catalog{
|
||||
byID: make(map[ID]*Package),
|
||||
idsByType: make(map[Type][]ID),
|
||||
idsByPath: make(map[string][]ID),
|
||||
byID: make(map[artifact.ID]Package),
|
||||
idsByType: make(map[Type][]artifact.ID),
|
||||
idsByPath: make(map[string][]artifact.ID),
|
||||
}
|
||||
|
||||
for _, p := range pkgs {
|
||||
@@ -38,21 +41,26 @@ func (c *Catalog) PackageCount() int {
|
||||
}
|
||||
|
||||
// Package returns the package with the given ID.
|
||||
func (c *Catalog) Package(id ID) *Package {
|
||||
func (c *Catalog) Package(id artifact.ID) *Package {
|
||||
v, exists := c.byID[id]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
return v
|
||||
var p Package
|
||||
if err := copier.Copy(&p, &v); err != nil {
|
||||
log.Warnf("unable to copy package id=%q name=%q: %+v", id, v.Name, err)
|
||||
return nil
|
||||
}
|
||||
return &p
|
||||
}
|
||||
|
||||
// PackagesByPath returns all packages that were discovered from the given path.
|
||||
func (c *Catalog) PackagesByPath(path string) []*Package {
|
||||
func (c *Catalog) PackagesByPath(path string) []Package {
|
||||
return c.Packages(c.idsByPath[path])
|
||||
}
|
||||
|
||||
// Packages returns all packages for the given ID.
|
||||
func (c *Catalog) Packages(ids []ID) (result []*Package) {
|
||||
func (c *Catalog) Packages(ids []artifact.ID) (result []Package) {
|
||||
for _, i := range ids {
|
||||
p, exists := c.byID[i]
|
||||
if exists {
|
||||
@@ -67,68 +75,32 @@ func (c *Catalog) Add(p Package) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if p.ID == "" {
|
||||
fingerprint, err := p.Fingerprint()
|
||||
if err != nil {
|
||||
log.Warnf("failed to add package to catalog: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
p.ID = ID(fingerprint)
|
||||
}
|
||||
// note: since we are capturing the ID, we cannot modify the package being added from this point forward
|
||||
id := p.ID()
|
||||
|
||||
// store by package ID
|
||||
c.byID[p.ID] = &p
|
||||
c.byID[id] = p
|
||||
|
||||
// store by package type
|
||||
c.idsByType[p.Type] = append(c.idsByType[p.Type], p.ID)
|
||||
c.idsByType[p.Type] = append(c.idsByType[p.Type], id)
|
||||
|
||||
// store by file location paths
|
||||
observedPaths := internal.NewStringSet()
|
||||
for _, l := range p.Locations {
|
||||
if l.RealPath != "" && !observedPaths.Contains(l.RealPath) {
|
||||
c.idsByPath[l.RealPath] = append(c.idsByPath[l.RealPath], p.ID)
|
||||
c.idsByPath[l.RealPath] = append(c.idsByPath[l.RealPath], id)
|
||||
observedPaths.Add(l.RealPath)
|
||||
}
|
||||
if l.VirtualPath != "" && l.RealPath != l.VirtualPath && !observedPaths.Contains(l.VirtualPath) {
|
||||
c.idsByPath[l.VirtualPath] = append(c.idsByPath[l.VirtualPath], p.ID)
|
||||
c.idsByPath[l.VirtualPath] = append(c.idsByPath[l.VirtualPath], id)
|
||||
observedPaths.Add(l.VirtualPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Catalog) Remove(id ID) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
_, exists := c.byID[id]
|
||||
if !exists {
|
||||
log.Errorf("package ID does not exist in the catalog : id=%+v", id)
|
||||
return
|
||||
}
|
||||
|
||||
// Remove all index references to this package ID
|
||||
for t, ids := range c.idsByType {
|
||||
c.idsByType[t] = removeID(id, ids)
|
||||
if len(c.idsByType[t]) == 0 {
|
||||
delete(c.idsByType, t)
|
||||
}
|
||||
}
|
||||
|
||||
for p, ids := range c.idsByPath {
|
||||
c.idsByPath[p] = removeID(id, ids)
|
||||
if len(c.idsByPath[p]) == 0 {
|
||||
delete(c.idsByPath, p)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove package
|
||||
delete(c.byID, id)
|
||||
}
|
||||
|
||||
// Enumerate all packages for the given type(s), enumerating all packages if no type is specified.
|
||||
func (c *Catalog) Enumerate(types ...Type) <-chan *Package {
|
||||
channel := make(chan *Package)
|
||||
func (c *Catalog) Enumerate(types ...Type) <-chan Package {
|
||||
channel := make(chan Package)
|
||||
go func() {
|
||||
defer close(channel)
|
||||
for ty, ids := range c.idsByType {
|
||||
@@ -146,7 +118,10 @@ func (c *Catalog) Enumerate(types ...Type) <-chan *Package {
|
||||
}
|
||||
}
|
||||
for _, id := range ids {
|
||||
channel <- c.Package(id)
|
||||
p := c.Package(id)
|
||||
if p != nil {
|
||||
channel <- *p
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -155,8 +130,7 @@ func (c *Catalog) Enumerate(types ...Type) <-chan *Package {
|
||||
|
||||
// Sorted enumerates all packages for the given types sorted by package name. Enumerates all packages if no type
|
||||
// is specified.
|
||||
func (c *Catalog) Sorted(types ...Type) []*Package {
|
||||
pkgs := make([]*Package, 0)
|
||||
func (c *Catalog) Sorted(types ...Type) (pkgs []Package) {
|
||||
for p := range c.Enumerate(types...) {
|
||||
pkgs = append(pkgs, p)
|
||||
}
|
||||
@@ -176,12 +150,3 @@ func (c *Catalog) Sorted(types ...Type) []*Package {
|
||||
|
||||
return pkgs
|
||||
}
|
||||
|
||||
func removeID(id ID, target []ID) (result []ID) {
|
||||
for _, value := range target {
|
||||
if value != id {
|
||||
result = append(result, value)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
+42
-80
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
var catalogAddAndRemoveTestPkgs = []Package{
|
||||
{
|
||||
ID: "my-id",
|
||||
Locations: []source.Location{
|
||||
{
|
||||
RealPath: "/a/path",
|
||||
@@ -24,7 +23,6 @@ var catalogAddAndRemoveTestPkgs = []Package{
|
||||
Type: RpmPkg,
|
||||
},
|
||||
{
|
||||
ID: "my-other-id",
|
||||
Locations: []source.Location{
|
||||
{
|
||||
RealPath: "/c/path",
|
||||
@@ -45,6 +43,11 @@ type expectedIndexes struct {
|
||||
}
|
||||
|
||||
func TestCatalogAddPopulatesIndex(t *testing.T) {
|
||||
|
||||
fixtureID := func(i int) string {
|
||||
return string(catalogAddAndRemoveTestPkgs[i].ID())
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pkgs []Package
|
||||
@@ -55,16 +58,16 @@ func TestCatalogAddPopulatesIndex(t *testing.T) {
|
||||
pkgs: catalogAddAndRemoveTestPkgs,
|
||||
expectedIndexes: expectedIndexes{
|
||||
byType: map[Type]*strset.Set{
|
||||
RpmPkg: strset.New("my-id"),
|
||||
NpmPkg: strset.New("my-other-id"),
|
||||
RpmPkg: strset.New(fixtureID(0)),
|
||||
NpmPkg: strset.New(fixtureID(1)),
|
||||
},
|
||||
byPath: map[string]*strset.Set{
|
||||
"/another/path": strset.New("my-id", "my-other-id"),
|
||||
"/a/path": strset.New("my-id"),
|
||||
"/b/path": strset.New("my-id"),
|
||||
"/bee/path": strset.New("my-id"),
|
||||
"/c/path": strset.New("my-other-id"),
|
||||
"/d/path": strset.New("my-other-id"),
|
||||
"/another/path": strset.New(fixtureID(0), fixtureID(1)),
|
||||
"/a/path": strset.New(fixtureID(0)),
|
||||
"/b/path": strset.New(fixtureID(0)),
|
||||
"/bee/path": strset.New(fixtureID(0)),
|
||||
"/c/path": strset.New(fixtureID(1)),
|
||||
"/d/path": strset.New(fixtureID(1)),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -80,50 +83,6 @@ func TestCatalogAddPopulatesIndex(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCatalogRemove(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pkgs []Package
|
||||
removeId ID
|
||||
expectedIndexes expectedIndexes
|
||||
}{
|
||||
{
|
||||
name: "vanilla-add",
|
||||
removeId: "my-other-id",
|
||||
pkgs: catalogAddAndRemoveTestPkgs,
|
||||
expectedIndexes: expectedIndexes{
|
||||
byType: map[Type]*strset.Set{
|
||||
RpmPkg: strset.New("my-id"),
|
||||
},
|
||||
byPath: map[string]*strset.Set{
|
||||
"/another/path": strset.New("my-id"),
|
||||
"/a/path": strset.New("my-id"),
|
||||
"/b/path": strset.New("my-id"),
|
||||
"/bee/path": strset.New("my-id"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
c := NewCatalog(test.pkgs...)
|
||||
c.Remove(test.removeId)
|
||||
|
||||
assertIndexes(t, c, test.expectedIndexes)
|
||||
|
||||
if c.Package(test.removeId) != nil {
|
||||
t.Errorf("expected package to be removed, but was found!")
|
||||
}
|
||||
|
||||
if c.PackageCount() != len(test.pkgs)-1 {
|
||||
t.Errorf("expected count to be affected but was not")
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertIndexes(t *testing.T, c *Catalog, expectedIndexes expectedIndexes) {
|
||||
// assert path index
|
||||
if len(c.idsByPath) != len(expectedIndexes.byPath) {
|
||||
@@ -132,7 +91,7 @@ func assertIndexes(t *testing.T, c *Catalog, expectedIndexes expectedIndexes) {
|
||||
for path, expectedIds := range expectedIndexes.byPath {
|
||||
actualIds := strset.New()
|
||||
for _, p := range c.PackagesByPath(path) {
|
||||
actualIds.Add(string(p.ID))
|
||||
actualIds.Add(string(p.ID()))
|
||||
}
|
||||
|
||||
if !expectedIds.IsEqual(actualIds) {
|
||||
@@ -147,7 +106,7 @@ func assertIndexes(t *testing.T, c *Catalog, expectedIndexes expectedIndexes) {
|
||||
for ty, expectedIds := range expectedIndexes.byType {
|
||||
actualIds := strset.New()
|
||||
for p := range c.Enumerate(ty) {
|
||||
actualIds.Add(string(p.ID))
|
||||
actualIds.Add(string(p.ID()))
|
||||
}
|
||||
|
||||
if !expectedIds.IsEqual(actualIds) {
|
||||
@@ -157,39 +116,42 @@ func assertIndexes(t *testing.T, c *Catalog, expectedIndexes expectedIndexes) {
|
||||
}
|
||||
|
||||
func TestCatalog_PathIndexDeduplicatesRealVsVirtualPaths(t *testing.T) {
|
||||
p1 := Package{
|
||||
Locations: []source.Location{
|
||||
{
|
||||
RealPath: "/b/path",
|
||||
VirtualPath: "/another/path",
|
||||
},
|
||||
{
|
||||
RealPath: "/b/path",
|
||||
VirtualPath: "/b/path",
|
||||
},
|
||||
},
|
||||
Type: RpmPkg,
|
||||
Name: "Package-1",
|
||||
}
|
||||
|
||||
p2 := Package{
|
||||
Locations: []source.Location{
|
||||
{
|
||||
RealPath: "/b/path",
|
||||
VirtualPath: "/b/path",
|
||||
},
|
||||
},
|
||||
Type: RpmPkg,
|
||||
Name: "Package-2",
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
pkg Package
|
||||
}{
|
||||
{
|
||||
name: "multiple locations with shared path",
|
||||
pkg: Package{
|
||||
ID: "my-id",
|
||||
Locations: []source.Location{
|
||||
{
|
||||
RealPath: "/b/path",
|
||||
VirtualPath: "/another/path",
|
||||
},
|
||||
{
|
||||
RealPath: "/b/path",
|
||||
VirtualPath: "/b/path",
|
||||
},
|
||||
},
|
||||
Type: RpmPkg,
|
||||
},
|
||||
pkg: p1,
|
||||
},
|
||||
{
|
||||
name: "one location with shared path",
|
||||
pkg: Package{
|
||||
ID: "my-id",
|
||||
Locations: []source.Location{
|
||||
{
|
||||
RealPath: "/b/path",
|
||||
VirtualPath: "/b/path",
|
||||
},
|
||||
},
|
||||
Type: RpmPkg,
|
||||
},
|
||||
pkg: p2,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
@@ -21,7 +23,7 @@ var _ common.ParserFn = parseApkDB
|
||||
|
||||
// parseApkDb parses individual packages from a given Alpine DB file. For more information on specific fields
|
||||
// see https://wiki.alpinelinux.org/wiki/Apk_spec .
|
||||
func parseApkDB(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
func parseApkDB(_ string, reader io.Reader) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
// larger capacity for the scanner.
|
||||
const maxScannerCapacity = 1024 * 1024
|
||||
// a new larger buffer for the scanner
|
||||
@@ -47,7 +49,7 @@ func parseApkDB(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
for scanner.Scan() {
|
||||
metadata, err := parseApkDBEntry(strings.NewReader(scanner.Text()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
if metadata != nil {
|
||||
packages = append(packages, pkg.Package{
|
||||
@@ -62,10 +64,10 @@ func parseApkDB(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse APK DB file: %w", err)
|
||||
return nil, nil, fmt.Errorf("failed to parse APK DB file: %w", err)
|
||||
}
|
||||
|
||||
return packages, nil
|
||||
return packages, nil, nil
|
||||
}
|
||||
|
||||
// nolint:funlen
|
||||
|
||||
@@ -775,7 +775,8 @@ func TestMultiplePackages(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
pkgs, err := parseApkDB(file.Name(), file)
|
||||
// TODO: no relationships are under test yet
|
||||
pkgs, _, err := parseApkDB(file.Name(), file)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to read file contents: ", err)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package cataloger
|
||||
import (
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/event"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -38,8 +39,9 @@ func newMonitor() (*progress.Manual, *progress.Manual) {
|
||||
// In order to efficiently retrieve contents from a underlying container image the content fetch requests are
|
||||
// done in bulk. Specifically, all files of interest are collected from each catalogers and accumulated into a single
|
||||
// request.
|
||||
func Catalog(resolver source.FileResolver, theDistro *distro.Distro, catalogers ...Cataloger) (*pkg.Catalog, error) {
|
||||
func Catalog(resolver source.FileResolver, theDistro *distro.Distro, catalogers ...Cataloger) (*pkg.Catalog, []artifact.Relationship, error) {
|
||||
catalog := pkg.NewCatalog()
|
||||
var allRelationships []artifact.Relationship
|
||||
|
||||
filesProcessed, packagesDiscovered := newMonitor()
|
||||
|
||||
@@ -47,7 +49,7 @@ func Catalog(resolver source.FileResolver, theDistro *distro.Distro, catalogers
|
||||
var errs error
|
||||
for _, theCataloger := range catalogers {
|
||||
// find packages from the underlying raw data
|
||||
packages, err := theCataloger.Catalog(resolver)
|
||||
packages, relationships, err := theCataloger.Catalog(resolver)
|
||||
if err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
continue
|
||||
@@ -68,14 +70,18 @@ func Catalog(resolver source.FileResolver, theDistro *distro.Distro, catalogers
|
||||
// add to catalog
|
||||
catalog.Add(p)
|
||||
}
|
||||
|
||||
allRelationships = append(allRelationships, relationships...)
|
||||
}
|
||||
|
||||
allRelationships = append(allRelationships, pkg.NewRelationships(catalog)...)
|
||||
|
||||
if errs != nil {
|
||||
return nil, errs
|
||||
return nil, nil, errs
|
||||
}
|
||||
|
||||
filesProcessed.SetCompleted()
|
||||
packagesDiscovered.SetCompleted()
|
||||
|
||||
return catalog, nil
|
||||
return catalog, allRelationships, nil
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@ catalogers defined in child packages as well as the interface definition to impl
|
||||
package cataloger
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/apkdb"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/deb"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/java"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/javascript"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/php"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/python"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/rpmdb"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/ruby"
|
||||
@@ -27,7 +27,7 @@ type Cataloger interface {
|
||||
// Name returns a string that uniquely describes a cataloger
|
||||
Name() string
|
||||
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing the catalog source.
|
||||
Catalog(resolver source.FileResolver) ([]pkg.Package, error)
|
||||
Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error)
|
||||
}
|
||||
|
||||
// ImageCatalogers returns a slice of locally implemented catalogers that are fit for detecting installations of packages.
|
||||
@@ -50,7 +50,6 @@ func DirectoryCatalogers() []Cataloger {
|
||||
ruby.NewGemFileLockCataloger(),
|
||||
python.NewPythonIndexCataloger(),
|
||||
python.NewPythonPackageCataloger(),
|
||||
php.NewPHPIndexCataloger(),
|
||||
javascript.NewJavascriptLockCataloger(),
|
||||
deb.NewDpkgdbCataloger(),
|
||||
rpmdb.NewRpmdbCataloger(),
|
||||
|
||||
@@ -83,7 +83,7 @@ func candidateVendors(p pkg.Package) []string {
|
||||
// allow * as a candidate. Note: do NOT allow Java packages to have * vendors.
|
||||
switch p.Language {
|
||||
case pkg.Ruby, pkg.JavaScript:
|
||||
vendors.addValue("*")
|
||||
vendors.addValue(wfn.Any)
|
||||
}
|
||||
|
||||
switch p.MetadataType {
|
||||
|
||||
@@ -637,7 +637,7 @@ func TestCandidateProducts(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("%+v %+v", test.p, test.expected), func(t *testing.T) {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.ElementsMatch(t, test.expected, candidateProducts(test.p))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ 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/pkg"
|
||||
@@ -35,18 +37,18 @@ func (c *GenericCataloger) Name() string {
|
||||
}
|
||||
|
||||
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing the catalog source.
|
||||
func (c *GenericCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, error) {
|
||||
func (c *GenericCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var packages []pkg.Package
|
||||
parserByLocation := c.selectFiles(resolver)
|
||||
var relationships []artifact.Relationship
|
||||
|
||||
for location, parser := range parserByLocation {
|
||||
for location, parser := range c.selectFiles(resolver) {
|
||||
contentReader, err := resolver.FileContentsByLocation(location)
|
||||
if err != nil {
|
||||
// TODO: fail or log?
|
||||
return nil, fmt.Errorf("unable to fetch contents for location=%v : %w", location, err)
|
||||
return nil, nil, fmt.Errorf("unable to fetch contents for location=%v : %w", location, err)
|
||||
}
|
||||
|
||||
entries, err := parser(location.RealPath, contentReader)
|
||||
discoveredPackages, discoveredRelationships, err := parser(location.RealPath, contentReader)
|
||||
internal.CloseAndLogError(contentReader, location.VirtualPath)
|
||||
if err != nil {
|
||||
// TODO: should we fail? or only log?
|
||||
@@ -54,14 +56,16 @@ func (c *GenericCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package,
|
||||
continue
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
entry.FoundBy = c.upstreamCataloger
|
||||
entry.Locations = []source.Location{location}
|
||||
for _, p := range discoveredPackages {
|
||||
p.FoundBy = c.upstreamCataloger
|
||||
p.Locations = append(p.Locations, location)
|
||||
|
||||
packages = append(packages, entry)
|
||||
packages = append(packages, p)
|
||||
}
|
||||
|
||||
relationships = append(relationships, discoveredRelationships...)
|
||||
}
|
||||
return packages, nil
|
||||
return packages, relationships, nil
|
||||
}
|
||||
|
||||
// SelectFiles takes a set of file trees and resolves and file references of interest for future cataloging
|
||||
|
||||
@@ -8,11 +8,12 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func parser(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
func parser(_ string, reader io.Reader) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
contents, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -21,7 +22,7 @@ func parser(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
{
|
||||
Name: string(contents),
|
||||
},
|
||||
}, nil
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
func TestGenericCataloger(t *testing.T) {
|
||||
@@ -47,7 +48,7 @@ func TestGenericCataloger(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
actualPkgs, err := cataloger.Catalog(resolver)
|
||||
actualPkgs, _, err := cataloger.Catalog(resolver)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, actualPkgs, len(expectedPkgs))
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@ package common
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
// ParserFn standardizes a function signature for parser functions that accept the virtual file path (not usable for file reads) and contents and return any discovered packages from that file
|
||||
type ParserFn func(string, io.Reader) ([]pkg.Package, error)
|
||||
type ParserFn func(string, io.Reader) ([]pkg.Package, []artifact.Relationship, error)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"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"
|
||||
)
|
||||
@@ -36,24 +37,23 @@ func (c *Cataloger) Name() string {
|
||||
}
|
||||
|
||||
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing dpkg support files.
|
||||
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, error) {
|
||||
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
dbFileMatches, err := resolver.FilesByGlob(pkg.DpkgDBGlob)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find dpkg status files's by glob: %w", err)
|
||||
return nil, nil, fmt.Errorf("failed to find dpkg status files's by glob: %w", err)
|
||||
}
|
||||
|
||||
var results []pkg.Package
|
||||
var pkgs []pkg.Package
|
||||
var allPackages []pkg.Package
|
||||
for _, dbLocation := range dbFileMatches {
|
||||
dbContents, err := resolver.FileContentsByLocation(dbLocation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pkgs, err = parseDpkgStatus(dbContents)
|
||||
pkgs, err := parseDpkgStatus(dbContents)
|
||||
internal.CloseAndLogError(dbContents, dbLocation.VirtualPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to catalog dpkg package=%+v: %w", dbLocation.RealPath, err)
|
||||
return nil, nil, fmt.Errorf("unable to catalog dpkg package=%+v: %w", dbLocation.RealPath, err)
|
||||
}
|
||||
|
||||
for i := range pkgs {
|
||||
@@ -70,9 +70,9 @@ func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, error)
|
||||
addLicenses(resolver, dbLocation, p)
|
||||
}
|
||||
|
||||
results = append(results, pkgs...)
|
||||
allPackages = append(allPackages, pkgs...)
|
||||
}
|
||||
return results, nil
|
||||
return allPackages, nil, nil
|
||||
}
|
||||
|
||||
func addLicenses(resolver source.FileResolver, dbLocation source.Location, p *pkg.Package) {
|
||||
|
||||
@@ -100,7 +100,7 @@ func TestDpkgCataloger(t *testing.T) {
|
||||
t.Errorf("could not get resolver error: %+v", err)
|
||||
}
|
||||
|
||||
actual, err := c.Catalog(resolver)
|
||||
actual, _, err := c.Catalog(resolver)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to catalog: %+v", err)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ var (
|
||||
// parseDpkgStatus is a parser function for Debian DB status contents, returning all Debian packages listed.
|
||||
func parseDpkgStatus(reader io.Reader) ([]pkg.Package, error) {
|
||||
buffedReader := bufio.NewReader(reader)
|
||||
var packages = make([]pkg.Package, 0)
|
||||
var packages []pkg.Package
|
||||
|
||||
continueProcessing := true
|
||||
for continueProcessing {
|
||||
|
||||
+10
-6
@@ -6,7 +6,10 @@ package golang
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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"
|
||||
)
|
||||
@@ -35,17 +38,18 @@ func (c *Cataloger) Name() string {
|
||||
}
|
||||
|
||||
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing rpm db installation.
|
||||
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, error) {
|
||||
pkgs := make([]pkg.Package, 0)
|
||||
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var pkgs []pkg.Package
|
||||
|
||||
fileMatches, err := resolver.FilesByMIMEType(mimeTypes...)
|
||||
if err != nil {
|
||||
return pkgs, fmt.Errorf("failed to find bin by mime types: %w", err)
|
||||
return pkgs, nil, fmt.Errorf("failed to find bin by mime types: %w", err)
|
||||
}
|
||||
|
||||
for _, location := range fileMatches {
|
||||
r, err := resolver.FileContentsByLocation(location)
|
||||
if err != nil {
|
||||
return pkgs, fmt.Errorf("failed to resolve file contents by location: %w", err)
|
||||
return pkgs, nil, fmt.Errorf("failed to resolve file contents by location: %w", err)
|
||||
}
|
||||
|
||||
goPkgs, err := parseGoBin(location, r)
|
||||
@@ -53,9 +57,9 @@ func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, error)
|
||||
log.Warnf("could not parse possible go binary: %+v", err)
|
||||
}
|
||||
|
||||
r.Close()
|
||||
internal.CloseAndLogError(r, location.RealPath)
|
||||
pkgs = append(pkgs, goPkgs...)
|
||||
}
|
||||
|
||||
return pkgs, nil
|
||||
return pkgs, nil, nil
|
||||
}
|
||||
@@ -23,9 +23,7 @@ func parseGoBin(location source.Location, reader io.ReadCloser) ([]pkg.Package,
|
||||
|
||||
goVersion, mod := findVers(x)
|
||||
|
||||
pkgs := buildGoPkgInfo(location, mod, goVersion)
|
||||
|
||||
return pkgs, nil
|
||||
return buildGoPkgInfo(location, mod, goVersion), nil
|
||||
}
|
||||
|
||||
func buildGoPkgInfo(location source.Location, mod, goVersion string) []pkg.Package {
|
||||
|
||||
@@ -6,22 +6,23 @@ import (
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"golang.org/x/mod/modfile"
|
||||
)
|
||||
|
||||
// parseGoMod takes a go.mod and lists all packages discovered.
|
||||
func parseGoMod(path string, reader io.Reader) ([]pkg.Package, error) {
|
||||
func parseGoMod(path string, reader io.Reader) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
packages := make(map[string]pkg.Package)
|
||||
|
||||
contents, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read go module: %w", err)
|
||||
return nil, nil, fmt.Errorf("failed to read go module: %w", err)
|
||||
}
|
||||
|
||||
file, err := modfile.Parse(path, contents, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse go module: %w", err)
|
||||
return nil, nil, fmt.Errorf("failed to parse go module: %w", err)
|
||||
}
|
||||
|
||||
for _, m := range file.Require {
|
||||
@@ -59,5 +60,5 @@ func parseGoMod(path string, reader io.Reader) ([]pkg.Package, error) {
|
||||
return pkgsSlice[i].Name < pkgsSlice[j].Name
|
||||
})
|
||||
|
||||
return pkgsSlice, nil
|
||||
return pkgsSlice, nil, nil
|
||||
}
|
||||
|
||||
@@ -70,7 +70,8 @@ func TestParseGoMod(t *testing.T) {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
actual, err := parseGoMod(test.fixture, f)
|
||||
// TODO: no relationships are under test yet
|
||||
actual, _, err := parseGoMod(test.fixture, f)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/anchore/syft/internal/log"
|
||||
|
||||
"github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common"
|
||||
)
|
||||
@@ -34,12 +35,12 @@ type archiveParser struct {
|
||||
}
|
||||
|
||||
// parseJavaArchive is a parser function for java archive contents, returning all Java libraries and nested archives.
|
||||
func parseJavaArchive(virtualPath string, reader io.Reader) ([]pkg.Package, error) {
|
||||
func parseJavaArchive(virtualPath string, reader io.Reader) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
parser, cleanupFn, err := newJavaArchiveParser(virtualPath, reader, true)
|
||||
// note: even on error, we should always run cleanup functions
|
||||
defer cleanupFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
return parser.parse()
|
||||
}
|
||||
@@ -80,29 +81,31 @@ func newJavaArchiveParser(virtualPath string, reader io.Reader, detectNested boo
|
||||
}
|
||||
|
||||
// parse the loaded archive and return all packages found.
|
||||
func (j *archiveParser) parse() ([]pkg.Package, error) {
|
||||
var pkgs = make([]pkg.Package, 0)
|
||||
func (j *archiveParser) parse() ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var pkgs []pkg.Package
|
||||
var relationships []artifact.Relationship
|
||||
|
||||
// find the parent package from the java manifest
|
||||
parentPkg, err := j.discoverMainPackage()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not generate package from %s: %w", j.virtualPath, err)
|
||||
return nil, nil, fmt.Errorf("could not generate package from %s: %w", j.virtualPath, err)
|
||||
}
|
||||
|
||||
// find aux packages from pom.properties/pom.xml and potentially modify the existing parentPkg
|
||||
auxPkgs, err := j.discoverPkgsFromAllMavenFiles(parentPkg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
pkgs = append(pkgs, auxPkgs...)
|
||||
|
||||
if j.detectNested {
|
||||
// find nested java archive packages
|
||||
nestedPkgs, err := j.discoverPkgsFromNestedArchives(parentPkg)
|
||||
nestedPkgs, nestedRelationships, err := j.discoverPkgsFromNestedArchives(parentPkg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
pkgs = append(pkgs, nestedPkgs...)
|
||||
relationships = append(relationships, nestedRelationships...)
|
||||
}
|
||||
|
||||
// lastly, add the parent package to the list (assuming the parent exists)
|
||||
@@ -110,7 +113,7 @@ func (j *archiveParser) parse() ([]pkg.Package, error) {
|
||||
pkgs = append([]pkg.Package{*parentPkg}, pkgs...)
|
||||
}
|
||||
|
||||
return pkgs, nil
|
||||
return pkgs, relationships, nil
|
||||
}
|
||||
|
||||
// discoverMainPackage parses the root Java manifest used as the parent package to all discovered nested packages.
|
||||
@@ -189,31 +192,32 @@ func (j *archiveParser) discoverPkgsFromAllMavenFiles(parentPkg *pkg.Package) ([
|
||||
|
||||
// discoverPkgsFromNestedArchives finds Java archives within Java archives, returning all listed Java packages found and
|
||||
// associating each discovered package to the given parent package.
|
||||
func (j *archiveParser) discoverPkgsFromNestedArchives(parentPkg *pkg.Package) ([]pkg.Package, error) {
|
||||
var pkgs = make([]pkg.Package, 0)
|
||||
func (j *archiveParser) discoverPkgsFromNestedArchives(parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var pkgs []pkg.Package
|
||||
var relationships []artifact.Relationship
|
||||
|
||||
// search and parse pom.properties files & fetch the contents
|
||||
openers, err := file.ExtractFromZipToUniqueTempFile(j.archivePath, j.contentPath, j.fileManifest.GlobMatch(archiveFormatGlobs...)...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to extract files from zip: %w", err)
|
||||
return nil, nil, fmt.Errorf("unable to extract files from zip: %w", err)
|
||||
}
|
||||
|
||||
// discover nested artifacts
|
||||
for archivePath, archiveOpener := range openers {
|
||||
archiveReadCloser, err := archiveOpener.Open()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to open archived file from tempdir: %w", err)
|
||||
return nil, nil, fmt.Errorf("unable to open archived file from tempdir: %w", err)
|
||||
}
|
||||
nestedPath := fmt.Sprintf("%s:%s", j.virtualPath, archivePath)
|
||||
nestedPkgs, err := parseJavaArchive(nestedPath, archiveReadCloser)
|
||||
nestedPkgs, nestedRelationships, err := parseJavaArchive(nestedPath, archiveReadCloser)
|
||||
if err != nil {
|
||||
if closeErr := archiveReadCloser.Close(); closeErr != nil {
|
||||
log.Warnf("unable to close archived file from tempdir: %+v", closeErr)
|
||||
}
|
||||
return nil, fmt.Errorf("unable to process nested java archive (%s): %w", archivePath, err)
|
||||
return nil, nil, fmt.Errorf("unable to process nested java archive (%s): %w", archivePath, err)
|
||||
}
|
||||
if err = archiveReadCloser.Close(); err != nil {
|
||||
return nil, fmt.Errorf("unable to close archived file from tempdir: %w", err)
|
||||
return nil, nil, fmt.Errorf("unable to close archived file from tempdir: %w", err)
|
||||
}
|
||||
|
||||
// attach the parent package to all discovered packages that are not already associated with a java archive
|
||||
@@ -226,9 +230,11 @@ func (j *archiveParser) discoverPkgsFromNestedArchives(parentPkg *pkg.Package) (
|
||||
}
|
||||
pkgs = append(pkgs, p)
|
||||
}
|
||||
|
||||
relationships = append(relationships, nestedRelationships...)
|
||||
}
|
||||
|
||||
return pkgs, nil
|
||||
return pkgs, relationships, nil
|
||||
}
|
||||
|
||||
func pomPropertiesByParentPath(archivePath string, extractPaths []string, virtualPath string) (map[string]pkg.PomProperties, error) {
|
||||
|
||||
@@ -242,7 +242,7 @@ func TestParseJar(t *testing.T) {
|
||||
t.Fatalf("should not have filed... %+v", err)
|
||||
}
|
||||
|
||||
actual, err := parser.parse()
|
||||
actual, _, err := parser.parse()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse java archive: %+v", err)
|
||||
}
|
||||
@@ -507,7 +507,7 @@ func TestParseNestedJar(t *testing.T) {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
actual, err := parseJavaArchive(fixture.Name(), fixture)
|
||||
actual, _, err := parseJavaArchive(fixture.Name(), fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse java archive: %+v", err)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common"
|
||||
)
|
||||
@@ -162,8 +163,8 @@ func licensesFromJSON(p PackageJSON) ([]string, error) {
|
||||
}
|
||||
|
||||
// parsePackageJSON parses a package.json and returns the discovered JavaScript packages.
|
||||
func parsePackageJSON(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
packages := make([]pkg.Package, 0)
|
||||
func parsePackageJSON(_ string, reader io.Reader) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var packages []pkg.Package
|
||||
dec := json.NewDecoder(reader)
|
||||
|
||||
for {
|
||||
@@ -171,17 +172,17 @@ func parsePackageJSON(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
if err := dec.Decode(&p); err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse package.json file: %w", err)
|
||||
return nil, nil, fmt.Errorf("failed to parse package.json file: %w", err)
|
||||
}
|
||||
|
||||
if !p.hasNameAndVersionValues() {
|
||||
log.Debug("encountered package.json file without a name and/or version field, ignoring this file")
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
licenses, err := licensesFromJSON(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse package.json file: %w", err)
|
||||
return nil, nil, fmt.Errorf("failed to parse package.json file: %w", err)
|
||||
}
|
||||
|
||||
packages = append(packages, pkg.Package{
|
||||
@@ -200,7 +201,7 @@ func parsePackageJSON(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
})
|
||||
}
|
||||
|
||||
return packages, nil
|
||||
return packages, nil, nil
|
||||
}
|
||||
|
||||
func (p PackageJSON) hasNameAndVersionValues() bool {
|
||||
|
||||
@@ -124,7 +124,7 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
actual, err := parsePackageJSON("", fixture)
|
||||
actual, _, err := parsePackageJSON("", fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse package-lock.json: %+v", err)
|
||||
}
|
||||
@@ -150,7 +150,8 @@ func TestParsePackageJSON_Partial(t *testing.T) { // see https://github.com/anch
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
actual, err := parsePackageJSON("", fixture)
|
||||
// TODO: no relationships are under test yet
|
||||
actual, _, err := parsePackageJSON("", fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse package-lock.json: %+v", err)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common"
|
||||
)
|
||||
@@ -28,11 +29,11 @@ type Dependency struct {
|
||||
}
|
||||
|
||||
// parsePackageLock parses a package-lock.json and returns the discovered JavaScript packages.
|
||||
func parsePackageLock(path string, reader io.Reader) ([]pkg.Package, error) {
|
||||
func parsePackageLock(path string, reader io.Reader) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
// in the case we find package-lock.json files in the node_modules directories, skip those
|
||||
// as the whole purpose of the lock file is for the specific dependencies of the root project
|
||||
if pathContainsNodeModulesDirectory(path) {
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
var packages []pkg.Package
|
||||
@@ -43,7 +44,7 @@ func parsePackageLock(path string, reader io.Reader) ([]pkg.Package, error) {
|
||||
if err := dec.Decode(&lock); err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse package-lock.json file: %w", err)
|
||||
return nil, nil, fmt.Errorf("failed to parse package-lock.json file: %w", err)
|
||||
}
|
||||
for name, pkgMeta := range lock.Dependencies {
|
||||
packages = append(packages, pkg.Package{
|
||||
@@ -55,5 +56,5 @@ func parsePackageLock(path string, reader io.Reader) ([]pkg.Package, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return packages, nil
|
||||
return packages, nil, nil
|
||||
}
|
||||
|
||||
@@ -109,7 +109,8 @@ func TestParsePackageLock(t *testing.T) {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
actual, err := parsePackageLock(fixture.Name(), fixture)
|
||||
// TODO: no relationships are under test yet
|
||||
actual, _, err := parsePackageLock(fixture.Name(), fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse package-lock.json: %+v", err)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"regexp"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common"
|
||||
)
|
||||
@@ -34,11 +35,11 @@ const (
|
||||
noVersion = ""
|
||||
)
|
||||
|
||||
func parseYarnLock(path string, reader io.Reader) ([]pkg.Package, error) {
|
||||
func parseYarnLock(path string, reader io.Reader) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
// in the case we find yarn.lock files in the node_modules directories, skip those
|
||||
// as the whole purpose of the lock file is for the specific dependencies of the project
|
||||
if pathContainsNodeModulesDirectory(path) {
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
var packages []pkg.Package
|
||||
@@ -79,10 +80,10 @@ func parseYarnLock(path string, reader io.Reader) ([]pkg.Package, error) {
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse yarn.lock file: %w", err)
|
||||
return nil, nil, fmt.Errorf("failed to parse yarn.lock file: %w", err)
|
||||
}
|
||||
|
||||
return packages, nil
|
||||
return packages, nil, nil
|
||||
}
|
||||
|
||||
func findPackageName(line string) string {
|
||||
|
||||
@@ -70,7 +70,8 @@ func TestParseYarnLock(t *testing.T) {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
actual, err := parseYarnLock(fixture.Name(), fixture)
|
||||
// TODO: no relationships are under test yet
|
||||
actual, _, err := parseYarnLock(fixture.Name(), fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse yarn.lock: %+v", err)
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
Package python provides a concrete Cataloger implementation for Python ecosystem files (egg, wheel, requirements.txt).
|
||||
*/
|
||||
package php
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common"
|
||||
)
|
||||
|
||||
// NewPythonIndexCataloger returns a new cataloger for python packages referenced from poetry lock files, requirements.txt files, and setup.py files.
|
||||
func NewPHPIndexCataloger() *common.GenericCataloger {
|
||||
globParsers := map[string]common.ParserFn{
|
||||
"**/composer.lock": parseComposerLock,
|
||||
}
|
||||
|
||||
return common.NewGenericCataloger(nil, globParsers, "php-index-cataloger")
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package php
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common"
|
||||
)
|
||||
|
||||
type ComposerLock struct {
|
||||
Packages []Dependency `json:"packages"`
|
||||
PackageDev []Dependency `json:"packages-dev"`
|
||||
}
|
||||
|
||||
type Dependency struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// integrity check
|
||||
var _ common.ParserFn = parseComposerLock
|
||||
|
||||
// parseComposerLock is a parser function for Composer.lock contents, returning "Default" php packages discovered.
|
||||
func parseComposerLock(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
packages := make([]pkg.Package, 0)
|
||||
dec := json.NewDecoder(reader)
|
||||
|
||||
for {
|
||||
var lock ComposerLock
|
||||
if err := dec.Decode(&lock); err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse composer.lock file: %w", err)
|
||||
}
|
||||
for _, pkgMeta := range lock.Packages {
|
||||
version := pkgMeta.Version
|
||||
name := pkgMeta.Name
|
||||
packages = append(packages, pkg.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Language: pkg.PHP,
|
||||
Type: pkg.PhpComposerPkg,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return packages, nil
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package php
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/go-test/deep"
|
||||
)
|
||||
|
||||
func TestParseComposerFileLock(t *testing.T) {
|
||||
expected := []pkg.Package{
|
||||
{
|
||||
Name: "adoy/fastcgi-client",
|
||||
Version: "1.0.2",
|
||||
Language: pkg.PHP,
|
||||
Type: pkg.PhpComposerPkg,
|
||||
},
|
||||
{
|
||||
Name: "alcaeus/mongo-php-adapter",
|
||||
Version: "1.1.11",
|
||||
Language: pkg.PHP,
|
||||
Type: pkg.PhpComposerPkg,
|
||||
},
|
||||
}
|
||||
fixture, err := os.Open("test-fixtures/composer.lock")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
actual, err := parseComposerLock(fixture.Name(), fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse requirements: %+v", err)
|
||||
}
|
||||
differences := deep.Equal(expected, actual)
|
||||
if differences != nil {
|
||||
t.Errorf("returned package list differed from expectation: %+v", differences)
|
||||
}
|
||||
|
||||
}
|
||||
-298
@@ -1,298 +0,0 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "746ba78c06aef0cf954135ea909f9eb9",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adoy/fastcgi-client",
|
||||
"version": "1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/adoy/PHP-FastCGI-Client.git",
|
||||
"reference": "6d9a552f0206a1db7feb442824540aa6c55e5b27"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/adoy/PHP-FastCGI-Client/zipball/6d9a552f0206a1db7feb442824540aa6c55e5b27",
|
||||
"reference": "6d9a552f0206a1db7feb442824540aa6c55e5b27",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Adoy\\FastCGI\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Pierrick Charron",
|
||||
"email": "pierrick@adoy.net"
|
||||
}
|
||||
],
|
||||
"description": "Lightweight, single file FastCGI client for PHP.",
|
||||
"keywords": [
|
||||
"fastcgi",
|
||||
"fcgi"
|
||||
],
|
||||
"time": "2019-12-11T13:49:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "alcaeus/mongo-php-adapter",
|
||||
"version": "1.1.11",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/alcaeus/mongo-php-adapter.git",
|
||||
"reference": "43b6add94c8b4cb9890d662cba4c0defde733dcf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/alcaeus/mongo-php-adapter/zipball/43b6add94c8b4cb9890d662cba4c0defde733dcf",
|
||||
"reference": "43b6add94c8b4cb9890d662cba4c0defde733dcf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-ctype": "*",
|
||||
"ext-hash": "*",
|
||||
"ext-mongodb": "^1.2.0",
|
||||
"mongodb/mongodb": "^1.0.1",
|
||||
"php": "^5.6 || ^7.0"
|
||||
},
|
||||
"provide": {
|
||||
"ext-mongo": "1.6.14"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.7.27 || ^6.0 || ^7.0",
|
||||
"squizlabs/php_codesniffer": "^3.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Mongo": "lib/Mongo"
|
||||
},
|
||||
"psr-4": {
|
||||
"Alcaeus\\MongoDbAdapter\\": "lib/Alcaeus/MongoDbAdapter"
|
||||
},
|
||||
"files": [
|
||||
"lib/Mongo/functions.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "alcaeus",
|
||||
"email": "alcaeus@alcaeus.org"
|
||||
},
|
||||
{
|
||||
"name": "Olivier Lechevalier",
|
||||
"email": "olivier.lechevalier@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Adapter to provide ext-mongo interface on top of mongo-php-libary",
|
||||
"keywords": [
|
||||
"database",
|
||||
"mongodb"
|
||||
],
|
||||
"time": "2019-11-11T20:47:32+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "behat/gherkin",
|
||||
"version": "v4.6.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Behat/Gherkin.git",
|
||||
"reference": "51ac4500c4dc30cbaaabcd2f25694299df666a31"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Behat/Gherkin/zipball/51ac4500c4dc30cbaaabcd2f25694299df666a31",
|
||||
"reference": "51ac4500c4dc30cbaaabcd2f25694299df666a31",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.5|~5",
|
||||
"symfony/phpunit-bridge": "~2.7|~3|~4",
|
||||
"symfony/yaml": "~2.3|~3|~4"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/yaml": "If you want to parse features, represented in YAML files"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.4-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Behat\\Gherkin": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Konstantin Kudryashov",
|
||||
"email": "ever.zet@gmail.com",
|
||||
"homepage": "http://everzet.com"
|
||||
}
|
||||
],
|
||||
"description": "Gherkin DSL parser for PHP 5.3",
|
||||
"homepage": "http://behat.org/",
|
||||
"keywords": [
|
||||
"BDD",
|
||||
"Behat",
|
||||
"Cucumber",
|
||||
"DSL",
|
||||
"gherkin",
|
||||
"parser"
|
||||
],
|
||||
"time": "2020-03-17T14:03:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "codeception/codeception",
|
||||
"version": "4.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Codeception/Codeception.git",
|
||||
"reference": "5515b6a6c6f1e1c909aaff2e5f3a15c177dfd1a9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/5515b6a6c6f1e1c909aaff2e5f3a15c177dfd1a9",
|
||||
"reference": "5515b6a6c6f1e1c909aaff2e5f3a15c177dfd1a9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"behat/gherkin": "^4.4.0",
|
||||
"codeception/lib-asserts": "^1.0",
|
||||
"codeception/phpunit-wrapper": ">6.0.15 <6.1.0 | ^6.6.1 | ^7.7.1 | ^8.1.1 | ^9.0",
|
||||
"codeception/stub": "^2.0 | ^3.0",
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"guzzlehttp/psr7": "~1.4",
|
||||
"php": ">=5.6.0 <8.0",
|
||||
"symfony/console": ">=2.7 <6.0",
|
||||
"symfony/css-selector": ">=2.7 <6.0",
|
||||
"symfony/event-dispatcher": ">=2.7 <6.0",
|
||||
"symfony/finder": ">=2.7 <6.0",
|
||||
"symfony/yaml": ">=2.7 <6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/module-asserts": "*@dev",
|
||||
"codeception/module-cli": "*@dev",
|
||||
"codeception/module-db": "*@dev",
|
||||
"codeception/module-filesystem": "*@dev",
|
||||
"codeception/module-phpbrowser": "*@dev",
|
||||
"codeception/specify": "~0.3",
|
||||
"codeception/util-universalframework": "*@dev",
|
||||
"monolog/monolog": "~1.8",
|
||||
"squizlabs/php_codesniffer": "~2.0",
|
||||
"symfony/process": ">=2.7 <6.0",
|
||||
"vlucas/phpdotenv": "^2.0 | ^3.0 | ^4.0"
|
||||
},
|
||||
"suggest": {
|
||||
"codeception/specify": "BDD-style code blocks",
|
||||
"codeception/verify": "BDD-style assertions",
|
||||
"hoa/console": "For interactive console functionality",
|
||||
"stecman/symfony-console-completion": "For BASH autocompletion",
|
||||
"symfony/phpunit-bridge": "For phpunit-bridge support"
|
||||
},
|
||||
"bin": [
|
||||
"codecept"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": []
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Codeception\\": "src/Codeception",
|
||||
"Codeception\\Extension\\": "ext"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michael Bodnarchuk",
|
||||
"email": "davert@mail.ua",
|
||||
"homepage": "http://codegyre.com"
|
||||
}
|
||||
],
|
||||
"description": "BDD-style testing framework",
|
||||
"homepage": "http://codeception.com/",
|
||||
"keywords": [
|
||||
"BDD",
|
||||
"TDD",
|
||||
"acceptance testing",
|
||||
"functional testing",
|
||||
"unit testing"
|
||||
],
|
||||
"time": "2020-06-07T16:31:51+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {
|
||||
"intelligence/bt-base": 20,
|
||||
"intelligence/bt-common": 20,
|
||||
"intelligence/bt-lib-tools": 20,
|
||||
"intelligence/ci-lib-cache": 20,
|
||||
"intelligence/ci-lib-client": 20,
|
||||
"intelligence/ci-lib-contributed": 20,
|
||||
"intelligence/ci-lib-eav": 20,
|
||||
"intelligence/ci-lib-report": 20,
|
||||
"intelligence/git-leaks-elastic-client": 20,
|
||||
"intelligence/phishing-elastic-client": 20,
|
||||
"libs/gib-sso-client": 20,
|
||||
"mozhin/phplib": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": "^7.4.0",
|
||||
"ext-amqp": "^1.9",
|
||||
"ext-ctype": "*",
|
||||
"ext-curl": "^7.4",
|
||||
"ext-date": "^7.4",
|
||||
"ext-fileinfo": "*",
|
||||
"ext-geoip": "^1.1",
|
||||
"ext-gettext": "*",
|
||||
"ext-iconv": "*",
|
||||
"ext-imagick": "^3.4",
|
||||
"ext-imap": "^7.4",
|
||||
"ext-intl": "^7.4",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "^7.4",
|
||||
"ext-mongodb": "^1.4",
|
||||
"ext-mysqli": "^7.4",
|
||||
"ext-pdo_mysql": "^7.4",
|
||||
"ext-redis": ">=3.1"
|
||||
},
|
||||
"platform-dev": []
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
@@ -31,13 +32,13 @@ func (c *PackageCataloger) Name() string {
|
||||
}
|
||||
|
||||
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing python egg and wheel installations.
|
||||
func (c *PackageCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, error) {
|
||||
func (c *PackageCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var fileMatches []source.Location
|
||||
|
||||
for _, glob := range []string{eggMetadataGlob, wheelMetadataGlob, eggFileMetadataGlob} {
|
||||
matches, err := resolver.FilesByGlob(glob)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find files by glob: %s", glob)
|
||||
return nil, nil, fmt.Errorf("failed to find files by glob: %s", glob)
|
||||
}
|
||||
fileMatches = append(fileMatches, matches...)
|
||||
}
|
||||
@@ -46,13 +47,13 @@ func (c *PackageCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package,
|
||||
for _, location := range fileMatches {
|
||||
p, err := c.catalogEggOrWheel(resolver, location)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to catalog python package=%+v: %w", location.RealPath, err)
|
||||
return nil, nil, fmt.Errorf("unable to catalog python package=%+v: %w", location.RealPath, err)
|
||||
}
|
||||
if p != nil {
|
||||
pkgs = append(pkgs, *p)
|
||||
}
|
||||
}
|
||||
return pkgs, nil
|
||||
return pkgs, nil, nil
|
||||
}
|
||||
|
||||
// catalogEggOrWheel takes the primary metadata file reference and returns the python package it represents.
|
||||
|
||||
@@ -144,7 +144,7 @@ func TestPythonPackageWheelCataloger(t *testing.T) {
|
||||
|
||||
test.expectedPackage.Locations = locations
|
||||
|
||||
actual, err := NewPythonPackageCataloger().Catalog(resolver)
|
||||
actual, _, err := NewPythonPackageCataloger().Catalog(resolver)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to catalog python package: %+v", err)
|
||||
}
|
||||
@@ -173,7 +173,7 @@ func TestIgnorePackage(t *testing.T) {
|
||||
t.Run(test.MetadataFixture, func(t *testing.T) {
|
||||
resolver := source.NewMockResolverForPaths(test.MetadataFixture)
|
||||
|
||||
actual, err := NewPythonPackageCataloger().Catalog(resolver)
|
||||
actual, _, err := NewPythonPackageCataloger().Catalog(resolver)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to catalog python package: %+v", err)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common"
|
||||
)
|
||||
@@ -37,7 +38,7 @@ type Dependency struct {
|
||||
var _ common.ParserFn = parsePipfileLock
|
||||
|
||||
// parsePipfileLock is a parser function for Pipfile.lock contents, returning "Default" python packages discovered.
|
||||
func parsePipfileLock(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
func parsePipfileLock(_ string, reader io.Reader) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
packages := make([]pkg.Package, 0)
|
||||
dec := json.NewDecoder(reader)
|
||||
|
||||
@@ -46,7 +47,7 @@ func parsePipfileLock(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
if err := dec.Decode(&lock); err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Pipfile.lock file: %w", err)
|
||||
return nil, nil, fmt.Errorf("failed to parse Pipfile.lock file: %w", err)
|
||||
}
|
||||
for name, pkgMeta := range lock.Default {
|
||||
version := strings.TrimPrefix(pkgMeta.Version, "==")
|
||||
@@ -59,5 +60,5 @@ func parsePipfileLock(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return packages, nil
|
||||
return packages, nil, nil
|
||||
}
|
||||
|
||||
@@ -39,7 +39,8 @@ func TestParsePipFileLock(t *testing.T) {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
actual, err := parsePipfileLock(fixture.Name(), fixture)
|
||||
// TODO: no relationships are under test yet
|
||||
actual, _, err := parsePipfileLock(fixture.Name(), fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse requirements: %+v", err)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common"
|
||||
"github.com/pelletier/go-toml"
|
||||
@@ -13,17 +14,17 @@ import (
|
||||
var _ common.ParserFn = parsePoetryLock
|
||||
|
||||
// parsePoetryLock is a parser function for poetry.lock contents, returning all python packages discovered.
|
||||
func parsePoetryLock(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
func parsePoetryLock(_ string, reader io.Reader) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
tree, err := toml.LoadReader(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load poetry.lock for parsing: %v", err)
|
||||
return nil, nil, fmt.Errorf("unable to load poetry.lock for parsing: %v", err)
|
||||
}
|
||||
|
||||
metadata := PoetryMetadata{}
|
||||
err = tree.Unmarshal(&metadata)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse poetry.lock: %v", err)
|
||||
return nil, nil, fmt.Errorf("unable to parse poetry.lock: %v", err)
|
||||
}
|
||||
|
||||
return metadata.Pkgs(), nil
|
||||
return metadata.Pkgs(), nil, nil
|
||||
}
|
||||
|
||||
@@ -45,7 +45,8 @@ func TestParsePoetryLock(t *testing.T) {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
actual, err := parsePoetryLock(fixture.Name(), fixture)
|
||||
// TODO: no relationships are under test yet
|
||||
actual, _, err := parsePoetryLock(fixture.Name(), fixture)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common"
|
||||
)
|
||||
@@ -15,7 +16,7 @@ var _ common.ParserFn = parseRequirementsTxt
|
||||
|
||||
// parseRequirementsTxt takes a Python requirements.txt file, returning all Python packages that are locked to a
|
||||
// specific version.
|
||||
func parseRequirementsTxt(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
func parseRequirementsTxt(_ string, reader io.Reader) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
packages := make([]pkg.Package, 0)
|
||||
|
||||
scanner := bufio.NewScanner(reader)
|
||||
@@ -55,10 +56,10 @@ func parseRequirementsTxt(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse python requirements file: %w", err)
|
||||
return nil, nil, fmt.Errorf("failed to parse python requirements file: %w", err)
|
||||
}
|
||||
|
||||
return packages, nil
|
||||
return packages, nil, nil
|
||||
}
|
||||
|
||||
// removeTrailingComment takes a requirements.txt line and strips off comment strings.
|
||||
|
||||
@@ -50,7 +50,8 @@ func TestParseRequirementsTxt(t *testing.T) {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
actual, err := parseRequirementsTxt(fixture.Name(), fixture)
|
||||
// TODO: no relationships are under test yet
|
||||
actual, _, err := parseRequirementsTxt(fixture.Name(), fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse requirements: %+v", err)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common"
|
||||
)
|
||||
@@ -19,7 +20,7 @@ var _ common.ParserFn = parseSetup
|
||||
// " mypy2 == v0.770", ' mypy3== v0.770', --> match(name=mypy2 version=v0.770), match(name=mypy3, version=v0.770)
|
||||
var pinnedDependency = regexp.MustCompile(`['"]\W?(\w+\W?==\W?[\w\.]*)`)
|
||||
|
||||
func parseSetup(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
func parseSetup(_ string, reader io.Reader) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
packages := make([]pkg.Package, 0)
|
||||
|
||||
scanner := bufio.NewScanner(reader)
|
||||
@@ -46,5 +47,5 @@ func parseSetup(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return packages, nil
|
||||
return packages, nil, nil
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func TestParseSetup(t *testing.T) {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
actual, err := parseSetup(fixture.Name(), fixture)
|
||||
actual, _, err := parseSetup(fixture.Name(), fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse requirements: %+v", err)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
@@ -27,24 +28,26 @@ func (c *Cataloger) Name() string {
|
||||
}
|
||||
|
||||
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing rpm db installation.
|
||||
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, error) {
|
||||
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
fileMatches, err := resolver.FilesByGlob(pkg.RpmDBGlob)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find rpmdb's by glob: %w", err)
|
||||
return nil, nil, fmt.Errorf("failed to find rpmdb's by glob: %w", err)
|
||||
}
|
||||
|
||||
var pkgs []pkg.Package
|
||||
for _, location := range fileMatches {
|
||||
dbContentReader, err := resolver.FileContentsByLocation(location)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pkgs, err = parseRpmDB(resolver, location, dbContentReader)
|
||||
discoveredPkgs, err := parseRpmDB(resolver, location, dbContentReader)
|
||||
internal.CloseAndLogError(dbContentReader, location.VirtualPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to catalog rpmdb package=%+v: %w", location.RealPath, err)
|
||||
return nil, nil, fmt.Errorf("unable to catalog rpmdb package=%+v: %w", location.RealPath, err)
|
||||
}
|
||||
|
||||
pkgs = append(pkgs, discoveredPkgs...)
|
||||
}
|
||||
return pkgs, nil
|
||||
return pkgs, nil, nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common"
|
||||
)
|
||||
@@ -16,7 +17,7 @@ var _ common.ParserFn = parseGemFileLockEntries
|
||||
var sectionsOfInterest = internal.NewStringSetFromSlice([]string{"GEM"})
|
||||
|
||||
// parseGemFileLockEntries is a parser function for Gemfile.lock contents, returning all Gems discovered.
|
||||
func parseGemFileLockEntries(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
func parseGemFileLockEntries(_ string, reader io.Reader) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
pkgs := make([]pkg.Package, 0)
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
@@ -49,9 +50,9 @@ func parseGemFileLockEntries(_ string, reader io.Reader) ([]pkg.Package, error)
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
return pkgs, nil
|
||||
return pkgs, nil, nil
|
||||
}
|
||||
|
||||
func isDependencyLine(line string) bool {
|
||||
|
||||
@@ -68,7 +68,8 @@ func TestParseGemfileLockEntries(t *testing.T) {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
actual, err := parseGemFileLockEntries(fixture.Name(), fixture)
|
||||
// TODO: no relationships are under test yet
|
||||
actual, _, err := parseGemFileLockEntries(fixture.Name(), fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse gemfile lock: %+v", err)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common"
|
||||
)
|
||||
@@ -60,7 +61,7 @@ func processList(s string) []string {
|
||||
return results
|
||||
}
|
||||
|
||||
func parseGemSpecEntries(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
func parseGemSpecEntries(_ string, reader io.Reader) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var pkgs []pkg.Package
|
||||
var fields = make(map[string]interface{})
|
||||
scanner := bufio.NewScanner(reader)
|
||||
@@ -93,7 +94,7 @@ func parseGemSpecEntries(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
if fields["name"] != "" && fields["version"] != "" {
|
||||
var metadata pkg.GemMetadata
|
||||
if err := mapstructure.Decode(fields, &metadata); err != nil {
|
||||
return nil, fmt.Errorf("unable to decode gem metadata: %w", err)
|
||||
return nil, nil, fmt.Errorf("unable to decode gem metadata: %w", err)
|
||||
}
|
||||
|
||||
pkgs = append(pkgs, pkg.Package{
|
||||
@@ -107,7 +108,7 @@ func parseGemSpecEntries(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
})
|
||||
}
|
||||
|
||||
return pkgs, nil
|
||||
return pkgs, nil, nil
|
||||
}
|
||||
|
||||
// renderUtf8 takes any string escaped string sub-sections from the ruby string and replaces those sections with the UTF8 runes.
|
||||
|
||||
@@ -31,7 +31,8 @@ func TestParseGemspec(t *testing.T) {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
actual, err := parseGemSpecEntries(fixture.Name(), fixture)
|
||||
// TODO: no relationships are under test yet
|
||||
actual, _, err := parseGemSpecEntries(fixture.Name(), fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse gemspec: %+v", err)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common"
|
||||
"github.com/pelletier/go-toml"
|
||||
@@ -13,17 +14,17 @@ import (
|
||||
var _ common.ParserFn = parseCargoLock
|
||||
|
||||
// parseCargoLock is a parser function for Cargo.lock contents, returning all rust cargo crates discovered.
|
||||
func parseCargoLock(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
func parseCargoLock(_ string, reader io.Reader) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
tree, err := toml.LoadReader(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load Cargo.lock for parsing: %v", err)
|
||||
return nil, nil, fmt.Errorf("unable to load Cargo.lock for parsing: %v", err)
|
||||
}
|
||||
|
||||
metadata := CargoMetadata{}
|
||||
err = tree.Unmarshal(&metadata)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse Cargo.lock: %v", err)
|
||||
return nil, nil, fmt.Errorf("unable to parse Cargo.lock: %v", err)
|
||||
}
|
||||
|
||||
return metadata.Pkgs(), nil
|
||||
return metadata.Pkgs(), nil, nil
|
||||
}
|
||||
|
||||
@@ -177,7 +177,8 @@ func TestParseCargoLock(t *testing.T) {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
actual, err := parseCargoLock(fixture.Name(), fixture)
|
||||
// TODO: no relationships are under test yet
|
||||
actual, _, err := parseCargoLock(fixture.Name(), fixture)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -45,5 +45,8 @@ func MustCPE(cpeStr string) CPE {
|
||||
|
||||
func normalizeCpeField(field string) string {
|
||||
// keep dashes and forward slashes unescaped
|
||||
if field == "*" {
|
||||
return wfn.Any
|
||||
}
|
||||
return strings.ReplaceAll(wfn.StripSlashes(field), `\/`, "/")
|
||||
}
|
||||
|
||||
+35
-1
@@ -1,6 +1,10 @@
|
||||
package pkg
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func must(c CPE, e error) CPE {
|
||||
if e != nil {
|
||||
@@ -46,3 +50,33 @@ func TestNewCPE(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_normalizeCpeField(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
field string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
field: "something",
|
||||
expected: "something",
|
||||
},
|
||||
{
|
||||
field: "some\\thing",
|
||||
expected: `some\thing`,
|
||||
},
|
||||
{
|
||||
field: "*",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
field: "",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.field, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, normalizeCpeField(test.field))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
package pkg
|
||||
|
||||
// ID represents a unique value for each package added to a package catalog.
|
||||
type ID string
|
||||
@@ -21,7 +21,7 @@ type JavaMetadata struct {
|
||||
Manifest *JavaManifest `mapstructure:"Manifest" json:"manifest,omitempty"`
|
||||
PomProperties *PomProperties `mapstructure:"PomProperties" json:"pomProperties,omitempty"`
|
||||
PomProject *PomProject `mapstructure:"PomProject" json:"pomProject,omitempty"`
|
||||
Parent *Package `json:"-"`
|
||||
Parent *Package `hash:"ignore" json:"-"` // note: the parent cannot be included in the minimal definition of uniqueness since this field is not reproducible in an encode-decode cycle (is lossy).
|
||||
}
|
||||
|
||||
// PomProperties represents the fields of interest extracted from a Java archive's pom.properties file.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user