mirror of
https://github.com/anchore/syft.git
synced 2026-10-25 03:22:22 +01:00
Compare commits
@@ -28,7 +28,7 @@ jobs:
|
||||
GORELEASER_LATEST_VERSION=$(go list -m -json github.com/goreleaser/goreleaser@latest 2>/dev/null | jq -r '.Version')
|
||||
GOSIMPORTS_LATEST_VERSION=$(go list -m -json github.com/rinchsan/gosimports@latest 2>/dev/null | jq -r '.Version')
|
||||
YAJSV_LATEST_VERSION=$(go list -m -json github.com/neilpa/yajsv@latest 2>/dev/null | jq -r '.Version')
|
||||
COSIGN_LATEST_VERSION=$(go list -m -json github.com/sigstore/cosign@latest 2>/dev/null | jq -r '.Version')
|
||||
COSIGN_LATEST_VERSION=$(go list -m -json github.com/sigstore/cosign/v2@latest 2>/dev/null | jq -r '.Version')
|
||||
GLOW_LATEST_VERSION=$(go list -m -json github.com/charmbracelet/glow@latest 2>/dev/null | jq -r '.Version')
|
||||
|
||||
# update version variables in the Makefile
|
||||
|
||||
@@ -14,11 +14,11 @@ GOLANGCILINT_VERSION := v1.52.2
|
||||
GOSIMPORTS_VERSION := v0.3.8
|
||||
BOUNCER_VERSION := v0.4.0
|
||||
CHRONICLE_VERSION := v0.6.0
|
||||
GORELEASER_VERSION := v1.17.2
|
||||
GORELEASER_VERSION := v1.18.2
|
||||
YAJSV_VERSION := v1.4.1
|
||||
COSIGN_VERSION := v1.13.1
|
||||
COSIGN_VERSION := v2.0.2
|
||||
QUILL_VERSION := v0.2.0
|
||||
GLOW_VERSION := v1.5.0
|
||||
GLOW_VERSION := v1.5.1
|
||||
|
||||
# Formatting variables #################################
|
||||
BOLD := $(shell tput -T linux bold)
|
||||
@@ -90,7 +90,7 @@ bootstrap-tools: $(TEMP_DIR)
|
||||
# the only difference between goimports and gosimports is that gosimports removes extra whitespace between import blocks (see https://github.com/golang/go/issues/20818)
|
||||
GOBIN="$(realpath $(TEMP_DIR))" go install github.com/rinchsan/gosimports/cmd/gosimports@$(GOSIMPORTS_VERSION)
|
||||
GOBIN="$(realpath $(TEMP_DIR))" go install github.com/neilpa/yajsv@$(YAJSV_VERSION)
|
||||
GOBIN="$(realpath $(TEMP_DIR))" go install github.com/sigstore/cosign/cmd/cosign@$(COSIGN_VERSION)
|
||||
GOBIN="$(realpath $(TEMP_DIR))" go install github.com/sigstore/cosign/v2/cmd/cosign@$(COSIGN_VERSION)
|
||||
GOBIN="$(realpath $(TEMP_DIR))" go install github.com/charmbracelet/glow@$(GLOW_VERSION)
|
||||
|
||||
.PHONY: bootstrap-go
|
||||
|
||||
@@ -30,34 +30,36 @@ require (
|
||||
// pinned to pull in 386 arch fix: https://github.com/scylladb/go-set/commit/cc7b2070d91ebf40d233207b633e28f5bd8f03a5
|
||||
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e
|
||||
github.com/sergi/go-diff v1.3.1
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/sirupsen/logrus v1.9.2
|
||||
github.com/spdx/tools-golang v0.5.0
|
||||
github.com/spf13/afero v1.9.5
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.15.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/stretchr/testify v1.8.3
|
||||
github.com/vifraa/gopom v0.2.1
|
||||
github.com/wagoodman/go-partybus v0.0.0-20210627031916-db1f5573bbc5
|
||||
github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5
|
||||
github.com/wagoodman/jotframe v0.0.0-20211129225309-56b0d0a4aebb
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
golang.org/x/mod v0.10.0
|
||||
golang.org/x/net v0.9.0
|
||||
golang.org/x/term v0.7.0
|
||||
golang.org/x/net v0.10.0
|
||||
golang.org/x/term v0.8.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/CycloneDX/cyclonedx-go v0.7.1
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8
|
||||
github.com/anchore/stereoscope v0.0.0-20230412183729-8602f1afc574
|
||||
github.com/anchore/stereoscope v0.0.0-20230522170632-e14bc4437b2e
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
|
||||
github.com/docker/docker v23.0.5+incompatible
|
||||
github.com/docker/docker v24.0.1+incompatible
|
||||
github.com/github/go-spdx/v2 v2.1.2
|
||||
github.com/go-git/go-billy/v5 v5.4.1
|
||||
github.com/go-git/go-git/v5 v5.6.1
|
||||
github.com/google/go-containerregistry v0.14.0
|
||||
github.com/google/go-containerregistry v0.15.2
|
||||
github.com/google/licensecheck v0.3.1
|
||||
github.com/invopop/jsonschema v0.7.0
|
||||
github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b
|
||||
@@ -70,10 +72,11 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/DataDog/zstd v1.4.5 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
|
||||
@@ -82,8 +85,8 @@ require (
|
||||
github.com/containerd/containerd v1.7.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/cli v23.0.1+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/cli v23.0.5+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
@@ -105,7 +108,7 @@ require (
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.16.4 // indirect
|
||||
github.com/klauspost/compress v1.16.5 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
@@ -114,9 +117,8 @@ require (
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc3 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
@@ -134,14 +136,14 @@ require (
|
||||
github.com/sylabs/squashfs v0.6.1 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/vbatts/tar-split v0.11.2 // indirect
|
||||
github.com/vbatts/tar-split v0.11.3 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/tools v0.8.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
|
||||
@@ -53,6 +53,7 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOEl
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/CycloneDX/cyclonedx-go v0.7.1 h1:5w1SxjGm9MTMNTuRbEPyw21ObdbaagTWF/KfF0qHTRE=
|
||||
github.com/CycloneDX/cyclonedx-go v0.7.1/go.mod h1:N/nrdWQI2SIjaACyyDs/u7+ddCkyl/zkNs8xFsHF2Ps=
|
||||
@@ -61,13 +62,15 @@ github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
||||
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
|
||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
|
||||
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
|
||||
@@ -95,8 +98,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 h1:AV7qjwMcM4r8wFhJq3jLRztew3ywIyPTRapl2T1s9o8=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4=
|
||||
github.com/anchore/stereoscope v0.0.0-20230412183729-8602f1afc574 h1:VFX+FD9EH6am+tfqwr1KeCAmabAknSJQX95aIY3QJJI=
|
||||
github.com/anchore/stereoscope v0.0.0-20230412183729-8602f1afc574/go.mod h1:2GGFHkHry/xDlEQgBrVGcarq+z7Z6hLnHdyhcKB2lfQ=
|
||||
github.com/anchore/stereoscope v0.0.0-20230522170632-e14bc4437b2e h1:YPWJxds1hKRedS92u7O6D6ULVOx1F2HGgS4CWqJdBYw=
|
||||
github.com/anchore/stereoscope v0.0.0-20230522170632-e14bc4437b2e/go.mod h1:0LsgHgXO4QFnk2hsYwtqd3fR18PIZXlFLIl2qb9tu3g=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
@@ -150,23 +153,21 @@ github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSk
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M=
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk=
|
||||
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
|
||||
github.com/docker/cli v23.0.1+incompatible h1:LRyWITpGzl2C9e9uGxzisptnxAn1zfZKXy13Ul2Q5oM=
|
||||
github.com/docker/cli v23.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
|
||||
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v23.0.5+incompatible h1:DaxtlTJjFSnLOXVNUBU1+6kXGz2lpDoEAH6QoxaSg8k=
|
||||
github.com/docker/docker v23.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/cli v23.0.5+incompatible h1:ufWmAOuD3Vmr7JP2G5K3cyuNC4YZWiAsuDEvFVVDafE=
|
||||
github.com/docker/cli v23.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
||||
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v24.0.1+incompatible h1:NxN81beIxDlUaVt46iUQrYHD9/W3u9EGl52r86O/IGw=
|
||||
github.com/docker/docker v24.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
|
||||
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
@@ -206,6 +207,8 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS
|
||||
github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro=
|
||||
github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/github/go-spdx/v2 v2.1.2 h1:p+Tv0yMgcuO0/vnMe9Qh4tmUgYhI6AsLVlakZ/Sx+DM=
|
||||
github.com/github/go-spdx/v2 v2.1.2/go.mod h1:hMCrsFgT0QnCwn7G8gxy/MxMpy67WgZrwFeISTn0o6w=
|
||||
github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4=
|
||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
|
||||
@@ -288,8 +291,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-containerregistry v0.14.0 h1:z58vMqHxuwvAsVwvKEkmVBz2TlgBgH5k6koEXBtlYkw=
|
||||
github.com/google/go-containerregistry v0.14.0/go.mod h1:aiJ2fp/SXvkWgmYHioXnbMdlgB8eXiiYOY55gfN91Wk=
|
||||
github.com/google/go-containerregistry v0.15.2 h1:MMkSh+tjSdnmJZO7ljvEqV1DjfekB6VUEAZgy3a+TQE=
|
||||
github.com/google/go-containerregistry v0.15.2/go.mod h1:wWK+LnOv4jXMM23IT/F1wdYftGWGr47Is8CG+pmHK1Q=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/licensecheck v0.3.1 h1:QoxgoDkaeC4nFrtGN1jV7IPmDCHFNIVh54e5hSt6sPs=
|
||||
github.com/google/licensecheck v0.3.1/go.mod h1:ORkR35t/JjW+emNKtfJDII0zlciG9JgbT7SmsohlHmY=
|
||||
@@ -399,8 +402,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU=
|
||||
github.com/klauspost/compress v1.16.4/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
|
||||
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
|
||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
@@ -474,8 +477,7 @@ github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM=
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc=
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
@@ -490,8 +492,8 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
@@ -536,7 +538,6 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
|
||||
@@ -552,12 +553,12 @@ github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y=
|
||||
github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0=
|
||||
github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
@@ -577,7 +578,6 @@ github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
|
||||
@@ -598,8 +598,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
||||
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
@@ -614,11 +615,11 @@ github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oW
|
||||
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
|
||||
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8=
|
||||
github.com/vbatts/go-mtree v0.5.3 h1:S/jYlfG8rZ+a0bhZd+RANXejy7M4Js8fq9U+XoWTd5w=
|
||||
github.com/vbatts/go-mtree v0.5.3/go.mod h1:eXsdoPMdL2jcJx6HweWi9lYQxBsTp4lNhqqAjgkZUg8=
|
||||
github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME=
|
||||
github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI=
|
||||
github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck=
|
||||
github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY=
|
||||
github.com/vifraa/gopom v0.2.1 h1:MYVMAMyiGzXPPy10EwojzKIL670kl5Zbae+o3fFvQEM=
|
||||
github.com/vifraa/gopom v0.2.1/go.mod h1:oPa1dcrGrtlO37WPDBm5SqHAT+wTgF8An1Q71Z6Vv4o=
|
||||
github.com/wagoodman/go-partybus v0.0.0-20210627031916-db1f5573bbc5 h1:phTLPgMRDYTizrBSKsNSOa2zthoC2KsJsaY/8sg3rD8=
|
||||
@@ -778,8 +779,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -886,13 +887,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -901,8 +903,8 @@ golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuX
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -931,7 +933,6 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
@@ -1165,7 +1166,6 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
@@ -6,5 +6,5 @@ const (
|
||||
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "7.1.5"
|
||||
JSONSchemaVersion = "8.0.0"
|
||||
)
|
||||
|
||||
+16
-10
@@ -4,7 +4,10 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/google/licensecheck"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -13,21 +16,24 @@ const (
|
||||
)
|
||||
|
||||
// Parse scans the contents of a license file to attempt to determine the type of license it is
|
||||
func Parse(reader io.Reader) (licenses []string, err error) {
|
||||
func Parse(reader io.Reader, l source.Location) (licenses []pkg.License, err error) {
|
||||
licenses = make([]pkg.License, 0)
|
||||
contents, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cov := licensecheck.Scan(contents)
|
||||
if cov.Percent < coverageThreshold {
|
||||
// unknown or no licenses here?
|
||||
return licenses, nil
|
||||
}
|
||||
|
||||
if cov.Percent < float64(coverageThreshold) {
|
||||
licenses = append(licenses, unknownLicenseType)
|
||||
}
|
||||
for _, m := range cov.Match {
|
||||
if slices.Contains(licenses, m.ID) {
|
||||
continue
|
||||
}
|
||||
licenses = append(licenses, m.ID)
|
||||
lic := pkg.NewLicenseFromLocations(m.ID, l)
|
||||
lic.Type = license.Concluded
|
||||
|
||||
licenses = append(licenses, lic)
|
||||
}
|
||||
return
|
||||
|
||||
return licenses, nil
|
||||
}
|
||||
|
||||
+20
-2
@@ -15,8 +15,10 @@ func NewStringSet(start ...string) StringSet {
|
||||
}
|
||||
|
||||
// Add a string to the set.
|
||||
func (s StringSet) Add(i string) {
|
||||
s[i] = struct{}{}
|
||||
func (s StringSet) Add(i ...string) {
|
||||
for _, str := range i {
|
||||
s[str] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove a string from the set.
|
||||
@@ -41,3 +43,19 @@ func (s StringSet) ToSlice() []string {
|
||||
sort.Strings(ret)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s StringSet) Equals(o StringSet) bool {
|
||||
if len(s) != len(o) {
|
||||
return false
|
||||
}
|
||||
for k := range s {
|
||||
if !o.Contains(k) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s StringSet) Empty() bool {
|
||||
return len(s) < 1
|
||||
}
|
||||
|
||||
@@ -29,6 +29,9 @@ can be extended to include specific package metadata struct shapes in the future
|
||||
// not matter as long as it is exported.
|
||||
|
||||
// TODO: this should be generated from reflection of whats in the pkg package
|
||||
// Should be created during generation below; use reflection's ability to
|
||||
// create types at runtime.
|
||||
// should be same name as struct minus metadata
|
||||
type artifactMetadataContainer struct {
|
||||
Alpm pkg.AlpmMetadata
|
||||
Apk pkg.ApkMetadata
|
||||
@@ -56,6 +59,7 @@ type artifactMetadataContainer struct {
|
||||
PythonPackage pkg.PythonPackageMetadata
|
||||
PythonPipfilelock pkg.PythonPipfileLockMetadata
|
||||
PythonRequirements pkg.PythonRequirementsMetadata
|
||||
RDescriptionFile pkg.RDescriptionFileMetadata
|
||||
Rebar pkg.RebarLockMetadata
|
||||
Rpm pkg.RpmMetadata
|
||||
RustCargo pkg.CargoPackageMetadata
|
||||
@@ -74,7 +78,6 @@ func build() *jsonschema.Schema {
|
||||
}
|
||||
documentSchema := reflector.ReflectFromType(reflect.TypeOf(&syftjsonModel.Document{}))
|
||||
metadataSchema := reflector.ReflectFromType(reflect.TypeOf(&artifactMetadataContainer{}))
|
||||
|
||||
// TODO: inject source definitions
|
||||
|
||||
// inject the definitions of all metadatas into the schema definitions
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -37,7 +37,7 @@ func (i *ContentsCataloger) Catalog(resolver source.FileResolver) (map[source.Co
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if i.skipFilesAboveSizeInBytes > 0 && metadata.Size > i.skipFilesAboveSizeInBytes {
|
||||
if i.skipFilesAboveSizeInBytes > 0 && metadata.Size() > i.skipFilesAboveSizeInBytes {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
)
|
||||
|
||||
type License struct {
|
||||
Value string
|
||||
SPDXExpression string
|
||||
Type license.Type
|
||||
LicenseEvidence *LicenseEvidence // evidence from license classifier
|
||||
}
|
||||
|
||||
type LicenseEvidence struct {
|
||||
Confidence int
|
||||
Offset int
|
||||
Extent int
|
||||
}
|
||||
|
||||
func NewLicense(value string) License {
|
||||
spdxExpression, err := license.ParseExpression(value)
|
||||
if err != nil {
|
||||
log.Trace("unable to parse license expression: %s, %w", value, err)
|
||||
}
|
||||
|
||||
return License{
|
||||
Value: value,
|
||||
SPDXExpression: spdxExpression,
|
||||
Type: license.Concluded,
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"flag"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -52,12 +51,15 @@ func TestFileMetadataCataloger(t *testing.T) {
|
||||
path: "/file-1.txt",
|
||||
exists: true,
|
||||
expected: source.FileMetadata{
|
||||
FileInfo: file.ManualInfo{
|
||||
NameValue: "file-1.txt",
|
||||
ModeValue: 0644,
|
||||
SizeValue: 7,
|
||||
},
|
||||
Path: "/file-1.txt",
|
||||
Mode: 0644,
|
||||
Type: file.TypeRegular,
|
||||
UserID: 1,
|
||||
GroupID: 2,
|
||||
Size: 7,
|
||||
MIMEType: "text/plain",
|
||||
},
|
||||
},
|
||||
@@ -65,8 +67,11 @@ func TestFileMetadataCataloger(t *testing.T) {
|
||||
path: "/hardlink-1",
|
||||
exists: true,
|
||||
expected: source.FileMetadata{
|
||||
FileInfo: file.ManualInfo{
|
||||
NameValue: "hardlink-1",
|
||||
ModeValue: 0644,
|
||||
},
|
||||
Path: "/hardlink-1",
|
||||
Mode: 0644,
|
||||
Type: file.TypeHardLink,
|
||||
LinkDestination: "file-1.txt",
|
||||
UserID: 1,
|
||||
@@ -78,8 +83,11 @@ func TestFileMetadataCataloger(t *testing.T) {
|
||||
path: "/symlink-1",
|
||||
exists: true,
|
||||
expected: source.FileMetadata{
|
||||
Path: "/symlink-1",
|
||||
Mode: 0777 | os.ModeSymlink,
|
||||
Path: "/symlink-1",
|
||||
FileInfo: file.ManualInfo{
|
||||
NameValue: "symlink-1",
|
||||
ModeValue: 0777 | os.ModeSymlink,
|
||||
},
|
||||
Type: file.TypeSymLink,
|
||||
LinkDestination: "file-1.txt",
|
||||
UserID: 0,
|
||||
@@ -91,8 +99,11 @@ func TestFileMetadataCataloger(t *testing.T) {
|
||||
path: "/char-device-1",
|
||||
exists: true,
|
||||
expected: source.FileMetadata{
|
||||
Path: "/char-device-1",
|
||||
Mode: 0644 | os.ModeDevice | os.ModeCharDevice,
|
||||
Path: "/char-device-1",
|
||||
FileInfo: file.ManualInfo{
|
||||
NameValue: "char-device-1",
|
||||
ModeValue: 0644 | os.ModeDevice | os.ModeCharDevice,
|
||||
},
|
||||
Type: file.TypeCharacterDevice,
|
||||
UserID: 0,
|
||||
GroupID: 0,
|
||||
@@ -103,8 +114,11 @@ func TestFileMetadataCataloger(t *testing.T) {
|
||||
path: "/block-device-1",
|
||||
exists: true,
|
||||
expected: source.FileMetadata{
|
||||
Path: "/block-device-1",
|
||||
Mode: 0644 | os.ModeDevice,
|
||||
Path: "/block-device-1",
|
||||
FileInfo: file.ManualInfo{
|
||||
NameValue: "block-device-1",
|
||||
ModeValue: 0644 | os.ModeDevice,
|
||||
},
|
||||
Type: file.TypeBlockDevice,
|
||||
UserID: 0,
|
||||
GroupID: 0,
|
||||
@@ -115,8 +129,11 @@ func TestFileMetadataCataloger(t *testing.T) {
|
||||
path: "/fifo-1",
|
||||
exists: true,
|
||||
expected: source.FileMetadata{
|
||||
Path: "/fifo-1",
|
||||
Mode: 0644 | os.ModeNamedPipe,
|
||||
Path: "/fifo-1",
|
||||
FileInfo: file.ManualInfo{
|
||||
NameValue: "fifo-1",
|
||||
ModeValue: 0644 | os.ModeNamedPipe,
|
||||
},
|
||||
Type: file.TypeFIFO,
|
||||
UserID: 0,
|
||||
GroupID: 0,
|
||||
@@ -127,13 +144,15 @@ func TestFileMetadataCataloger(t *testing.T) {
|
||||
path: "/bin",
|
||||
exists: true,
|
||||
expected: source.FileMetadata{
|
||||
Path: "/bin",
|
||||
Mode: 0755 | os.ModeDir,
|
||||
Path: "/bin",
|
||||
FileInfo: file.ManualInfo{
|
||||
NameValue: "bin",
|
||||
ModeValue: 0755 | os.ModeDir,
|
||||
},
|
||||
Type: file.TypeDirectory,
|
||||
UserID: 0,
|
||||
GroupID: 0,
|
||||
MIMEType: "",
|
||||
IsDir: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -146,13 +165,14 @@ func TestFileMetadataCataloger(t *testing.T) {
|
||||
l := source.NewLocationFromImage(test.path, *ref.Reference, img)
|
||||
|
||||
if _, ok := actual[l.Coordinates]; ok {
|
||||
redact := actual[l.Coordinates]
|
||||
redact.ModTime = time.Time{}
|
||||
actual[l.Coordinates] = redact
|
||||
// we're not interested in keeping the test fixtures up to date with the latest file modification times
|
||||
// thus ModTime is not under test
|
||||
fi := test.expected.FileInfo.(file.ManualInfo)
|
||||
fi.ModTimeValue = actual[l.Coordinates].ModTime()
|
||||
test.expected.FileInfo = fi
|
||||
}
|
||||
|
||||
assert.Equal(t, test.expected, actual[l.Coordinates], "mismatched metadata")
|
||||
|
||||
assert.True(t, test.expected.Equal(actual[l.Coordinates]))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -71,11 +71,11 @@ func (i *SecretsCataloger) catalogLocation(resolver source.FileResolver, locatio
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if metadata.Size == 0 {
|
||||
if metadata.Size() == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if i.skipFilesAboveSize > 0 && metadata.Size > i.skipFilesAboveSize {
|
||||
if i.skipFilesAboveSize > 0 && metadata.Size() > i.skipFilesAboveSize {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ func decodeComponent(c *cyclonedx.Component) *pkg.Package {
|
||||
Name: c.Name,
|
||||
Version: c.Version,
|
||||
Locations: decodeLocations(values),
|
||||
Licenses: decodeLicenses(c),
|
||||
Licenses: pkg.NewLicenseSet(decodeLicenses(c)...),
|
||||
CPEs: decodeCPEs(c),
|
||||
PURL: c.PackageURL,
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ func Test_encodeComponentProperties(t *testing.T) {
|
||||
OriginPackage: "libc-dev",
|
||||
Maintainer: "Natanael Copa <ncopa@alpinelinux.org>",
|
||||
Version: "0.7.2-r0",
|
||||
License: "BSD",
|
||||
Architecture: "x86_64",
|
||||
URL: "http://alpinelinux.org",
|
||||
Description: "Meta package to pull in correct libc",
|
||||
@@ -140,7 +139,6 @@ func Test_encodeComponentProperties(t *testing.T) {
|
||||
Version: "0.9.2",
|
||||
SourceRpm: "dive-0.9.2-1.src.rpm",
|
||||
Size: 12406784,
|
||||
License: "MIT",
|
||||
Vendor: "",
|
||||
Files: []pkg.RpmdbFileRecord{},
|
||||
},
|
||||
|
||||
@@ -206,7 +206,7 @@ func collectRelationships(bom *cyclonedx.BOM, s *sbom.SBOM, idMap map[string]int
|
||||
return
|
||||
}
|
||||
for _, d := range *bom.Dependencies {
|
||||
from, fromExists := idMap[d.Ref].(artifact.Identifiable)
|
||||
to, fromExists := idMap[d.Ref].(artifact.Identifiable)
|
||||
if !fromExists {
|
||||
continue
|
||||
}
|
||||
@@ -216,7 +216,7 @@ func collectRelationships(bom *cyclonedx.BOM, s *sbom.SBOM, idMap map[string]int
|
||||
}
|
||||
|
||||
for _, t := range *d.Dependencies {
|
||||
to, toExists := idMap[t].(artifact.Identifiable)
|
||||
from, toExists := idMap[t].(artifact.Identifiable)
|
||||
if !toExists {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ import (
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
func Test_decode(t *testing.T) {
|
||||
@@ -184,16 +187,16 @@ func Test_decode(t *testing.T) {
|
||||
ver: "1.2.3",
|
||||
},
|
||||
{
|
||||
pkg: "package-1",
|
||||
ver: "1.0.1",
|
||||
cpe: "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
|
||||
purl: "pkg:some/package-1@1.0.1?arch=arm64&upstream=upstream1&distro=alpine-1",
|
||||
relation: "package-2",
|
||||
pkg: "package-1",
|
||||
ver: "1.0.1",
|
||||
cpe: "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
|
||||
purl: "pkg:some/package-1@1.0.1?arch=arm64&upstream=upstream1&distro=alpine-1",
|
||||
},
|
||||
{
|
||||
pkg: "package-2",
|
||||
ver: "2.0.2",
|
||||
purl: "pkg:apk/alpine/alpine-baselayout@3.2.0-r16?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.14.2",
|
||||
pkg: "package-2",
|
||||
ver: "2.0.2",
|
||||
purl: "pkg:apk/alpine/alpine-baselayout@3.2.0-r16?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.14.2",
|
||||
relation: "package-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -257,6 +260,46 @@ func Test_decode(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_relationshipDirection(t *testing.T) {
|
||||
cyclonedx_bom := cyclonedx.BOM{Metadata: nil,
|
||||
Components: &[]cyclonedx.Component{
|
||||
{
|
||||
BOMRef: "p1",
|
||||
Type: cyclonedx.ComponentTypeLibrary,
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
PackageURL: "pkg:some/package-1@1.0.1?arch=arm64&upstream=upstream1&distro=alpine-1",
|
||||
},
|
||||
{
|
||||
BOMRef: "p2",
|
||||
Type: cyclonedx.ComponentTypeLibrary,
|
||||
Name: "package-2",
|
||||
Version: "2.0.2",
|
||||
PackageURL: "pkg:some/package-2@2.0.2?arch=arm64&upstream=upstream1&distro=alpine-1",
|
||||
},
|
||||
},
|
||||
Dependencies: &[]cyclonedx.Dependency{
|
||||
{
|
||||
Ref: "p1",
|
||||
Dependencies: &[]string{"p2"},
|
||||
},
|
||||
}}
|
||||
sbom, err := ToSyftModel(&cyclonedx_bom)
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, sbom.Relationships, 1)
|
||||
relationship := sbom.Relationships[0]
|
||||
|
||||
// check that p2 -- dependency of --> p1
|
||||
// same as p1 -- depends on --> p2
|
||||
assert.Equal(t, artifact.DependencyOfRelationship, relationship.Type)
|
||||
assert.Equal(t, "package-2", packageNameFromIdentifier(sbom, relationship.From))
|
||||
assert.Equal(t, "package-1", packageNameFromIdentifier(sbom, relationship.To))
|
||||
}
|
||||
|
||||
func packageNameFromIdentifier(model *sbom.SBOM, identifier artifact.Identifiable) string {
|
||||
return model.Artifacts.Packages.Package(identifier.ID()).Name
|
||||
}
|
||||
|
||||
func Test_missingDataDecode(t *testing.T) {
|
||||
bom := &cyclonedx.BOM{
|
||||
Metadata: nil,
|
||||
@@ -279,8 +322,7 @@ func Test_missingDataDecode(t *testing.T) {
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
assert.Len(t, pkg.Licenses, 0)
|
||||
assert.Equal(t, pkg.Licenses.Empty(), true)
|
||||
}
|
||||
|
||||
func Test_missingComponentsDecode(t *testing.T) {
|
||||
|
||||
@@ -50,7 +50,7 @@ func Test_encodeExternalReferences(t *testing.T) {
|
||||
Language: pkg.Rust,
|
||||
Type: pkg.RustPkg,
|
||||
MetadataType: pkg.RustCargoPackageMetadataType,
|
||||
Licenses: nil,
|
||||
Licenses: pkg.NewLicenseSet(),
|
||||
Metadata: pkg.CargoPackageMetadata{
|
||||
Name: "ansi_term",
|
||||
Version: "0.12.1",
|
||||
|
||||
@@ -1,53 +1,205 @@
|
||||
package cyclonedxhelpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
// This should be a function that just surfaces licenses already validated in the package struct
|
||||
func encodeLicenses(p pkg.Package) *cyclonedx.Licenses {
|
||||
lc := cyclonedx.Licenses{}
|
||||
for _, licenseName := range p.Licenses {
|
||||
if value, exists := spdxlicense.ID(licenseName); exists {
|
||||
lc = append(lc, cyclonedx.LicenseChoice{
|
||||
spdxc, otherc, ex := separateLicenses(p)
|
||||
if len(otherc) > 0 {
|
||||
// found non spdx related licenses
|
||||
// build individual license choices for each
|
||||
// complex expressions are not combined and set as NAME fields
|
||||
for _, e := range ex {
|
||||
otherc = append(otherc, cyclonedx.LicenseChoice{
|
||||
License: &cyclonedx.License{
|
||||
Name: e,
|
||||
},
|
||||
})
|
||||
}
|
||||
otherc = append(otherc, spdxc...)
|
||||
return &otherc
|
||||
}
|
||||
|
||||
if len(spdxc) > 0 {
|
||||
for _, l := range ex {
|
||||
spdxc = append(spdxc, cyclonedx.LicenseChoice{
|
||||
License: &cyclonedx.License{
|
||||
Name: l,
|
||||
},
|
||||
})
|
||||
}
|
||||
return &spdxc
|
||||
}
|
||||
|
||||
if len(ex) > 0 {
|
||||
// only expressions found
|
||||
var expressions cyclonedx.Licenses
|
||||
expressions = append(expressions, cyclonedx.LicenseChoice{
|
||||
Expression: mergeSPDX(ex),
|
||||
})
|
||||
return &expressions
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeLicenses(c *cyclonedx.Component) []pkg.License {
|
||||
licenses := make([]pkg.License, 0)
|
||||
if c == nil || c.Licenses == nil {
|
||||
return licenses
|
||||
}
|
||||
|
||||
for _, l := range *c.Licenses {
|
||||
if l.License == nil {
|
||||
continue
|
||||
}
|
||||
// these fields are mutually exclusive in the spec
|
||||
switch {
|
||||
case l.License.ID != "":
|
||||
licenses = append(licenses, pkg.NewLicenseFromURLs(l.License.ID, l.License.URL))
|
||||
case l.License.Name != "":
|
||||
licenses = append(licenses, pkg.NewLicenseFromURLs(l.License.Name, l.License.URL))
|
||||
case l.Expression != "":
|
||||
licenses = append(licenses, pkg.NewLicenseFromURLs(l.Expression, l.License.URL))
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return licenses
|
||||
}
|
||||
|
||||
// nolint:funlen
|
||||
func separateLicenses(p pkg.Package) (spdx, other cyclonedx.Licenses, expressions []string) {
|
||||
ex := make([]string, 0)
|
||||
spdxc := cyclonedx.Licenses{}
|
||||
otherc := cyclonedx.Licenses{}
|
||||
/*
|
||||
pkg.License can be a couple of things: see above declarations
|
||||
- Complex SPDX expression
|
||||
- Some other Valid license ID
|
||||
- Some non-standard non spdx license
|
||||
|
||||
To determine if an expression is a singular ID we first run it against the SPDX license list.
|
||||
|
||||
The weird case we run into is if there is a package with a license that is not a valid SPDX expression
|
||||
and a license that is a valid complex expression. In this case we will surface the valid complex expression
|
||||
as a license choice and the invalid expression as a license string.
|
||||
|
||||
*/
|
||||
seen := make(map[string]bool)
|
||||
for _, l := range p.Licenses.ToSlice() {
|
||||
// singular expression case
|
||||
// only ID field here since we guarantee that the license is valid
|
||||
if value, exists := spdxlicense.ID(l.SPDXExpression); exists {
|
||||
if !l.URLs.Empty() {
|
||||
processLicenseURLs(l, value, &spdxc)
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exists := seen[value]; exists {
|
||||
continue
|
||||
}
|
||||
// try making set of license choices to avoid duplicates
|
||||
// only update if the license has more information
|
||||
spdxc = append(spdxc, cyclonedx.LicenseChoice{
|
||||
License: &cyclonedx.License{
|
||||
ID: value,
|
||||
},
|
||||
})
|
||||
seen[value] = true
|
||||
// we have added the license to the SPDX license list check next license
|
||||
continue
|
||||
}
|
||||
|
||||
// not found so append the licenseName as is
|
||||
lc = append(lc, cyclonedx.LicenseChoice{
|
||||
if l.SPDXExpression != "" {
|
||||
// COMPLEX EXPRESSION CASE
|
||||
ex = append(ex, l.SPDXExpression)
|
||||
continue
|
||||
}
|
||||
|
||||
// license string that are not valid spdx expressions or ids
|
||||
// we only use license Name here since we cannot guarantee that the license is a valid SPDX expression
|
||||
if !l.URLs.Empty() {
|
||||
processLicenseURLs(l, "", &otherc)
|
||||
continue
|
||||
}
|
||||
otherc = append(otherc, cyclonedx.LicenseChoice{
|
||||
License: &cyclonedx.License{
|
||||
Name: licenseName,
|
||||
Name: l.Value,
|
||||
},
|
||||
})
|
||||
}
|
||||
if len(lc) > 0 {
|
||||
return &lc
|
||||
}
|
||||
return nil
|
||||
return spdxc, otherc, ex
|
||||
}
|
||||
|
||||
func decodeLicenses(c *cyclonedx.Component) (out []string) {
|
||||
if c.Licenses != nil {
|
||||
for _, l := range *c.Licenses {
|
||||
if l.License != nil {
|
||||
var lic string
|
||||
switch {
|
||||
case l.License.ID != "":
|
||||
lic = l.License.ID
|
||||
case l.License.Name != "":
|
||||
lic = l.License.Name
|
||||
default:
|
||||
continue
|
||||
}
|
||||
out = append(out, lic)
|
||||
}
|
||||
func processLicenseURLs(l pkg.License, spdxID string, populate *cyclonedx.Licenses) {
|
||||
for _, url := range l.URLs.ToSlice() {
|
||||
if spdxID == "" {
|
||||
*populate = append(*populate, cyclonedx.LicenseChoice{
|
||||
License: &cyclonedx.License{
|
||||
URL: url,
|
||||
Name: l.Value,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
*populate = append(*populate, cyclonedx.LicenseChoice{
|
||||
License: &cyclonedx.License{
|
||||
ID: spdxID,
|
||||
URL: url,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func mergeSPDX(ex []string) string {
|
||||
var candidate []string
|
||||
for _, e := range ex {
|
||||
// if the expression does not have balanced parens add them
|
||||
if !strings.HasPrefix(e, "(") && !strings.HasSuffix(e, ")") {
|
||||
e = "(" + e + ")"
|
||||
candidate = append(candidate, e)
|
||||
}
|
||||
}
|
||||
|
||||
if len(candidate) == 1 {
|
||||
return reduceOuter(strings.Join(candidate, " AND "))
|
||||
}
|
||||
|
||||
return strings.Join(candidate, " AND ")
|
||||
}
|
||||
|
||||
func reduceOuter(expression string) string {
|
||||
var (
|
||||
sb strings.Builder
|
||||
openCount int
|
||||
)
|
||||
|
||||
for _, c := range expression {
|
||||
if string(c) == "(" && openCount > 0 {
|
||||
fmt.Fprintf(&sb, "%c", c)
|
||||
}
|
||||
if string(c) == "(" {
|
||||
openCount++
|
||||
continue
|
||||
}
|
||||
if string(c) == ")" && openCount > 1 {
|
||||
fmt.Fprintf(&sb, "%c", c)
|
||||
}
|
||||
if string(c) == ")" {
|
||||
openCount--
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&sb, "%c", c)
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
@@ -23,60 +25,170 @@ func Test_encodeLicense(t *testing.T) {
|
||||
{
|
||||
name: "no SPDX licenses",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"made-up",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("RandomLicense"),
|
||||
),
|
||||
},
|
||||
expected: &cyclonedx.Licenses{
|
||||
{License: &cyclonedx.License{Name: "made-up"}},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
Name: "RandomLicense",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with SPDX license",
|
||||
name: "single SPDX ID and Non SPDX ID",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("mit"),
|
||||
pkg.NewLicense("FOOBAR"),
|
||||
),
|
||||
},
|
||||
expected: &cyclonedx.Licenses{
|
||||
{License: &cyclonedx.License{ID: "MIT"}},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
Name: "FOOBAR",
|
||||
},
|
||||
},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
ID: "MIT",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with SPDX license expression",
|
||||
name: "with complex SPDX license expression",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
"GPL-3.0",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT AND GPL-3.0-only"),
|
||||
),
|
||||
},
|
||||
expected: &cyclonedx.Licenses{
|
||||
{License: &cyclonedx.License{ID: "MIT"}},
|
||||
{License: &cyclonedx.License{ID: "GPL-3.0-only"}},
|
||||
{
|
||||
Expression: "MIT AND GPL-3.0-only",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "cap insensitive",
|
||||
name: "with multiple complex SPDX license expression",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"gpl-3.0",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT AND GPL-3.0-only"),
|
||||
pkg.NewLicense("MIT AND GPL-3.0-only WITH Classpath-exception-2.0"),
|
||||
),
|
||||
},
|
||||
expected: &cyclonedx.Licenses{
|
||||
{License: &cyclonedx.License{ID: "GPL-3.0-only"}},
|
||||
{
|
||||
Expression: "(MIT AND GPL-3.0-only) AND (MIT AND GPL-3.0-only WITH Classpath-exception-2.0)",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "debian to spdx conversion",
|
||||
name: "with multiple URLs and expressions",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"GPL-2",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromURLs("MIT", "https://opensource.org/licenses/MIT", "https://spdx.org/licenses/MIT.html"),
|
||||
pkg.NewLicense("MIT AND GPL-3.0-only"),
|
||||
pkg.NewLicenseFromURLs("FakeLicense", "htts://someurl.com"),
|
||||
),
|
||||
},
|
||||
expected: &cyclonedx.Licenses{
|
||||
{License: &cyclonedx.License{ID: "GPL-2.0-only"}},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
Name: "FakeLicense",
|
||||
URL: "htts://someurl.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
Name: "MIT AND GPL-3.0-only",
|
||||
},
|
||||
},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
ID: "MIT",
|
||||
URL: "https://opensource.org/licenses/MIT",
|
||||
},
|
||||
},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
ID: "MIT",
|
||||
URL: "https://spdx.org/licenses/MIT.html",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with multiple values licenses are deduplicated",
|
||||
input: pkg.Package{
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("Apache-2"),
|
||||
pkg.NewLicense("Apache-2.0"),
|
||||
),
|
||||
},
|
||||
expected: &cyclonedx.Licenses{
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
ID: "Apache-2.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with multiple URLs and single with no URLs",
|
||||
input: pkg.Package{
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT"),
|
||||
pkg.NewLicenseFromURLs("MIT", "https://opensource.org/licenses/MIT", "https://spdx.org/licenses/MIT.html"),
|
||||
pkg.NewLicense("MIT AND GPL-3.0-only"),
|
||||
),
|
||||
},
|
||||
expected: &cyclonedx.Licenses{
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
ID: "MIT",
|
||||
URL: "https://opensource.org/licenses/MIT",
|
||||
},
|
||||
},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
ID: "MIT",
|
||||
URL: "https://spdx.org/licenses/MIT.html",
|
||||
},
|
||||
},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
Name: "MIT AND GPL-3.0-only",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// TODO: do we drop the non SPDX ID license and do a single expression
|
||||
// OR do we keep the non SPDX ID license and do multiple licenses where the complex
|
||||
// expressions are set as the NAME field?
|
||||
//{
|
||||
// name: "with multiple complex SPDX license expression and a non spdx id",
|
||||
// input: pkg.Package{
|
||||
// Licenses: []pkg.License{
|
||||
// {
|
||||
// SPDXExpression: "MIT AND GPL-3.0-only",
|
||||
// },
|
||||
// {
|
||||
// SPDXExpression: "MIT AND GPL-3.0-only WITH Classpath-exception-2.0",
|
||||
// },
|
||||
// {
|
||||
// Value: "FOOBAR",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// expected: &cyclonedx.Licenses{
|
||||
// {
|
||||
// Expression: "(MIT AND GPL-3.0-only) AND (MIT AND GPL-3.0-only WITH Classpath-exception-2.0)",
|
||||
// },
|
||||
// },
|
||||
//},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
@@ -84,3 +196,81 @@ func Test_encodeLicense(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeLicenses(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input *cyclonedx.Component
|
||||
expected []pkg.License
|
||||
}{
|
||||
{
|
||||
name: "no licenses",
|
||||
input: &cyclonedx.Component{},
|
||||
expected: []pkg.License{},
|
||||
},
|
||||
{
|
||||
name: "no SPDX license ID or expression",
|
||||
input: &cyclonedx.Component{
|
||||
Licenses: &cyclonedx.Licenses{
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
Name: "RandomLicense",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []pkg.License{
|
||||
{
|
||||
Value: "RandomLicense",
|
||||
// CycloneDX specification doesn't give a field for determining the license type
|
||||
Type: license.Declared,
|
||||
URLs: internal.NewStringSet(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with SPDX license ID",
|
||||
input: &cyclonedx.Component{
|
||||
Licenses: &cyclonedx.Licenses{
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
ID: "MIT",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []pkg.License{
|
||||
{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
URLs: internal.NewStringSet(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with complex SPDX license expression",
|
||||
input: &cyclonedx.Component{
|
||||
Licenses: &cyclonedx.Licenses{
|
||||
{
|
||||
License: &cyclonedx.License{},
|
||||
Expression: "MIT AND GPL-3.0-only WITH Classpath-exception-2.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []pkg.License{
|
||||
{
|
||||
Value: "MIT AND GPL-3.0-only WITH Classpath-exception-2.0",
|
||||
SPDXExpression: "MIT AND GPL-3.0-only WITH Classpath-exception-2.0",
|
||||
Type: license.Declared,
|
||||
URLs: internal.NewStringSet(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, decodeLicenses(test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func License(p pkg.Package) string {
|
||||
func License(p pkg.Package) (concluded, declared 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,35 +18,70 @@ 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 len(p.Licenses) == 0 {
|
||||
return NONE
|
||||
if p.Licenses.Empty() {
|
||||
return NOASSERTION, NOASSERTION
|
||||
}
|
||||
|
||||
// take all licenses and assume an AND expression; for information about license expressions see https://spdx.github.io/spdx-spec/appendix-IV-SPDX-license-expressions/
|
||||
parsedLicenses := parseLicenses(p.Licenses)
|
||||
// take all licenses and assume an AND expression;
|
||||
// for information about license expressions see:
|
||||
// https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/
|
||||
pc, pd := parseLicenses(p.Licenses.ToSlice())
|
||||
|
||||
for i, v := range parsedLicenses {
|
||||
for i, v := range pc {
|
||||
if strings.HasPrefix(v, spdxlicense.LicenseRefPrefix) {
|
||||
parsedLicenses[i] = SanitizeElementID(v)
|
||||
pc[i] = SanitizeElementID(v)
|
||||
}
|
||||
}
|
||||
|
||||
if len(parsedLicenses) == 0 {
|
||||
for i, v := range pd {
|
||||
if strings.HasPrefix(v, spdxlicense.LicenseRefPrefix) {
|
||||
pd[i] = SanitizeElementID(v)
|
||||
}
|
||||
}
|
||||
|
||||
return joinLicenses(pc), joinLicenses(pd)
|
||||
}
|
||||
|
||||
func joinLicenses(licenses []string) string {
|
||||
if len(licenses) == 0 {
|
||||
return NOASSERTION
|
||||
}
|
||||
|
||||
return strings.Join(parsedLicenses, " AND ")
|
||||
var newLicenses []string
|
||||
|
||||
for _, v := range licenses {
|
||||
// check if license does not start or end with parens
|
||||
if !strings.HasPrefix(v, "(") && !strings.HasSuffix(v, ")") {
|
||||
// if license contains AND, OR, or WITH, then wrap in parens
|
||||
if strings.Contains(v, " AND ") ||
|
||||
strings.Contains(v, " OR ") ||
|
||||
strings.Contains(v, " WITH ") {
|
||||
newLicenses = append(newLicenses, "("+v+")")
|
||||
continue
|
||||
}
|
||||
}
|
||||
newLicenses = append(newLicenses, v)
|
||||
}
|
||||
|
||||
return strings.Join(newLicenses, " AND ")
|
||||
}
|
||||
|
||||
func parseLicenses(raw []string) (parsedLicenses []string) {
|
||||
func parseLicenses(raw []pkg.License) (concluded, declared []string) {
|
||||
for _, l := range raw {
|
||||
if value, exists := spdxlicense.ID(l); exists {
|
||||
parsedLicenses = append(parsedLicenses, value)
|
||||
var candidate string
|
||||
if l.SPDXExpression != "" {
|
||||
candidate = l.SPDXExpression
|
||||
} else {
|
||||
// we did not find a valid SPDX license ID so treat as separate license
|
||||
otherLicense := spdxlicense.LicenseRefPrefix + l
|
||||
parsedLicenses = append(parsedLicenses, otherLicense)
|
||||
candidate = spdxlicense.LicenseRefPrefix + l.Value
|
||||
}
|
||||
|
||||
switch l.Type {
|
||||
case license.Concluded:
|
||||
concluded = append(concluded, candidate)
|
||||
case license.Declared:
|
||||
declared = append(declared, candidate)
|
||||
}
|
||||
}
|
||||
return
|
||||
return concluded, declared
|
||||
}
|
||||
|
||||
@@ -9,77 +9,120 @@ import (
|
||||
)
|
||||
|
||||
func Test_License(t *testing.T) {
|
||||
type expected struct {
|
||||
concluded string
|
||||
declared string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected string
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
name: "no licenses",
|
||||
input: pkg.Package{},
|
||||
expected: NONE,
|
||||
name: "no licenses",
|
||||
input: pkg.Package{},
|
||||
expected: expected{
|
||||
concluded: "NOASSERTION",
|
||||
declared: "NOASSERTION",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no SPDX licenses",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"made-up",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicense("made-up")),
|
||||
},
|
||||
expected: expected{
|
||||
concluded: "NOASSERTION",
|
||||
declared: "LicenseRef-made-up",
|
||||
},
|
||||
expected: "LicenseRef-made-up",
|
||||
},
|
||||
{
|
||||
name: "with SPDX license",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicense("MIT")),
|
||||
},
|
||||
expected: struct {
|
||||
concluded string
|
||||
declared string
|
||||
}{
|
||||
concluded: "NOASSERTION",
|
||||
declared: "MIT",
|
||||
},
|
||||
expected: "MIT",
|
||||
},
|
||||
{
|
||||
name: "with SPDX license expression",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
"GPL-3.0",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT"),
|
||||
pkg.NewLicense("GPL-3.0-only"),
|
||||
),
|
||||
},
|
||||
expected: "MIT AND GPL-3.0-only",
|
||||
},
|
||||
{
|
||||
name: "cap insensitive",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"gpl-3.0",
|
||||
},
|
||||
expected: expected{
|
||||
concluded: "NOASSERTION",
|
||||
// because we sort licenses alphabetically GPL ends up at the start
|
||||
declared: "GPL-3.0-only AND MIT",
|
||||
},
|
||||
expected: "GPL-3.0-only",
|
||||
},
|
||||
{
|
||||
name: "debian to spdx conversion",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"GPL-2",
|
||||
},
|
||||
},
|
||||
expected: "GPL-2.0-only",
|
||||
},
|
||||
{
|
||||
name: "includes valid LicenseRef-",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"one thing first",
|
||||
"two things/#$^second",
|
||||
"MIT",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("one thing first"),
|
||||
pkg.NewLicense("two things/#$^second"),
|
||||
pkg.NewLicense("MIT"),
|
||||
),
|
||||
},
|
||||
expected: expected{
|
||||
concluded: "NOASSERTION",
|
||||
// because we separate licenses between valid SPDX and non valid, valid ID always end at the front
|
||||
declared: "MIT AND LicenseRef-one-thing-first AND LicenseRef-two-things----second",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "join parentheses correctly",
|
||||
input: pkg.Package{
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("one thing first"),
|
||||
pkg.NewLicense("MIT AND GPL-3.0-only"),
|
||||
pkg.NewLicense("MIT OR APACHE-2.0"),
|
||||
),
|
||||
},
|
||||
expected: expected{
|
||||
concluded: "NOASSERTION",
|
||||
// because we separate licenses between valid SPDX and non valid, valid ID always end at the front
|
||||
declared: "(MIT AND GPL-3.0-only) AND (MIT OR APACHE-2.0) AND LicenseRef-one-thing-first",
|
||||
},
|
||||
expected: "LicenseRef-one-thing-first AND LicenseRef-two-things----second AND MIT",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, License(test.input))
|
||||
c, d := License(test.input)
|
||||
assert.Equal(t, test.expected.concluded, c)
|
||||
assert.Equal(t, test.expected.declared, d)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_joinLicenses(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "multiple licenses",
|
||||
args: []string{"MIT", "GPL-3.0-only"},
|
||||
want: "MIT AND GPL-3.0-only",
|
||||
},
|
||||
{
|
||||
name: "multiple licenses with complex expressions",
|
||||
args: []string{"MIT AND Apache", "GPL-3.0-only"},
|
||||
want: "(MIT AND Apache) AND GPL-3.0-only",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, joinLicenses(tt.args), "joinLicenses(%v)", tt.args)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ func SourceInfo(p pkg.Package) string {
|
||||
answer = "acquired package info from linux kernel module files"
|
||||
case pkg.NixPkg:
|
||||
answer = "acquired package info from nix store path"
|
||||
case pkg.Rpkg:
|
||||
answer = "acquired package info from R-package DESCRIPTION file"
|
||||
default:
|
||||
answer = "acquired package info from the following paths"
|
||||
}
|
||||
|
||||
@@ -223,6 +223,14 @@ func Test_SourceInfo(t *testing.T) {
|
||||
"from nix store path",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.Rpkg,
|
||||
},
|
||||
expected: []string{
|
||||
"acquired package info from R-package DESCRIPTION file",
|
||||
},
|
||||
},
|
||||
}
|
||||
var pkgTypes []pkg.Type
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -170,7 +170,8 @@ func toPackages(catalog *pkg.Collection, sbom sbom.SBOM) (results []*spdx.Packag
|
||||
// If the Concluded License is not the same as the Declared License, a written explanation should be provided
|
||||
// in the Comments on License field (section 7.16). With respect to NOASSERTION, a written explanation in
|
||||
// the Comments on License field (section 7.16) is preferred.
|
||||
license := License(p)
|
||||
// extract these correctly to the spdx license format
|
||||
concluded, declared := License(p)
|
||||
|
||||
// two ways to get filesAnalyzed == true:
|
||||
// 1. syft has generated a sha1 digest for the package itself - usually in the java cataloger
|
||||
@@ -274,7 +275,7 @@ func toPackages(catalog *pkg.Collection, sbom sbom.SBOM) (results []*spdx.Packag
|
||||
// Cardinality: mandatory, one
|
||||
// Purpose: Contain the license the SPDX file creator has concluded as governing the
|
||||
// package or alternative values, if the governing license cannot be determined.
|
||||
PackageLicenseConcluded: license,
|
||||
PackageLicenseConcluded: concluded,
|
||||
|
||||
// 7.14: All Licenses Info from Files: SPDX License Expression, "NONE" or "NOASSERTION"
|
||||
// Cardinality: mandatory, one or many if filesAnalyzed is true / omitted;
|
||||
@@ -286,7 +287,7 @@ func toPackages(catalog *pkg.Collection, sbom sbom.SBOM) (results []*spdx.Packag
|
||||
// Purpose: List the licenses that have been declared by the authors of the package.
|
||||
// Any license information that does not originate from the package authors, e.g. license
|
||||
// information from a third party repository, should not be included in this field.
|
||||
PackageLicenseDeclared: license,
|
||||
PackageLicenseDeclared: declared,
|
||||
|
||||
// 7.16: Comments on License
|
||||
// Cardinality: optional, one
|
||||
@@ -534,10 +535,18 @@ func toFileTypes(metadata *source.FileMetadata) (ty []string) {
|
||||
return ty
|
||||
}
|
||||
|
||||
// other licenses are for licenses from the pkg.Package that do not have an SPDXExpression
|
||||
// field. The spdxexpression field is only filled given a validated Value field.
|
||||
func toOtherLicenses(catalog *pkg.Collection) []*spdx.OtherLicense {
|
||||
licenses := map[string]bool{}
|
||||
for _, p := range catalog.Sorted() {
|
||||
for _, license := range parseLicenses(p.Licenses) {
|
||||
declaredLicenses, concludedLicenses := parseLicenses(p.Licenses.ToSlice())
|
||||
for _, license := range declaredLicenses {
|
||||
if strings.HasPrefix(license, spdxlicense.LicenseRefPrefix) {
|
||||
licenses[license] = true
|
||||
}
|
||||
}
|
||||
for _, license := range concludedLicenses {
|
||||
if strings.HasPrefix(license, spdxlicense.LicenseRefPrefix) {
|
||||
licenses[license] = true
|
||||
}
|
||||
@@ -549,12 +558,12 @@ func toOtherLicenses(catalog *pkg.Collection) []*spdx.OtherLicense {
|
||||
sorted := maps.Keys(licenses)
|
||||
slices.Sort(sorted)
|
||||
for _, license := range sorted {
|
||||
// separate the actual ID from the prefix
|
||||
// separate the found value from the prefix
|
||||
// this only contains licenses that are not found on the SPDX License List
|
||||
name := strings.TrimPrefix(license, spdxlicense.LicenseRefPrefix)
|
||||
result = append(result, &spdx.OtherLicense{
|
||||
LicenseIdentifier: SanitizeElementID(license),
|
||||
LicenseName: name,
|
||||
ExtractedText: NONE, // we probably should have some extracted text here, but this is good enough for now
|
||||
ExtractedText: name,
|
||||
})
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -448,46 +448,40 @@ func Test_OtherLicenses(t *testing.T) {
|
||||
{
|
||||
name: "no licenseRef",
|
||||
pkg: pkg.Package{
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(),
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "single licenseRef",
|
||||
pkg: pkg.Package{
|
||||
Licenses: []string{
|
||||
"un known",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("foobar"),
|
||||
),
|
||||
},
|
||||
expected: []*spdx.OtherLicense{
|
||||
{
|
||||
LicenseIdentifier: "LicenseRef-un-known",
|
||||
LicenseName: "un known",
|
||||
ExtractedText: NONE,
|
||||
LicenseIdentifier: "LicenseRef-foobar",
|
||||
ExtractedText: "foobar",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple licenseRef",
|
||||
pkg: pkg.Package{
|
||||
Licenses: []string{
|
||||
"un known",
|
||||
"not known %s",
|
||||
"MIT",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("internal made up license name"),
|
||||
pkg.NewLicense("new apple license 2.0"),
|
||||
),
|
||||
},
|
||||
expected: []*spdx.OtherLicense{
|
||||
{
|
||||
LicenseIdentifier: "LicenseRef-not-known--s",
|
||||
LicenseName: "not known %s",
|
||||
ExtractedText: NONE,
|
||||
LicenseIdentifier: "LicenseRef-internal-made-up-license-name",
|
||||
ExtractedText: "internal made up license name",
|
||||
},
|
||||
{
|
||||
LicenseIdentifier: "LicenseRef-un-known",
|
||||
LicenseName: "un known",
|
||||
ExtractedText: NONE,
|
||||
LicenseIdentifier: "LicenseRef-new-apple-license-2.0",
|
||||
ExtractedText: "new apple license 2.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/formats/common/util"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
@@ -279,7 +280,7 @@ func toSyftPackage(p *spdx.Package) *pkg.Package {
|
||||
Type: info.typ,
|
||||
Name: p.PackageName,
|
||||
Version: p.PackageVersion,
|
||||
Licenses: parseLicense(p.PackageLicenseDeclared),
|
||||
Licenses: pkg.NewLicenseSet(parseSPDXLicenses(p)...),
|
||||
CPEs: extractCPEs(p),
|
||||
PURL: info.purl.String(),
|
||||
Language: info.lang,
|
||||
@@ -292,6 +293,33 @@ func toSyftPackage(p *spdx.Package) *pkg.Package {
|
||||
return &sP
|
||||
}
|
||||
|
||||
func parseSPDXLicenses(p *spdx.Package) []pkg.License {
|
||||
licenses := make([]pkg.License, 0)
|
||||
|
||||
// concluded
|
||||
if p.PackageLicenseConcluded != NOASSERTION && p.PackageLicenseConcluded != NONE && p.PackageLicenseConcluded != "" {
|
||||
l := pkg.NewLicense(cleanSPDXID(p.PackageLicenseConcluded))
|
||||
l.Type = license.Concluded
|
||||
licenses = append(licenses, l)
|
||||
}
|
||||
|
||||
// declared
|
||||
if p.PackageLicenseDeclared != NOASSERTION && p.PackageLicenseDeclared != NONE && p.PackageLicenseDeclared != "" {
|
||||
l := pkg.NewLicense(cleanSPDXID(p.PackageLicenseDeclared))
|
||||
l.Type = license.Declared
|
||||
licenses = append(licenses, l)
|
||||
}
|
||||
|
||||
return licenses
|
||||
}
|
||||
|
||||
func cleanSPDXID(id string) string {
|
||||
if strings.HasPrefix(id, "LicenseRef-") {
|
||||
return strings.TrimPrefix(id, "LicenseRef-")
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface{}) {
|
||||
arch := info.qualifierValue(pkg.PURLQualifierArch)
|
||||
@@ -317,7 +345,6 @@ func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface
|
||||
OriginPackage: upstreamName,
|
||||
Maintainer: supplier,
|
||||
Version: p.PackageVersion,
|
||||
License: p.PackageLicenseDeclared,
|
||||
Architecture: arch,
|
||||
URL: p.PackageHomePage,
|
||||
Description: p.PackageDescription,
|
||||
@@ -330,17 +357,12 @@ func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface
|
||||
} else {
|
||||
epoch = &converted
|
||||
}
|
||||
license := p.PackageLicenseDeclared
|
||||
if license == "" {
|
||||
license = p.PackageLicenseConcluded
|
||||
}
|
||||
return pkg.RpmMetadataType, pkg.RpmMetadata{
|
||||
Name: p.PackageName,
|
||||
Version: p.PackageVersion,
|
||||
Epoch: epoch,
|
||||
Arch: arch,
|
||||
SourceRpm: upstreamValue,
|
||||
License: license,
|
||||
Vendor: originator,
|
||||
}
|
||||
case pkg.DebPkg:
|
||||
@@ -400,10 +422,3 @@ func extractCPEs(p *spdx.Package) (cpes []cpe.CPE) {
|
||||
}
|
||||
return cpes
|
||||
}
|
||||
|
||||
func parseLicense(l string) []string {
|
||||
if l == NOASSERTION || l == NONE {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(l, " AND ")
|
||||
}
|
||||
|
||||
+5
-5
@@ -2,10 +2,10 @@
|
||||
"$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json",
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.4",
|
||||
"serialNumber": "urn:uuid:redacted",
|
||||
"serialNumber": "urn:uuid:1b71a5b4-4bc5-4548-a51a-212e631976cd",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "timestamp:redacted",
|
||||
"timestamp": "2023-05-08T14:40:32-04:00",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "anchore",
|
||||
@@ -14,14 +14,14 @@
|
||||
}
|
||||
],
|
||||
"component": {
|
||||
"bom-ref": "redacted",
|
||||
"bom-ref": "163686ac6e30c752",
|
||||
"type": "file",
|
||||
"name": "/some/path"
|
||||
}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"bom-ref": "redacted",
|
||||
"bom-ref": "8c7e1242588c971a",
|
||||
"type": "library",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
@@ -58,7 +58,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"bom-ref": "redacted",
|
||||
"bom-ref": "pkg:deb/debian/package-2@2.0.1?package-id=db4abfe497c180d3",
|
||||
"type": "library",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
"$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json",
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.4",
|
||||
"serialNumber": "urn:uuid:redacted",
|
||||
"serialNumber": "urn:uuid:1695d6ae-0ddf-4e77-9c9d-74df1bdd8d5b",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "timestamp:redacted",
|
||||
"timestamp": "2023-05-08T14:40:32-04:00",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "anchore",
|
||||
@@ -14,15 +14,15 @@
|
||||
}
|
||||
],
|
||||
"component": {
|
||||
"bom-ref": "redacted",
|
||||
"bom-ref": "38160ebc2a6876e8",
|
||||
"type": "container",
|
||||
"name": "user-image-input",
|
||||
"version": "sha256:redacted"
|
||||
"version": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368"
|
||||
}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"bom-ref": "redacted",
|
||||
"bom-ref": "ec2e0c93617507ef",
|
||||
"type": "library",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
@@ -54,7 +54,7 @@
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:layerID",
|
||||
"value": "sha256:redacted"
|
||||
"value": "sha256:ab62016f9bec7286af65604081564cadeeb364a48faca2346c3f5a5a1f5ef777"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:path",
|
||||
@@ -63,7 +63,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"bom-ref": "redacted",
|
||||
"bom-ref": "pkg:deb/debian/package-2@2.0.1?package-id=958443e2d9304af4",
|
||||
"type": "library",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
@@ -84,7 +84,7 @@
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:layerID",
|
||||
"value": "sha256:redacted"
|
||||
"value": "sha256:f1803845b6747d94d6e4ecce2331457e5f1c4fb97de5216f392a76f4582f63b2"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:path",
|
||||
|
||||
BIN
Binary file not shown.
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_decodeXML(t *testing.T) {
|
||||
@@ -34,7 +35,7 @@ func Test_decodeXML(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.file, func(t *testing.T) {
|
||||
reader, err := os.Open("test-fixtures/" + test.file)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
if test.err {
|
||||
err = Format().Validate(reader)
|
||||
@@ -44,7 +45,7 @@ func Test_decodeXML(t *testing.T) {
|
||||
|
||||
bom, err := Format().Decode(reader)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
split := strings.SplitN(test.distro, ":", 2)
|
||||
name := split[0]
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:2939b822-b9cb-489d-8a8b-4431b755031d" version="1">
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:60f4e726-e884-4ae3-9b0e-18a918fbb02e" version="1">
|
||||
<metadata>
|
||||
<timestamp>2022-11-07T09:11:06-05:00</timestamp>
|
||||
<timestamp>2023-05-08T14:40:52-04:00</timestamp>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>anchore</vendor>
|
||||
@@ -14,7 +14,7 @@
|
||||
</component>
|
||||
</metadata>
|
||||
<components>
|
||||
<component bom-ref="1b1d0be59ac59d2c" type="library">
|
||||
<component bom-ref="8c7e1242588c971a" type="library">
|
||||
<name>package-1</name>
|
||||
<version>1.0.1</version>
|
||||
<licenses>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:2896b5ce-2016-49e8-a422-239d662846c7" version="1">
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:c8894728-c156-4fc5-8f5d-3e397eede5a7" version="1">
|
||||
<metadata>
|
||||
<timestamp>2022-11-07T09:11:06-05:00</timestamp>
|
||||
<timestamp>2023-05-08T14:40:52-04:00</timestamp>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>anchore</vendor>
|
||||
@@ -9,13 +9,13 @@
|
||||
<version>v0.42.0-bogus</version>
|
||||
</tool>
|
||||
</tools>
|
||||
<component bom-ref="522dc6b135a55bb4" type="container">
|
||||
<component bom-ref="38160ebc2a6876e8" type="container">
|
||||
<name>user-image-input</name>
|
||||
<version>sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368</version>
|
||||
</component>
|
||||
</metadata>
|
||||
<components>
|
||||
<component bom-ref="66ba429119b8bec6" type="library">
|
||||
<component bom-ref="ec2e0c93617507ef" type="library">
|
||||
<name>package-1</name>
|
||||
<version>1.0.1</version>
|
||||
<licenses>
|
||||
@@ -30,7 +30,7 @@
|
||||
<property name="syft:package:language">python</property>
|
||||
<property name="syft:package:metadataType">PythonPackageMetadata</property>
|
||||
<property name="syft:package:type">python</property>
|
||||
<property name="syft:location:0:layerID">sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59</property>
|
||||
<property name="syft:location:0:layerID">sha256:ab62016f9bec7286af65604081564cadeeb364a48faca2346c3f5a5a1f5ef777</property>
|
||||
<property name="syft:location:0:path">/somefile-1.txt</property>
|
||||
</properties>
|
||||
</component>
|
||||
@@ -43,7 +43,7 @@
|
||||
<property name="syft:package:foundBy">the-cataloger-2</property>
|
||||
<property name="syft:package:metadataType">DpkgMetadata</property>
|
||||
<property name="syft:package:type">deb</property>
|
||||
<property name="syft:location:0:layerID">sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec</property>
|
||||
<property name="syft:location:0:layerID">sha256:f1803845b6747d94d6e4ecce2331457e5f1c4fb97de5216f392a76f4582f63b2</property>
|
||||
<property name="syft:location:0:path">/somefile-2.txt</property>
|
||||
<property name="syft:metadata:installedSize">0</property>
|
||||
</properties>
|
||||
|
||||
BIN
Binary file not shown.
@@ -162,7 +162,9 @@ func populateImageCatalog(catalog *pkg.Collection, img *image.Image) {
|
||||
FoundBy: "the-cataloger-1",
|
||||
Language: pkg.Python,
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Licenses: []string{"MIT"},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT"),
|
||||
),
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
@@ -268,7 +270,9 @@ func newDirectoryCatalog() *pkg.Collection {
|
||||
),
|
||||
Language: pkg.Python,
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Licenses: []string{"MIT"},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT"),
|
||||
),
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
@@ -319,7 +323,9 @@ func newDirectoryCatalogWithAuthorField() *pkg.Collection {
|
||||
),
|
||||
Language: pkg.Python,
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Licenses: []string{"MIT"},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT"),
|
||||
),
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
|
||||
@@ -3,23 +3,23 @@
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "/some/path",
|
||||
"documentNamespace": "https://anchore.com/syft/dir/some/path-4029b5ec-6d70-4c0c-aedf-b61c8f5ea93c",
|
||||
"documentNamespace": "https://anchore.com/syft/dir/some/path-5ea40e59-d91a-4682-a016-da45ddd540e4",
|
||||
"creationInfo": {
|
||||
"licenseListVersion": "3.20",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-v0.42.0-bogus"
|
||||
],
|
||||
"created": "2023-05-02T18:24:17Z"
|
||||
"created": "2023-05-09T17:11:26Z"
|
||||
},
|
||||
"packages": [
|
||||
{
|
||||
"name": "package-1",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-1b1d0be59ac59d2c",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-9265397e5e15168a",
|
||||
"versionInfo": "1.0.1",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"sourceInfo": "acquired package info from installed python package manifest file: /some/path/pkg1",
|
||||
"licenseConcluded": "MIT",
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "MIT",
|
||||
"copyrightText": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
@@ -41,8 +41,8 @@
|
||||
"versionInfo": "2.0.1",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"sourceInfo": "acquired package info from DPKG DB: /some/path/pkg1",
|
||||
"licenseConcluded": "NONE",
|
||||
"licenseDeclared": "NONE",
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "NOASSERTION",
|
||||
"copyrightText": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
{
|
||||
|
||||
@@ -3,23 +3,23 @@
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "user-image-input",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-6b0c6ff8-0f5f-4d95-8c1b-eb966d400804",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-2cc737fb-af51-4e4b-9395-cceabcc305eb",
|
||||
"creationInfo": {
|
||||
"licenseListVersion": "3.20",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-v0.42.0-bogus"
|
||||
],
|
||||
"created": "2023-05-02T18:24:18Z"
|
||||
"created": "2023-05-09T17:11:26Z"
|
||||
},
|
||||
"packages": [
|
||||
{
|
||||
"name": "package-1",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"versionInfo": "1.0.1",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt",
|
||||
"licenseConcluded": "MIT",
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "MIT",
|
||||
"copyrightText": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
@@ -41,8 +41,8 @@
|
||||
"versionInfo": "2.0.1",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt",
|
||||
"licenseConcluded": "NONE",
|
||||
"licenseDeclared": "NONE",
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "NOASSERTION",
|
||||
"copyrightText": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
{
|
||||
|
||||
@@ -3,23 +3,23 @@
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "user-image-input",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-ec2f9b25-22ca-46b8-b7f4-484994fe126c",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-1de3ac0e-5829-4294-9198-8d8fcdb5dd51",
|
||||
"creationInfo": {
|
||||
"licenseListVersion": "3.20",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-v0.42.0-bogus"
|
||||
],
|
||||
"created": "2023-05-02T18:24:18Z"
|
||||
"created": "2023-05-09T17:11:26Z"
|
||||
},
|
||||
"packages": [
|
||||
{
|
||||
"name": "package-1",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"versionInfo": "1.0.1",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt",
|
||||
"licenseConcluded": "MIT",
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "MIT",
|
||||
"copyrightText": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
@@ -41,8 +41,8 @@
|
||||
"versionInfo": "2.0.1",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt",
|
||||
"licenseConcluded": "NONE",
|
||||
"licenseDeclared": "NONE",
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "NOASSERTION",
|
||||
"copyrightText": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
{
|
||||
@@ -152,32 +152,32 @@
|
||||
],
|
||||
"relationships": [
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"relatedSpdxElement": "SPDXRef-File-f1-5265a4dde3edbf7c",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"relatedSpdxElement": "SPDXRef-File-z1-f5-839d99ee67d9d174",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"relatedSpdxElement": "SPDXRef-File-a1-f6-9c2f7510199b17f6",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"relatedSpdxElement": "SPDXRef-File-d2-f4-c641caa71518099f",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"relatedSpdxElement": "SPDXRef-File-d1-f3-c6f5b29dca12661f",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"relatedSpdxElement": "SPDXRef-File-f2-f9e49132a4b96ccd",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
|
||||
Binary file not shown.
@@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.3
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: foobar/baz
|
||||
DocumentNamespace: https://anchore.com/syft/dir/foobar/baz-9c1f31fb-7c72-40a6-8c81-3a08590000a2
|
||||
DocumentNamespace: https://anchore.com/syft/dir/foobar/baz-1813dede-1ac5-4c44-a640-4c56e213d575
|
||||
LicenseListVersion: 3.20
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-v0.42.0-bogus
|
||||
Created: 2023-05-02T18:24:33Z
|
||||
Created: 2023-05-09T17:11:49Z
|
||||
|
||||
##### Package: @at-sign
|
||||
|
||||
@@ -15,8 +15,8 @@ SPDXID: SPDXRef-Package---at-sign-3732f7a5679bdec4
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from the following paths:
|
||||
PackageLicenseConcluded: NONE
|
||||
PackageLicenseDeclared: NONE
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: NOASSERTION
|
||||
PackageCopyrightText: NOASSERTION
|
||||
|
||||
##### Package: some/slashes
|
||||
@@ -26,8 +26,8 @@ SPDXID: SPDXRef-Package--some-slashes-1345166d4801153b
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from the following paths:
|
||||
PackageLicenseConcluded: NONE
|
||||
PackageLicenseDeclared: NONE
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: NOASSERTION
|
||||
PackageCopyrightText: NOASSERTION
|
||||
|
||||
##### Package: under_scores
|
||||
@@ -37,8 +37,8 @@ SPDXID: SPDXRef-Package--under-scores-290d5c77210978c1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from the following paths:
|
||||
PackageLicenseConcluded: NONE
|
||||
PackageLicenseDeclared: NONE
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: NOASSERTION
|
||||
PackageCopyrightText: NOASSERTION
|
||||
|
||||
##### Relationships
|
||||
|
||||
@@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.3
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: user-image-input
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-5be37b11-b99a-47ff-8725-3984e323d129
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-96ea886a-3297-4847-b211-6da405ff1f8f
|
||||
LicenseListVersion: 3.20
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-v0.42.0-bogus
|
||||
Created: 2023-05-02T18:24:33Z
|
||||
Created: 2023-05-09T17:11:49Z
|
||||
|
||||
##### Unpackaged files
|
||||
|
||||
@@ -54,8 +54,8 @@ PackageVersion: 2.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from DPKG DB: /somefile-2.txt
|
||||
PackageLicenseConcluded: NONE
|
||||
PackageLicenseDeclared: NONE
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: NOASSERTION
|
||||
PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
|
||||
ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||
@@ -63,12 +63,12 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||
##### Package: package-1
|
||||
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1-66ba429119b8bec6
|
||||
SPDXID: SPDXRef-Package-python-package-1-125840abc1c66dd7
|
||||
PackageVersion: 1.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from installed python package manifest file: /somefile-1.txt
|
||||
PackageLicenseConcluded: MIT
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: MIT
|
||||
PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:1:*:*:*:*:*:*:*
|
||||
@@ -76,11 +76,11 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-1
|
||||
|
||||
##### Relationships
|
||||
|
||||
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-File-f1-5265a4dde3edbf7c
|
||||
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-File-z1-f5-839d99ee67d9d174
|
||||
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-File-a1-f6-9c2f7510199b17f6
|
||||
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-File-d2-f4-c641caa71518099f
|
||||
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-File-d1-f3-c6f5b29dca12661f
|
||||
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-File-f2-f9e49132a4b96ccd
|
||||
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-f1-5265a4dde3edbf7c
|
||||
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-z1-f5-839d99ee67d9d174
|
||||
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-a1-f6-9c2f7510199b17f6
|
||||
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-d2-f4-c641caa71518099f
|
||||
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-d1-f3-c6f5b29dca12661f
|
||||
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-f2-f9e49132a4b96ccd
|
||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DOCUMENT
|
||||
|
||||
|
||||
+6
-6
@@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.3
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: /some/path
|
||||
DocumentNamespace: https://anchore.com/syft/dir/some/path-0f346656-6d10-4dec-b549-a256468cbd35
|
||||
DocumentNamespace: https://anchore.com/syft/dir/some/path-f7bdb1ee-7fef-48e7-a386-6ee3836d4a28
|
||||
LicenseListVersion: 3.20
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-v0.42.0-bogus
|
||||
Created: 2023-05-02T18:24:33Z
|
||||
Created: 2023-05-09T17:11:49Z
|
||||
|
||||
##### Package: package-2
|
||||
|
||||
@@ -16,8 +16,8 @@ PackageVersion: 2.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from DPKG DB: /some/path/pkg1
|
||||
PackageLicenseConcluded: NONE
|
||||
PackageLicenseDeclared: NONE
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: NOASSERTION
|
||||
PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
|
||||
ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||
@@ -25,12 +25,12 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||
##### Package: package-1
|
||||
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1-1b1d0be59ac59d2c
|
||||
SPDXID: SPDXRef-Package-python-package-1-9265397e5e15168a
|
||||
PackageVersion: 1.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from installed python package manifest file: /some/path/pkg1
|
||||
PackageLicenseConcluded: MIT
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: MIT
|
||||
PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
|
||||
|
||||
+6
-6
@@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.3
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: user-image-input
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-4ce1e7c7-642f-4428-bb44-1b48b8edf74d
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-44d44a85-2207-4b51-bd73-d0c7b080f6d3
|
||||
LicenseListVersion: 3.20
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-v0.42.0-bogus
|
||||
Created: 2023-05-02T18:24:33Z
|
||||
Created: 2023-05-09T17:11:49Z
|
||||
|
||||
##### Package: package-2
|
||||
|
||||
@@ -16,8 +16,8 @@ PackageVersion: 2.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from DPKG DB: /somefile-2.txt
|
||||
PackageLicenseConcluded: NONE
|
||||
PackageLicenseDeclared: NONE
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: NOASSERTION
|
||||
PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
|
||||
ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||
@@ -25,12 +25,12 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||
##### Package: package-1
|
||||
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1-66ba429119b8bec6
|
||||
SPDXID: SPDXRef-Package-python-package-1-125840abc1c66dd7
|
||||
PackageVersion: 1.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from installed python package manifest file: /somefile-1.txt
|
||||
PackageLicenseConcluded: MIT
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: MIT
|
||||
PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:1:*:*:*:*:*:*:*
|
||||
|
||||
BIN
Binary file not shown.
@@ -5,6 +5,10 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/formats/syftjson/model"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
@@ -18,5 +22,27 @@ func decoder(reader io.Reader) (*sbom.SBOM, error) {
|
||||
return nil, fmt.Errorf("unable to decode syft-json: %w", err)
|
||||
}
|
||||
|
||||
if err := checkSupportedSchema(doc.Schema.Version, internal.JSONSchemaVersion); err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
|
||||
return toSyftModel(doc)
|
||||
}
|
||||
|
||||
func checkSupportedSchema(documentVerion string, parserVersion string) error {
|
||||
documentV, err := semver.NewVersion(documentVerion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error comparing document schema version with parser schema version: %w", err)
|
||||
}
|
||||
|
||||
parserV, err := semver.NewVersion(parserVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error comparing document schema version with parser schema version: %w", err)
|
||||
}
|
||||
|
||||
if documentV.GreaterThan(parserV) {
|
||||
return fmt.Errorf("document has schema version %s, but parser has older schema version (%s)", documentVerion, parserVersion)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package syftjson
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -49,3 +51,38 @@ func TestEncodeDecodeCycle(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutOfDateParser(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
documentVersion string
|
||||
parserVersion string
|
||||
want error
|
||||
}{{
|
||||
name: "no warning when doc version is older",
|
||||
documentVersion: "1.0.9",
|
||||
parserVersion: "3.1.0",
|
||||
}, {
|
||||
name: "warning when parser is older",
|
||||
documentVersion: "4.3.2",
|
||||
parserVersion: "3.1.0",
|
||||
want: fmt.Errorf("document has schema version %s, but parser has older schema version (%s)", "4.3.2", "3.1.0"),
|
||||
}, {
|
||||
name: "warning when document version is unparseable",
|
||||
documentVersion: "some-nonsense",
|
||||
parserVersion: "3.1.0",
|
||||
want: fmt.Errorf("error comparing document schema version with parser schema version: %w", errors.New("Invalid Semantic Version")),
|
||||
}, {
|
||||
name: "warning when parser version is unparseable",
|
||||
documentVersion: "7.1.0",
|
||||
parserVersion: "some-nonsense",
|
||||
want: fmt.Errorf("error comparing document schema version with parser schema version: %w", errors.New("Invalid Semantic Version")),
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := checkSupportedSchema(tt.documentVersion, tt.parserVersion)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
||||
FoundBy: "the-cataloger-1",
|
||||
Language: pkg.Python,
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Licenses: []string{"MIT"},
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicense("MIT")),
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
@@ -103,26 +103,38 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
||||
Packages: catalog,
|
||||
FileMetadata: map[source.Coordinates]source.FileMetadata{
|
||||
source.NewLocation("/a/place").Coordinates: {
|
||||
Mode: 0775,
|
||||
FileInfo: stereoFile.ManualInfo{
|
||||
NameValue: "/a/place",
|
||||
ModeValue: 0775,
|
||||
},
|
||||
Type: stereoFile.TypeDirectory,
|
||||
UserID: 0,
|
||||
GroupID: 0,
|
||||
},
|
||||
source.NewLocation("/a/place/a").Coordinates: {
|
||||
Mode: 0775,
|
||||
FileInfo: stereoFile.ManualInfo{
|
||||
NameValue: "/a/place/a",
|
||||
ModeValue: 0775,
|
||||
},
|
||||
Type: stereoFile.TypeRegular,
|
||||
UserID: 0,
|
||||
GroupID: 0,
|
||||
},
|
||||
source.NewLocation("/b").Coordinates: {
|
||||
Mode: 0775,
|
||||
FileInfo: stereoFile.ManualInfo{
|
||||
NameValue: "/b",
|
||||
ModeValue: 0775,
|
||||
},
|
||||
Type: stereoFile.TypeSymLink,
|
||||
LinkDestination: "/c",
|
||||
UserID: 0,
|
||||
GroupID: 0,
|
||||
},
|
||||
source.NewLocation("/b/place/b").Coordinates: {
|
||||
Mode: 0644,
|
||||
FileInfo: stereoFile.ManualInfo{
|
||||
NameValue: "/b/place/b",
|
||||
ModeValue: 0644,
|
||||
},
|
||||
Type: stereoFile.TypeRegular,
|
||||
UserID: 1,
|
||||
GroupID: 2,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"reflect"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
@@ -27,12 +28,50 @@ type PackageBasicData struct {
|
||||
Type pkg.Type `json:"type"`
|
||||
FoundBy string `json:"foundBy"`
|
||||
Locations []source.Location `json:"locations"`
|
||||
Licenses []string `json:"licenses"`
|
||||
Licenses licenses `json:"licenses"`
|
||||
Language pkg.Language `json:"language"`
|
||||
CPEs []string `json:"cpes"`
|
||||
PURL string `json:"purl"`
|
||||
}
|
||||
|
||||
type licenses []License
|
||||
|
||||
type License struct {
|
||||
Value string `json:"value"`
|
||||
SPDXExpression string `json:"spdxExpression"`
|
||||
Type license.Type `json:"type"`
|
||||
URLs []string `json:"urls"`
|
||||
Locations []source.Location `json:"locations"`
|
||||
}
|
||||
|
||||
func newModelLicensesFromValues(licenses []string) (ml []License) {
|
||||
for _, v := range licenses {
|
||||
expression, err := license.ParseExpression(v)
|
||||
if err != nil {
|
||||
log.Trace("could not find valid spdx expression for %s: %w", v, err)
|
||||
}
|
||||
ml = append(ml, License{
|
||||
Value: v,
|
||||
SPDXExpression: expression,
|
||||
Type: license.Declared,
|
||||
})
|
||||
}
|
||||
return ml
|
||||
}
|
||||
|
||||
func (f *licenses) UnmarshalJSON(b []byte) error {
|
||||
var licenses []License
|
||||
if err := json.Unmarshal(b, &licenses); err != nil {
|
||||
var simpleLicense []string
|
||||
if err := json.Unmarshal(b, &simpleLicense); err != nil {
|
||||
return fmt.Errorf("unable to unmarshal license: %w", err)
|
||||
}
|
||||
licenses = newModelLicensesFromValues(simpleLicense)
|
||||
}
|
||||
*f = licenses
|
||||
return nil
|
||||
}
|
||||
|
||||
// PackageCustomData contains ambiguous values (type-wise) from pkg.Package.
|
||||
type PackageCustomData struct {
|
||||
MetadataType pkg.MetadataType `json:"metadataType,omitempty"`
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
@@ -30,7 +31,14 @@ func TestUnmarshalPackageGolang(t *testing.T) {
|
||||
"path": "/Users/hal/go/bin/syft"
|
||||
}
|
||||
],
|
||||
"licenses": [],
|
||||
"licenses": [
|
||||
{
|
||||
"value": "MIT",
|
||||
"spdxExpression": "MIT",
|
||||
"type": "declared",
|
||||
"url": []
|
||||
}
|
||||
],
|
||||
"language": "go",
|
||||
"cpes": [],
|
||||
"purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0",
|
||||
@@ -61,7 +69,20 @@ func TestUnmarshalPackageGolang(t *testing.T) {
|
||||
"path": "/Users/hal/go/bin/syft"
|
||||
}
|
||||
],
|
||||
"licenses": [],
|
||||
"licenses": [
|
||||
{
|
||||
"value": "MIT",
|
||||
"spdxExpression": "MIT",
|
||||
"type": "declared",
|
||||
"url": ["https://www.github.com"]
|
||||
},
|
||||
{
|
||||
"value": "MIT",
|
||||
"spdxExpression": "MIT",
|
||||
"type": "declared",
|
||||
"locations": [{"path": "/Users/hal/go/bin/syft"}]
|
||||
}
|
||||
],
|
||||
"language": "go",
|
||||
"cpes": [],
|
||||
"purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0"
|
||||
@@ -71,6 +92,83 @@ func TestUnmarshalPackageGolang(t *testing.T) {
|
||||
assert.Empty(t, p.Metadata)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can handle package with []string licenses",
|
||||
packageData: []byte(`{
|
||||
"id": "8b594519bc23da50",
|
||||
"name": "gopkg.in/square/go-jose.v2",
|
||||
"version": "v2.6.0",
|
||||
"type": "go-module",
|
||||
"foundBy": "go-mod-cataloger",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/Users/hal/go/bin/syft"
|
||||
}
|
||||
],
|
||||
"licenses": ["MIT", "Apache-2.0"],
|
||||
"language": "go",
|
||||
"cpes": [],
|
||||
"purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0"
|
||||
}`),
|
||||
assert: func(p *Package) {
|
||||
assert.Equal(t, licenses{
|
||||
{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
},
|
||||
{
|
||||
Value: "Apache-2.0",
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: license.Declared,
|
||||
},
|
||||
}, p.Licenses)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can handle package with []pkg.License licenses",
|
||||
packageData: []byte(`{
|
||||
"id": "8b594519bc23da50",
|
||||
"name": "gopkg.in/square/go-jose.v2",
|
||||
"version": "v2.6.0",
|
||||
"type": "go-module",
|
||||
"foundBy": "go-mod-cataloger",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/Users/hal/go/bin/syft"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
{
|
||||
"value": "MIT",
|
||||
"spdxExpression": "MIT",
|
||||
"type": "declared"
|
||||
},
|
||||
{
|
||||
"value": "Apache-2.0",
|
||||
"spdxExpression": "Apache-2.0",
|
||||
"type": "declared"
|
||||
}
|
||||
],
|
||||
"language": "go",
|
||||
"cpes": [],
|
||||
"purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0"
|
||||
}`),
|
||||
assert: func(p *Package) {
|
||||
assert.Equal(t, licenses{
|
||||
{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
},
|
||||
{
|
||||
Value: "Apache-2.0",
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: license.Declared,
|
||||
},
|
||||
}, p.Licenses)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -152,9 +250,6 @@ func Test_unpackMetadata(t *testing.T) {
|
||||
"layerID": "sha256:74ddd0ec08fa43d09f32636ba91a0a3053b02cb4627c35051aff89f853606b59"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
"GPLv2+"
|
||||
],
|
||||
"language": "",
|
||||
"cpes": [
|
||||
"cpe:2.3:a:centos:acl:2.2.53-1.el8:*:*:*:*:*:*:*",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "1b1d0be59ac59d2c",
|
||||
"id": "9265397e5e15168a",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "python",
|
||||
@@ -12,7 +12,13 @@
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
"MIT"
|
||||
{
|
||||
"value": "MIT",
|
||||
"spdxExpression": "MIT",
|
||||
"type": "declared",
|
||||
"urls": [],
|
||||
"locations": []
|
||||
}
|
||||
],
|
||||
"language": "python",
|
||||
"cpes": [
|
||||
@@ -23,7 +29,6 @@
|
||||
"metadata": {
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"license": "",
|
||||
"author": "",
|
||||
"authorEmail": "",
|
||||
"platform": "",
|
||||
@@ -87,5 +92,9 @@
|
||||
"configuration": {
|
||||
"config-key": "config-value"
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "8.0.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-8.0.0.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "304a5a8e5958a49d",
|
||||
"id": "271e49ba46e0b601",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "python",
|
||||
@@ -12,7 +12,13 @@
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
"MIT"
|
||||
{
|
||||
"value": "MIT",
|
||||
"spdxExpression": "MIT",
|
||||
"type": "declared",
|
||||
"urls": [],
|
||||
"locations": []
|
||||
}
|
||||
],
|
||||
"language": "python",
|
||||
"cpes": [
|
||||
@@ -23,7 +29,6 @@
|
||||
"metadata": {
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"license": "",
|
||||
"author": "",
|
||||
"authorEmail": "",
|
||||
"platform": "",
|
||||
@@ -187,5 +192,9 @@
|
||||
"configuration": {
|
||||
"config-key": "config-value"
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "8.0.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-8.0.0.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "66ba429119b8bec6",
|
||||
"id": "125840abc1c66dd7",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "python",
|
||||
@@ -9,11 +9,17 @@
|
||||
"locations": [
|
||||
{
|
||||
"path": "/somefile-1.txt",
|
||||
"layerID": "sha256:7e139310bd6ce0956d65a70d26a6d31b240a4f47094a831638f05d381b6c424a"
|
||||
"layerID": "sha256:ab62016f9bec7286af65604081564cadeeb364a48faca2346c3f5a5a1f5ef777"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
"MIT"
|
||||
{
|
||||
"value": "MIT",
|
||||
"spdxExpression": "MIT",
|
||||
"type": "declared",
|
||||
"urls": [],
|
||||
"locations": []
|
||||
}
|
||||
],
|
||||
"language": "python",
|
||||
"cpes": [
|
||||
@@ -24,7 +30,6 @@
|
||||
"metadata": {
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"license": "",
|
||||
"author": "",
|
||||
"authorEmail": "",
|
||||
"platform": "",
|
||||
@@ -40,7 +45,7 @@
|
||||
"locations": [
|
||||
{
|
||||
"path": "/somefile-2.txt",
|
||||
"layerID": "sha256:cc833bf31a480c064d65ca67ee37f77f0d0c8ab98eedde7b286ad1ef6f5bdcac"
|
||||
"layerID": "sha256:f1803845b6747d94d6e4ecce2331457e5f1c4fb97de5216f392a76f4582f63b2"
|
||||
}
|
||||
],
|
||||
"licenses": [],
|
||||
@@ -64,11 +69,11 @@
|
||||
],
|
||||
"artifactRelationships": [],
|
||||
"source": {
|
||||
"id": "0af8fa79f5497297e4e32f3e03de14ac20ad695159df0ac8373e6543614b9a50",
|
||||
"id": "c8ac88bbaf3d1c036f6a1d601c3d52bafbf05571c97d68322e7cb3a7ecaa304f",
|
||||
"type": "image",
|
||||
"target": {
|
||||
"userInput": "user-image-input",
|
||||
"imageID": "sha256:0cb4395791986bda17562bd6f76811bb6f163f686e198397197ef8241bed58df",
|
||||
"imageID": "sha256:a3c61dc134d2f31b415c50324e75842d7f91622f39a89468e51938330b3fd3af",
|
||||
"manifestDigest": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"tags": [
|
||||
@@ -78,17 +83,17 @@
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:7e139310bd6ce0956d65a70d26a6d31b240a4f47094a831638f05d381b6c424a",
|
||||
"digest": "sha256:ab62016f9bec7286af65604081564cadeeb364a48faca2346c3f5a5a1f5ef777",
|
||||
"size": 22
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:cc833bf31a480c064d65ca67ee37f77f0d0c8ab98eedde7b286ad1ef6f5bdcac",
|
||||
"digest": "sha256:f1803845b6747d94d6e4ecce2331457e5f1c4fb97de5216f392a76f4582f63b2",
|
||||
"size": 16
|
||||
}
|
||||
],
|
||||
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NzEsImRpZ2VzdCI6InNoYTI1NjowY2I0Mzk1NzkxOTg2YmRhMTc1NjJiZDZmNzY4MTFiYjZmMTYzZjY4NmUxOTgzOTcxOTdlZjgyNDFiZWQ1OGRmIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1Njo3ZTEzOTMxMGJkNmNlMDk1NmQ2NWE3MGQyNmE2ZDMxYjI0MGE0ZjQ3MDk0YTgzMTYzOGYwNWQzODFiNmM0MjRhIn0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OmNjODMzYmYzMWE0ODBjMDY0ZDY1Y2E2N2VlMzdmNzdmMGQwYzhhYjk4ZWVkZGU3YjI4NmFkMWVmNmY1YmRjYWMifV19",
|
||||
"config": "eyJhcmNoaXRlY3R1cmUiOiJhcm02NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjMtMDQtMThUMTQ6MDk6NDIuMzAxMDI2MzhaIiwiaGlzdG9yeSI6W3siY3JlYXRlZCI6IjIwMjMtMDQtMThUMTQ6MDk6NDIuMjg3OTQyNzEzWiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0xLnR4dCAvc29tZWZpbGUtMS50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn0seyJjcmVhdGVkIjoiMjAyMy0wNC0xOFQxNDowOTo0Mi4zMDEwMjYzOFoiLCJjcmVhdGVkX2J5IjoiQUREIGZpbGUtMi50eHQgL3NvbWVmaWxlLTIudHh0ICMgYnVpbGRraXQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCJ9XSwib3MiOiJsaW51eCIsInJvb3RmcyI6eyJ0eXBlIjoibGF5ZXJzIiwiZGlmZl9pZHMiOlsic2hhMjU2OjdlMTM5MzEwYmQ2Y2UwOTU2ZDY1YTcwZDI2YTZkMzFiMjQwYTRmNDcwOTRhODMxNjM4ZjA1ZDM4MWI2YzQyNGEiLCJzaGEyNTY6Y2M4MzNiZjMxYTQ4MGMwNjRkNjVjYTY3ZWUzN2Y3N2YwZDBjOGFiOThlZWRkZTdiMjg2YWQxZWY2ZjViZGNhYyJdfX0=",
|
||||
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NzMsImRpZ2VzdCI6InNoYTI1NjphM2M2MWRjMTM0ZDJmMzFiNDE1YzUwMzI0ZTc1ODQyZDdmOTE2MjJmMzlhODk0NjhlNTE5MzgzMzBiM2ZkM2FmIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjphYjYyMDE2ZjliZWM3Mjg2YWY2NTYwNDA4MTU2NGNhZGVlYjM2NGE0OGZhY2EyMzQ2YzNmNWE1YTFmNWVmNzc3In0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OmYxODAzODQ1YjY3NDdkOTRkNmU0ZWNjZTIzMzE0NTdlNWYxYzRmYjk3ZGU1MjE2ZjM5MmE3NmY0NTgyZjYzYjIifV19",
|
||||
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjMtMDQtMjFUMTk6MTA6MzcuNjUxODMxMjM0WiIsImhpc3RvcnkiOlt7ImNyZWF0ZWQiOiIyMDIzLTA0LTIxVDE5OjEwOjM3LjYwNzYxMzU1NVoiLCJjcmVhdGVkX2J5IjoiQUREIGZpbGUtMS50eHQgL3NvbWVmaWxlLTEudHh0ICMgYnVpbGRraXQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCJ9LHsiY3JlYXRlZCI6IjIwMjMtMDQtMjFUMTk6MTA6MzcuNjUxODMxMjM0WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6YWI2MjAxNmY5YmVjNzI4NmFmNjU2MDQwODE1NjRjYWRlZWIzNjRhNDhmYWNhMjM0NmMzZjVhNWExZjVlZjc3NyIsInNoYTI1NjpmMTgwMzg0NWI2NzQ3ZDk0ZDZlNGVjY2UyMzMxNDU3ZTVmMWM0ZmI5N2RlNTIxNmYzOTJhNzZmNDU4MmY2M2IyIl19fQ==",
|
||||
"repoDigests": [],
|
||||
"architecture": "",
|
||||
"os": ""
|
||||
@@ -110,5 +115,9 @@
|
||||
"configuration": {
|
||||
"config-key": "config-value"
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "8.0.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-8.0.0.json"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -131,10 +131,18 @@ func toFileMetadataEntry(coordinates source.Coordinates, metadata *source.FileMe
|
||||
return nil
|
||||
}
|
||||
|
||||
mode, err := strconv.Atoi(fmt.Sprintf("%o", metadata.Mode))
|
||||
if err != nil {
|
||||
log.Warnf("invalid mode found in file catalog @ location=%+v mode=%q: %+v", coordinates, metadata.Mode, err)
|
||||
mode = 0
|
||||
var mode int
|
||||
var size int64
|
||||
if metadata != nil && metadata.FileInfo != nil {
|
||||
var err error
|
||||
|
||||
mode, err = strconv.Atoi(fmt.Sprintf("%o", metadata.Mode()))
|
||||
if err != nil {
|
||||
log.Warnf("invalid mode found in file catalog @ location=%+v mode=%q: %+v", coordinates, metadata.Mode, err)
|
||||
mode = 0
|
||||
}
|
||||
|
||||
size = metadata.Size()
|
||||
}
|
||||
|
||||
return &model.FileMetadataEntry{
|
||||
@@ -144,7 +152,7 @@ func toFileMetadataEntry(coordinates source.Coordinates, metadata *source.FileMe
|
||||
UserID: metadata.UserID,
|
||||
GroupID: metadata.GroupID,
|
||||
MIMEType: metadata.MIMEType,
|
||||
Size: metadata.Size,
|
||||
Size: size,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,6 +192,24 @@ func toPackageModels(catalog *pkg.Collection) []model.Package {
|
||||
return artifacts
|
||||
}
|
||||
|
||||
func toLicenseModel(pkgLicenses []pkg.License) (modelLicenses []model.License) {
|
||||
for _, l := range pkgLicenses {
|
||||
// guarantee collection
|
||||
locations := make([]source.Location, 0)
|
||||
if v := l.Locations.ToSlice(); v != nil {
|
||||
locations = v
|
||||
}
|
||||
modelLicenses = append(modelLicenses, model.License{
|
||||
Value: l.Value,
|
||||
SPDXExpression: l.SPDXExpression,
|
||||
Type: l.Type,
|
||||
URLs: l.URLs.ToSlice(),
|
||||
Locations: locations,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// toPackageModel crates a new Package from the given pkg.Package.
|
||||
func toPackageModel(p pkg.Package) model.Package {
|
||||
var cpes = make([]string, len(p.CPEs))
|
||||
@@ -191,9 +217,11 @@ func toPackageModel(p pkg.Package) model.Package {
|
||||
cpes[i] = cpe.String(c)
|
||||
}
|
||||
|
||||
var licenses = make([]string, 0)
|
||||
if p.Licenses != nil {
|
||||
licenses = p.Licenses
|
||||
// we want to make sure all catalogers are
|
||||
// initializing the array; this is a good choke point for this check
|
||||
var licenses = make([]model.License, 0)
|
||||
if !p.Licenses.Empty() {
|
||||
licenses = toLicenseModel(p.Licenses.ToSlice())
|
||||
}
|
||||
|
||||
return model.Package{
|
||||
|
||||
@@ -152,3 +152,45 @@ func Test_toFileType(t *testing.T) {
|
||||
|
||||
assert.ElementsMatch(t, allTypesTested, file.AllTypes(), "not all file.Types are under test")
|
||||
}
|
||||
|
||||
func Test_toFileMetadataEntry(t *testing.T) {
|
||||
coords := source.Coordinates{
|
||||
RealPath: "/path",
|
||||
FileSystemID: "x",
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
metadata *source.FileMetadata
|
||||
want *model.FileMetadataEntry
|
||||
}{
|
||||
{
|
||||
name: "no metadata",
|
||||
},
|
||||
{
|
||||
name: "no file info",
|
||||
metadata: &source.FileMetadata{
|
||||
FileInfo: nil,
|
||||
},
|
||||
want: &model.FileMetadataEntry{
|
||||
Type: file.TypeRegular.String(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with file info",
|
||||
metadata: &source.FileMetadata{
|
||||
FileInfo: &file.ManualInfo{
|
||||
ModeValue: 1,
|
||||
},
|
||||
},
|
||||
want: &model.FileMetadataEntry{
|
||||
Mode: 1,
|
||||
Type: file.TypeRegular.String(),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, toFileMetadataEntry(coords, tt.metadata))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package syftjson
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
@@ -35,10 +38,30 @@ func toSyftModel(doc model.Document) (*sbom.SBOM, error) {
|
||||
},
|
||||
Source: *toSyftSourceData(doc.Source),
|
||||
Descriptor: toSyftDescriptor(doc.Descriptor),
|
||||
Relationships: toSyftRelationships(&doc, catalog, doc.ArtifactRelationships, idAliases),
|
||||
Relationships: warnConversionErrors(toSyftRelationships(&doc, catalog, doc.ArtifactRelationships, idAliases)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func warnConversionErrors[T any](converted []T, errors []error) []T {
|
||||
errorMessages := deduplicateErrors(errors)
|
||||
for _, msg := range errorMessages {
|
||||
log.Warn(msg)
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func deduplicateErrors(errors []error) []string {
|
||||
errorCounts := make(map[string]int)
|
||||
var errorMessages []string
|
||||
for _, e := range errors {
|
||||
errorCounts[e.Error()] = errorCounts[e.Error()] + 1
|
||||
}
|
||||
for msg, count := range errorCounts {
|
||||
errorMessages = append(errorMessages, fmt.Sprintf("%q occurred %d time(s)", msg, count))
|
||||
}
|
||||
return errorMessages
|
||||
}
|
||||
|
||||
func toSyftFiles(files []model.File) sbom.Artifacts {
|
||||
ret := sbom.Artifacts{
|
||||
FileMetadata: make(map[source.Coordinates]source.FileMetadata),
|
||||
@@ -57,14 +80,16 @@ func toSyftFiles(files []model.File) sbom.Artifacts {
|
||||
fm := os.FileMode(mode)
|
||||
|
||||
ret.FileMetadata[coord] = source.FileMetadata{
|
||||
FileInfo: stereoscopeFile.ManualInfo{
|
||||
NameValue: path.Base(coord.RealPath),
|
||||
SizeValue: f.Metadata.Size,
|
||||
ModeValue: fm,
|
||||
},
|
||||
Path: coord.RealPath,
|
||||
LinkDestination: f.Metadata.LinkDestination,
|
||||
Size: f.Metadata.Size,
|
||||
UserID: f.Metadata.UserID,
|
||||
GroupID: f.Metadata.GroupID,
|
||||
Type: toSyftFileType(f.Metadata.Type),
|
||||
IsDir: fm.IsDir(),
|
||||
Mode: fm,
|
||||
MIMEType: f.Metadata.MIMEType,
|
||||
}
|
||||
}
|
||||
@@ -80,6 +105,19 @@ func toSyftFiles(files []model.File) sbom.Artifacts {
|
||||
return ret
|
||||
}
|
||||
|
||||
func toSyftLicenses(m []model.License) (p []pkg.License) {
|
||||
for _, l := range m {
|
||||
p = append(p, pkg.License{
|
||||
Value: l.Value,
|
||||
SPDXExpression: l.SPDXExpression,
|
||||
Type: l.Type,
|
||||
URLs: internal.NewStringSet(l.URLs...),
|
||||
Locations: source.NewLocationSet(l.Locations...),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func toSyftFileType(ty string) stereoscopeFile.Type {
|
||||
switch ty {
|
||||
case "SymbolicLink":
|
||||
@@ -131,7 +169,7 @@ func toSyftLinuxRelease(d model.LinuxRelease) *linux.Release {
|
||||
}
|
||||
}
|
||||
|
||||
func toSyftRelationships(doc *model.Document, catalog *pkg.Collection, relationships []model.Relationship, idAliases map[string]string) []artifact.Relationship {
|
||||
func toSyftRelationships(doc *model.Document, catalog *pkg.Collection, relationships []model.Relationship, idAliases map[string]string) ([]artifact.Relationship, []error) {
|
||||
idMap := make(map[string]interface{})
|
||||
|
||||
for _, p := range catalog.Sorted() {
|
||||
@@ -150,13 +188,18 @@ func toSyftRelationships(doc *model.Document, catalog *pkg.Collection, relations
|
||||
}
|
||||
|
||||
var out []artifact.Relationship
|
||||
var conversionErrors []error
|
||||
for _, r := range relationships {
|
||||
syftRelationship := toSyftRelationship(idMap, r, idAliases)
|
||||
syftRelationship, err := toSyftRelationship(idMap, r, idAliases)
|
||||
if err != nil {
|
||||
conversionErrors = append(conversionErrors, err)
|
||||
}
|
||||
if syftRelationship != nil {
|
||||
out = append(out, *syftRelationship)
|
||||
}
|
||||
}
|
||||
return out
|
||||
|
||||
return out, conversionErrors
|
||||
}
|
||||
|
||||
func toSyftSource(s model.Source) *source.Source {
|
||||
@@ -167,7 +210,7 @@ func toSyftSource(s model.Source) *source.Source {
|
||||
return newSrc
|
||||
}
|
||||
|
||||
func toSyftRelationship(idMap map[string]interface{}, relationship model.Relationship, idAliases map[string]string) *artifact.Relationship {
|
||||
func toSyftRelationship(idMap map[string]interface{}, relationship model.Relationship, idAliases map[string]string) (*artifact.Relationship, error) {
|
||||
id := func(id string) string {
|
||||
aliased, ok := idAliases[id]
|
||||
if ok {
|
||||
@@ -178,14 +221,12 @@ func toSyftRelationship(idMap map[string]interface{}, relationship model.Relatio
|
||||
|
||||
from, ok := idMap[id(relationship.Parent)].(artifact.Identifiable)
|
||||
if !ok {
|
||||
log.Warnf("relationship mapping from key %s is not a valid artifact.Identifiable type: %+v", relationship.Parent, idMap[relationship.Parent])
|
||||
return nil
|
||||
return nil, fmt.Errorf("relationship mapping from key %s is not a valid artifact.Identifiable type: %+v", relationship.Parent, idMap[relationship.Parent])
|
||||
}
|
||||
|
||||
to, ok := idMap[id(relationship.Child)].(artifact.Identifiable)
|
||||
if !ok {
|
||||
log.Warnf("relationship mapping to key %s is not a valid artifact.Identifiable type: %+v", relationship.Child, idMap[relationship.Child])
|
||||
return nil
|
||||
return nil, fmt.Errorf("relationship mapping to key %s is not a valid artifact.Identifiable type: %+v", relationship.Child, idMap[relationship.Child])
|
||||
}
|
||||
|
||||
typ := artifact.RelationshipType(relationship.Type)
|
||||
@@ -194,8 +235,7 @@ func toSyftRelationship(idMap map[string]interface{}, relationship model.Relatio
|
||||
case artifact.OwnershipByFileOverlapRelationship, artifact.ContainsRelationship, artifact.DependencyOfRelationship, artifact.EvidentByRelationship:
|
||||
default:
|
||||
if !strings.Contains(string(typ), "dependency-of") {
|
||||
log.Warnf("unknown relationship type: %s", typ)
|
||||
return nil
|
||||
return nil, fmt.Errorf("unknown relationship type: %s", string(typ))
|
||||
}
|
||||
// lets try to stay as compatible as possible with similar relationship types without dropping the relationship
|
||||
log.Warnf("assuming %q for relationship type %q", artifact.DependencyOfRelationship, typ)
|
||||
@@ -206,7 +246,7 @@ func toSyftRelationship(idMap map[string]interface{}, relationship model.Relatio
|
||||
To: to,
|
||||
Type: typ,
|
||||
Data: relationship.Metadata,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toSyftDescriptor(d model.Descriptor) sbom.Descriptor {
|
||||
@@ -281,7 +321,7 @@ func toSyftPackage(p model.Package, idAliases map[string]string) pkg.Package {
|
||||
Version: p.Version,
|
||||
FoundBy: p.FoundBy,
|
||||
Locations: source.NewLocationSet(p.Locations...),
|
||||
Licenses: p.Licenses,
|
||||
Licenses: pkg.NewLicenseSet(toSyftLicenses(p.Licenses)...),
|
||||
Language: p.Language,
|
||||
Type: p.Type,
|
||||
CPEs: cpes,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package syftjson
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/formats/syftjson/model"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
@@ -200,14 +202,16 @@ func Test_toSyftFiles(t *testing.T) {
|
||||
want: sbom.Artifacts{
|
||||
FileMetadata: map[source.Coordinates]source.FileMetadata{
|
||||
coord: {
|
||||
FileInfo: stereoFile.ManualInfo{
|
||||
NameValue: "place",
|
||||
SizeValue: 92,
|
||||
ModeValue: 511, // 777 octal = 511 decimal
|
||||
},
|
||||
Path: coord.RealPath,
|
||||
LinkDestination: "",
|
||||
Size: 92,
|
||||
UserID: 42,
|
||||
GroupID: 32,
|
||||
Type: stereoFile.TypeRegular,
|
||||
IsDir: false,
|
||||
Mode: 511, // 777 octal = 511 decimal
|
||||
MIMEType: "text/plain",
|
||||
},
|
||||
},
|
||||
@@ -228,3 +232,120 @@ func Test_toSyftFiles(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_toSyfRelationship(t *testing.T) {
|
||||
packageWithId := func(id string) *pkg.Package {
|
||||
p := &pkg.Package{}
|
||||
p.OverrideID(artifact.ID(id))
|
||||
return p
|
||||
}
|
||||
childPackage := packageWithId("some-child-id")
|
||||
parentPackage := packageWithId("some-parent-id")
|
||||
tests := []struct {
|
||||
name string
|
||||
idMap map[string]interface{}
|
||||
idAliases map[string]string
|
||||
relationships model.Relationship
|
||||
want *artifact.Relationship
|
||||
wantError error
|
||||
}{
|
||||
{
|
||||
name: "one relationship no warnings",
|
||||
idMap: map[string]interface{}{
|
||||
"some-child-id": childPackage,
|
||||
"some-parent-id": parentPackage,
|
||||
},
|
||||
idAliases: map[string]string{},
|
||||
relationships: model.Relationship{
|
||||
Parent: "some-parent-id",
|
||||
Child: "some-child-id",
|
||||
Type: string(artifact.ContainsRelationship),
|
||||
},
|
||||
want: &artifact.Relationship{
|
||||
To: childPackage,
|
||||
From: parentPackage,
|
||||
Type: artifact.ContainsRelationship,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "relationship unknown type one warning",
|
||||
idMap: map[string]interface{}{
|
||||
"some-child-id": childPackage,
|
||||
"some-parent-id": parentPackage,
|
||||
},
|
||||
idAliases: map[string]string{},
|
||||
relationships: model.Relationship{
|
||||
Parent: "some-parent-id",
|
||||
Child: "some-child-id",
|
||||
Type: "some-unknown-relationship-type",
|
||||
},
|
||||
wantError: errors.New(
|
||||
"unknown relationship type: some-unknown-relationship-type",
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "relationship missing child ID one warning",
|
||||
idMap: map[string]interface{}{
|
||||
"some-parent-id": parentPackage,
|
||||
},
|
||||
idAliases: map[string]string{},
|
||||
relationships: model.Relationship{
|
||||
Parent: "some-parent-id",
|
||||
Child: "some-child-id",
|
||||
Type: string(artifact.ContainsRelationship),
|
||||
},
|
||||
wantError: errors.New(
|
||||
"relationship mapping to key some-child-id is not a valid artifact.Identifiable type: <nil>",
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "relationship missing parent ID one warning",
|
||||
idMap: map[string]interface{}{
|
||||
"some-child-id": childPackage,
|
||||
},
|
||||
idAliases: map[string]string{},
|
||||
relationships: model.Relationship{
|
||||
Parent: "some-parent-id",
|
||||
Child: "some-child-id",
|
||||
Type: string(artifact.ContainsRelationship),
|
||||
},
|
||||
wantError: errors.New("relationship mapping from key some-parent-id is not a valid artifact.Identifiable type: <nil>"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, gotErr := toSyftRelationship(tt.idMap, tt.relationships, tt.idAliases)
|
||||
assert.Equal(t, tt.want, got)
|
||||
assert.Equal(t, tt.wantError, gotErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_deduplicateErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
errors []error
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "no errors, nil slice",
|
||||
},
|
||||
{
|
||||
name: "deduplicates errors",
|
||||
errors: []error{
|
||||
errors.New("some error"),
|
||||
errors.New("some error"),
|
||||
},
|
||||
want: []string{
|
||||
`"some error" occurred 2 time(s)`,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := deduplicateErrors(tt.errors)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// package license provides common methods for working with SPDX license data
|
||||
package license
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/github/go-spdx/v2/spdxexp"
|
||||
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
)
|
||||
|
||||
type Type string
|
||||
|
||||
const (
|
||||
Declared Type = "declared"
|
||||
Concluded Type = "concluded"
|
||||
)
|
||||
|
||||
func ParseExpression(expression string) (ex string, err error) {
|
||||
// https://github.com/anchore/syft/issues/1837
|
||||
// The current spdx library can panic when parsing some expressions
|
||||
// This is a temporary fix to recover and patch until we can investigate and contribute
|
||||
// a fix to the upstream github library
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("recovered from panic while parsing license expression at: \n%s", string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
licenseID, exists := spdxlicense.ID(expression)
|
||||
if exists {
|
||||
return licenseID, nil
|
||||
}
|
||||
// If it doesn't exist initially in the SPDX list it might be a more complex expression
|
||||
// ignored variable is any invalid expressions
|
||||
// TODO: contribute to spdxexp to expose deprecated license IDs
|
||||
// https://github.com/anchore/syft/issues/1814
|
||||
valid, _ := spdxexp.ValidateLicenses([]string{expression})
|
||||
if !valid {
|
||||
return "", fmt.Errorf("invalid SPDX expression: %s", expression)
|
||||
}
|
||||
|
||||
return expression, nil
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package license
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseExpression(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
expression string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid single ID expression returns SPDX ID",
|
||||
expression: "mit",
|
||||
want: "MIT",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Valid SPDX expression returns SPDX expression",
|
||||
expression: "MIT OR Apache-2.0",
|
||||
want: "MIT OR Apache-2.0",
|
||||
},
|
||||
{
|
||||
name: "Invalid SPDX expression returns error",
|
||||
expression: "MIT OR Apache-2.0 OR invalid",
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ParseExpression(tt.expression)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ParseExpression() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("ParseExpression() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -20,13 +20,12 @@ type AlpmMetadata struct {
|
||||
Description string `mapstructure:"desc" json:"description" cyclonedx:"description"`
|
||||
Architecture string `mapstructure:"arch" json:"architecture" cyclonedx:"architecture"`
|
||||
Size int `mapstructure:"size" json:"size" cyclonedx:"size"`
|
||||
Packager string `mapstructure:"packager" json:"packager" cyclonedx:"packager"`
|
||||
License string `mapstructure:"license" json:"license" cyclonedx:"license"`
|
||||
URL string `mapstructure:"url" json:"url" cyclonedx:"url"`
|
||||
Validation string `mapstructure:"validation" json:"validation" cyclonedx:"validation"`
|
||||
Reason int `mapstructure:"reason" json:"reason" cyclonedx:"reason"`
|
||||
Files []AlpmFileRecord `mapstructure:"files" json:"files" cyclonedx:"files"`
|
||||
Backup []AlpmFileRecord `mapstructure:"backup" json:"backup" cyclonedx:"backup"`
|
||||
Packager string `mapstructure:"packager" json:"packager"`
|
||||
URL string `mapstructure:"url" json:"url"`
|
||||
Validation string `mapstructure:"validation" json:"validation"`
|
||||
Reason int `mapstructure:"reason" json:"reason"`
|
||||
Files []AlpmFileRecord `mapstructure:"files" json:"files"`
|
||||
Backup []AlpmFileRecord `mapstructure:"backup" json:"backup"`
|
||||
}
|
||||
|
||||
type AlpmFileRecord struct {
|
||||
|
||||
@@ -27,7 +27,6 @@ type ApkMetadata struct {
|
||||
OriginPackage string `mapstructure:"o" json:"originPackage" cyclonedx:"originPackage"`
|
||||
Maintainer string `mapstructure:"m" json:"maintainer"`
|
||||
Version string `mapstructure:"V" json:"version"`
|
||||
License string `mapstructure:"L" json:"license"`
|
||||
Architecture string `mapstructure:"A" json:"architecture"`
|
||||
URL string `mapstructure:"U" json:"url"`
|
||||
Description string `mapstructure:"T" json:"description"`
|
||||
|
||||
@@ -47,7 +47,6 @@ func TestApkMetadata_UnmarshalJSON(t *testing.T) {
|
||||
OriginPackage: "pax-utils",
|
||||
Maintainer: "Natanael Copa <ncopa@alpinelinux.org>",
|
||||
Version: "1.3.4-r0",
|
||||
License: "GPL-2.0-only",
|
||||
Architecture: "x86_64",
|
||||
URL: "https://wiki.gentoo.org/wiki/Hardened/PaX_Utilities",
|
||||
Description: "Scan ELF binaries for stuff",
|
||||
@@ -86,7 +85,6 @@ func TestApkMetadata_UnmarshalJSON(t *testing.T) {
|
||||
OriginPackage: "pax-utils",
|
||||
Maintainer: "Natanael Copa <ncopa@alpinelinux.org>",
|
||||
Version: "1.3.4-r0",
|
||||
License: "GPL-2.0-only",
|
||||
Architecture: "x86_64",
|
||||
URL: "https://wiki.gentoo.org/wiki/Hardened/PaX_Utilities",
|
||||
Description: "Scan ELF binaries for stuff",
|
||||
|
||||
@@ -17,6 +17,49 @@ type expectedIndexes struct {
|
||||
byPath map[string]*strset.Set
|
||||
}
|
||||
|
||||
func TestCatalogMergePackageLicenses(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pkgs []Package
|
||||
expectedPkgs []Package
|
||||
}{
|
||||
{
|
||||
name: "merges licenses of packages with equal ID",
|
||||
pkgs: []Package{
|
||||
{
|
||||
id: "equal",
|
||||
Licenses: NewLicenseSet(
|
||||
NewLicensesFromValues("foo", "baq", "quz")...,
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "equal",
|
||||
Licenses: NewLicenseSet(
|
||||
NewLicensesFromValues("bar", "baz", "foo", "qux")...,
|
||||
),
|
||||
},
|
||||
},
|
||||
expectedPkgs: []Package{
|
||||
{
|
||||
id: "equal",
|
||||
Licenses: NewLicenseSet(
|
||||
NewLicensesFromValues("foo", "baq", "quz", "qux", "bar", "baz")...,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
collection := NewCollection(test.pkgs...)
|
||||
for i, p := range collection.Sorted() {
|
||||
assert.Equal(t, test.expectedPkgs[i].Licenses, p.Licenses)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCatalogDeleteRemovesPackages(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -13,15 +13,18 @@ import (
|
||||
)
|
||||
|
||||
func TestAlpmCataloger(t *testing.T) {
|
||||
|
||||
dbLocation := source.NewLocation("var/lib/pacman/local/gmp-6.2.1-2/desc")
|
||||
expectedPkgs := []pkg.Package{
|
||||
{
|
||||
Name: "gmp",
|
||||
Version: "6.2.1-2",
|
||||
Type: pkg.AlpmPkg,
|
||||
FoundBy: "alpmdb-cataloger",
|
||||
Licenses: []string{"LGPL3", "GPL"},
|
||||
Locations: source.NewLocationSet(source.NewLocation("var/lib/pacman/local/gmp-6.2.1-2/desc")),
|
||||
Name: "gmp",
|
||||
Version: "6.2.1-2",
|
||||
Type: pkg.AlpmPkg,
|
||||
FoundBy: "alpmdb-cataloger",
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("LGPL3", dbLocation),
|
||||
pkg.NewLicenseFromLocations("GPL", dbLocation),
|
||||
),
|
||||
Locations: source.NewLocationSet(dbLocation),
|
||||
CPEs: nil,
|
||||
PURL: "",
|
||||
MetadataType: "AlpmMetadata",
|
||||
@@ -33,7 +36,6 @@ func TestAlpmCataloger(t *testing.T) {
|
||||
Architecture: "x86_64",
|
||||
Size: 1044438,
|
||||
Packager: "Antonio Rojas <arojas@archlinux.org>",
|
||||
License: "LGPL3\nGPL",
|
||||
URL: "https://gmplib.org/",
|
||||
Validation: "pgp",
|
||||
Reason: 1,
|
||||
|
||||
@@ -1,29 +1,33 @@
|
||||
package alpm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func newPackage(m pkg.AlpmMetadata, release *linux.Release, locations ...source.Location) pkg.Package {
|
||||
func newPackage(m *parsedData, release *linux.Release, dbLocation source.Location) pkg.Package {
|
||||
licenseCandidates := strings.Split(m.Licenses, "\n")
|
||||
|
||||
p := pkg.Package{
|
||||
Name: m.Package,
|
||||
Version: m.Version,
|
||||
Locations: source.NewLocationSet(locations...),
|
||||
Locations: source.NewLocationSet(dbLocation),
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation.WithoutAnnotations(), licenseCandidates...)...),
|
||||
Type: pkg.AlpmPkg,
|
||||
Licenses: internal.SplitAny(m.License, " \n"),
|
||||
PURL: packageURL(m, release),
|
||||
MetadataType: pkg.AlpmMetadataType,
|
||||
Metadata: m,
|
||||
Metadata: m.AlpmMetadata,
|
||||
}
|
||||
p.SetID()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func packageURL(m pkg.AlpmMetadata, distro *linux.Release) string {
|
||||
func packageURL(m *parsedData, distro *linux.Release) string {
|
||||
if distro == nil || distro.ID != "arch" {
|
||||
// note: there is no namespace variation (like with debian ID_LIKE for ubuntu ID, for example)
|
||||
return ""
|
||||
|
||||
@@ -13,16 +13,19 @@ import (
|
||||
func Test_PackageURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
metadata pkg.AlpmMetadata
|
||||
metadata *parsedData
|
||||
distro linux.Release
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "bad distro id",
|
||||
metadata: pkg.AlpmMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
metadata: &parsedData{
|
||||
Licenses: "",
|
||||
AlpmMetadata: pkg.AlpmMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "something-else",
|
||||
@@ -32,10 +35,13 @@ func Test_PackageURL(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "gocase",
|
||||
metadata: pkg.AlpmMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
metadata: &parsedData{
|
||||
Licenses: "",
|
||||
AlpmMetadata: pkg.AlpmMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "arch",
|
||||
@@ -45,9 +51,12 @@ func Test_PackageURL(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "missing architecture",
|
||||
metadata: pkg.AlpmMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
metadata: &parsedData{
|
||||
Licenses: "",
|
||||
AlpmMetadata: pkg.AlpmMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "arch",
|
||||
@@ -55,10 +64,13 @@ func Test_PackageURL(t *testing.T) {
|
||||
expected: "pkg:alpm/arch/p@v?distro=arch",
|
||||
},
|
||||
{
|
||||
metadata: pkg.AlpmMetadata{
|
||||
Package: "python",
|
||||
Version: "3.10.0",
|
||||
Architecture: "any",
|
||||
metadata: &parsedData{
|
||||
Licenses: "",
|
||||
AlpmMetadata: pkg.AlpmMetadata{
|
||||
Package: "python",
|
||||
Version: "3.10.0",
|
||||
Architecture: "any",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "arch",
|
||||
@@ -67,10 +79,13 @@ func Test_PackageURL(t *testing.T) {
|
||||
expected: "pkg:alpm/arch/python@3.10.0?arch=any&distro=arch-rolling",
|
||||
},
|
||||
{
|
||||
metadata: pkg.AlpmMetadata{
|
||||
Package: "g plus plus",
|
||||
Version: "v84",
|
||||
Architecture: "x86_64",
|
||||
metadata: &parsedData{
|
||||
Licenses: "",
|
||||
AlpmMetadata: pkg.AlpmMetadata{
|
||||
Package: "g plus plus",
|
||||
Version: "v84",
|
||||
Architecture: "x86_64",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "arch",
|
||||
@@ -80,11 +95,14 @@ func Test_PackageURL(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "add source information as qualifier",
|
||||
metadata: pkg.AlpmMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
BasePackage: "origin",
|
||||
metadata: &parsedData{
|
||||
Licenses: "",
|
||||
AlpmMetadata: pkg.AlpmMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
BasePackage: "origin",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "arch",
|
||||
|
||||
@@ -31,8 +31,13 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
type parsedData struct {
|
||||
Licenses string `mapstructure:"license"`
|
||||
pkg.AlpmMetadata `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
func parseAlpmDB(resolver source.FileResolver, env *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
metadata, err := parseAlpmDBEntry(reader)
|
||||
data, err := parseAlpmDBEntry(reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -48,9 +53,10 @@ func parseAlpmDB(resolver source.FileResolver, env *generic.Environment, reader
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// The replace the files found the the pacman database with the files from the mtree These contain more metadata and
|
||||
// replace the files found the pacman database with the files from the mtree These contain more metadata and
|
||||
// thus more useful.
|
||||
metadata.Files = pkgFiles
|
||||
// TODO: probably want to use MTREE and PKGINFO here
|
||||
data.Files = pkgFiles
|
||||
|
||||
// We only really do this to get any backup database entries from the files database
|
||||
files := filepath.Join(base, "files")
|
||||
@@ -62,23 +68,23 @@ func parseAlpmDB(resolver source.FileResolver, env *generic.Environment, reader
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
} else if filesMetadata != nil {
|
||||
metadata.Backup = filesMetadata.Backup
|
||||
data.Backup = filesMetadata.Backup
|
||||
}
|
||||
|
||||
if metadata.Package == "" {
|
||||
if data.Package == "" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
return []pkg.Package{
|
||||
newPackage(
|
||||
*metadata,
|
||||
data,
|
||||
env.LinuxRelease,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
func parseAlpmDBEntry(reader io.Reader) (*pkg.AlpmMetadata, error) {
|
||||
func parseAlpmDBEntry(reader io.Reader) (*parsedData, error) {
|
||||
scanner := newScanner(reader)
|
||||
metadata, err := parseDatabase(scanner)
|
||||
if err != nil {
|
||||
@@ -128,8 +134,7 @@ func getFileReader(path string, resolver source.FileResolver) (io.Reader, error)
|
||||
return dbContentReader, nil
|
||||
}
|
||||
|
||||
func parseDatabase(b *bufio.Scanner) (*pkg.AlpmMetadata, error) {
|
||||
var entry pkg.AlpmMetadata
|
||||
func parseDatabase(b *bufio.Scanner) (*parsedData, error) {
|
||||
var err error
|
||||
pkgFields := make(map[string]interface{})
|
||||
for b.Scan() {
|
||||
@@ -181,16 +186,23 @@ func parseDatabase(b *bufio.Scanner) (*pkg.AlpmMetadata, error) {
|
||||
pkgFields[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return parsePkgFiles(pkgFields)
|
||||
}
|
||||
|
||||
func parsePkgFiles(pkgFields map[string]interface{}) (*parsedData, error) {
|
||||
var entry parsedData
|
||||
if err := mapstructure.Decode(pkgFields, &entry); err != nil {
|
||||
return nil, fmt.Errorf("unable to parse ALPM metadata: %w", err)
|
||||
}
|
||||
if entry.Package == "" && len(entry.Files) == 0 && len(entry.Backup) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if entry.Backup == nil {
|
||||
entry.Backup = make([]pkg.AlpmFileRecord, 0)
|
||||
}
|
||||
|
||||
if entry.Package == "" && len(entry.Files) == 0 && len(entry.Backup) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return &entry, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -4,21 +4,30 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func newPackage(d pkg.ApkMetadata, release *linux.Release, locations ...source.Location) pkg.Package {
|
||||
func newPackage(d parsedData, release *linux.Release, dbLocation source.Location) pkg.Package {
|
||||
// check if license is a valid spdx expression before splitting
|
||||
licenseStrings := []string{d.License}
|
||||
_, err := license.ParseExpression(d.License)
|
||||
if err != nil {
|
||||
// invalid so update to split on space
|
||||
licenseStrings = strings.Split(d.License, " ")
|
||||
}
|
||||
|
||||
p := pkg.Package{
|
||||
Name: d.Package,
|
||||
Version: d.Version,
|
||||
Locations: source.NewLocationSet(locations...),
|
||||
Licenses: strings.Split(d.License, " "),
|
||||
PURL: packageURL(d, release),
|
||||
Locations: source.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation, licenseStrings...)...),
|
||||
PURL: packageURL(d.ApkMetadata, release),
|
||||
Type: pkg.ApkPkg,
|
||||
MetadataType: pkg.ApkMetadataType,
|
||||
Metadata: d,
|
||||
Metadata: d.ApkMetadata,
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
|
||||
@@ -15,16 +15,19 @@ import (
|
||||
func Test_PackageURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
metadata pkg.ApkMetadata
|
||||
metadata parsedData
|
||||
distro linux.Release
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "non-alpine distro",
|
||||
metadata: pkg.ApkMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "something else",
|
||||
@@ -34,10 +37,13 @@ func Test_PackageURL(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "gocase",
|
||||
metadata: pkg.ApkMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "alpine",
|
||||
@@ -47,9 +53,12 @@ func Test_PackageURL(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "missing architecture",
|
||||
metadata: pkg.ApkMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "alpine",
|
||||
@@ -59,10 +68,13 @@ func Test_PackageURL(t *testing.T) {
|
||||
},
|
||||
// verify #351
|
||||
{
|
||||
metadata: pkg.ApkMetadata{
|
||||
Package: "g++",
|
||||
Version: "v84",
|
||||
Architecture: "am86",
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
Package: "g++",
|
||||
Version: "v84",
|
||||
Architecture: "am86",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "alpine",
|
||||
@@ -71,10 +83,13 @@ func Test_PackageURL(t *testing.T) {
|
||||
expected: "pkg:apk/alpine/g++@v84?arch=am86&distro=alpine-3.4.6",
|
||||
},
|
||||
{
|
||||
metadata: pkg.ApkMetadata{
|
||||
Package: "g plus plus",
|
||||
Version: "v84",
|
||||
Architecture: "am86",
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
Package: "g plus plus",
|
||||
Version: "v84",
|
||||
Architecture: "am86",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "alpine",
|
||||
@@ -84,11 +99,14 @@ func Test_PackageURL(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "add source information as qualifier",
|
||||
metadata: pkg.ApkMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
OriginPackage: "origin",
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
OriginPackage: "origin",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "alpine",
|
||||
@@ -98,10 +116,13 @@ func Test_PackageURL(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "wolfi distro",
|
||||
metadata: pkg.ApkMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "wolfi",
|
||||
@@ -113,7 +134,7 @@ func Test_PackageURL(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual := packageURL(test.metadata, &test.distro)
|
||||
actual := packageURL(test.metadata.ApkMetadata, &test.distro)
|
||||
if actual != test.expected {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(test.expected, actual, true)
|
||||
|
||||
@@ -26,6 +26,11 @@ var (
|
||||
repoRegex = regexp.MustCompile(`(?m)^https://.*\.alpinelinux\.org/alpine/v([^/]+)/([a-zA-Z0-9_]+)$`)
|
||||
)
|
||||
|
||||
type parsedData struct {
|
||||
License string `mapstructure:"L" json:"license"`
|
||||
pkg.ApkMetadata
|
||||
}
|
||||
|
||||
// parseApkDB parses packages from a given APK installed DB file. For more
|
||||
// information on specific fields, see https://wiki.alpinelinux.org/wiki/Apk_spec.
|
||||
//
|
||||
@@ -33,15 +38,15 @@ var (
|
||||
func parseApkDB(resolver source.FileResolver, env *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
var apks []pkg.ApkMetadata
|
||||
var currentEntry pkg.ApkMetadata
|
||||
var apks []parsedData
|
||||
var currentEntry parsedData
|
||||
entryParsingInProgress := false
|
||||
fileParsingCtx := newApkFileParsingContext()
|
||||
|
||||
// creating a dedicated append-like function here instead of using `append(...)`
|
||||
// below since there is nontrivial logic to be performed for each finalized apk
|
||||
// entry.
|
||||
appendApk := func(p pkg.ApkMetadata) {
|
||||
appendApk := func(p parsedData) {
|
||||
if files := fileParsingCtx.files; len(files) >= 1 {
|
||||
// attached accumulated files to current package
|
||||
p.Files = files
|
||||
@@ -68,7 +73,7 @@ func parseApkDB(resolver source.FileResolver, env *generic.Environment, reader s
|
||||
entryParsingInProgress = false
|
||||
|
||||
// zero-out currentEntry for use by any future entry
|
||||
currentEntry = pkg.ApkMetadata{}
|
||||
currentEntry = parsedData{}
|
||||
|
||||
continue
|
||||
}
|
||||
@@ -123,7 +128,7 @@ func parseApkDB(resolver source.FileResolver, env *generic.Environment, reader s
|
||||
|
||||
pkgs := make([]pkg.Package, 0, len(apks))
|
||||
for _, apk := range apks {
|
||||
pkgs = append(pkgs, newPackage(apk, r, reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)))
|
||||
pkgs = append(pkgs, newPackage(apk, r, reader.Location))
|
||||
}
|
||||
|
||||
return pkgs, discoverPackageDependencies(pkgs), nil
|
||||
@@ -201,7 +206,7 @@ type apkField struct {
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func (f apkField) apply(p *pkg.ApkMetadata, ctx *apkFileParsingContext) {
|
||||
func (f apkField) apply(p *parsedData, ctx *apkFileParsingContext) {
|
||||
switch f.name {
|
||||
// APKINDEX field parsing
|
||||
|
||||
@@ -347,7 +352,7 @@ func parseListValue(value string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func nilFieldsToEmptySlice(p *pkg.ApkMetadata) {
|
||||
func nilFieldsToEmptySlice(p *parsedData) {
|
||||
if p.Dependencies == nil {
|
||||
p.Dependencies = []string{}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ S:1175
|
||||
I:4096
|
||||
T:Meta package to pull in correct libc
|
||||
U:http://alpinelinux.org
|
||||
L:BSD
|
||||
L:MPL-2.0 AND MIT
|
||||
o:libc-dev
|
||||
m:Natanael Copa <ncopa@alpinelinux.org>
|
||||
t:1575749004
|
||||
|
||||
@@ -239,30 +239,6 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("redis-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-argocd-2.5.11",
|
||||
fixtureDir: "test-fixtures/classifiers/dynamic/argocd-2.5.11",
|
||||
expected: pkg.Package{
|
||||
Name: "argocd",
|
||||
Version: "2.5.11",
|
||||
Type: "binary",
|
||||
PURL: "pkg:golang/github.com/argoproj/argo-cd@2.5.11",
|
||||
Locations: locations("argocd"),
|
||||
Metadata: metadata("argocd"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-argocd-2.6.4",
|
||||
fixtureDir: "test-fixtures/classifiers/dynamic/argocd-2.6.4",
|
||||
expected: pkg.Package{
|
||||
Name: "argocd",
|
||||
Version: "2.6.4",
|
||||
Type: "binary",
|
||||
PURL: "pkg:golang/github.com/argoproj/argo-cd@2.6.4",
|
||||
Locations: locations("argocd"),
|
||||
Metadata: metadata("argocd"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-helm-3.11.1",
|
||||
fixtureDir: "test-fixtures/classifiers/dynamic/helm-3.11.1",
|
||||
@@ -287,66 +263,6 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("helm"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-kubectl-1.24.11",
|
||||
fixtureDir: "test-fixtures/classifiers/dynamic/kubectl-1.24.11",
|
||||
expected: pkg.Package{
|
||||
Name: "kubectl",
|
||||
Version: "1.24.11",
|
||||
Type: "binary",
|
||||
PURL: "pkg:golang/k8s.io/kubectl@1.24.11",
|
||||
Locations: locations("kubectl"),
|
||||
Metadata: metadata("kubectl"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-kubectl-1.25.7",
|
||||
fixtureDir: "test-fixtures/classifiers/dynamic/kubectl-1.25.7",
|
||||
expected: pkg.Package{
|
||||
Name: "kubectl",
|
||||
Version: "1.25.7",
|
||||
Type: "binary",
|
||||
PURL: "pkg:golang/k8s.io/kubectl@1.25.7",
|
||||
Locations: locations("kubectl"),
|
||||
Metadata: metadata("kubectl"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-kubectl-1.26.2",
|
||||
fixtureDir: "test-fixtures/classifiers/dynamic/kubectl-1.26.2",
|
||||
expected: pkg.Package{
|
||||
Name: "kubectl",
|
||||
Version: "1.26.2",
|
||||
Type: "binary",
|
||||
PURL: "pkg:golang/k8s.io/kubectl@1.26.2",
|
||||
Locations: locations("kubectl"),
|
||||
Metadata: metadata("kubectl"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-kustomize-4.5.7",
|
||||
fixtureDir: "test-fixtures/classifiers/dynamic/kustomize-4.5.7",
|
||||
expected: pkg.Package{
|
||||
Name: "kustomize",
|
||||
Version: "4.5.7",
|
||||
Type: "binary",
|
||||
PURL: "pkg:golang/sigs.k8s.io/kustomize@4.5.7",
|
||||
Locations: locations("kustomize"),
|
||||
Metadata: metadata("kustomize"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-kustomize-5.0.0",
|
||||
fixtureDir: "test-fixtures/classifiers/dynamic/kustomize-5.0.0",
|
||||
expected: pkg.Package{
|
||||
Name: "kustomize",
|
||||
Version: "5.0.0",
|
||||
Type: "binary",
|
||||
PURL: "pkg:golang/sigs.k8s.io/kustomize@5.0.0",
|
||||
Locations: locations("kustomize"),
|
||||
Metadata: metadata("kustomize"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-redis-4.0.11",
|
||||
fixtureDir: "test-fixtures/classifiers/positive/redis-server-4.0.11",
|
||||
|
||||
@@ -46,15 +46,6 @@ var defaultClassifiers = []classifier{
|
||||
PURL: mustPURL("pkg:generic/go@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:golang:go:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
{
|
||||
Class: "argocd",
|
||||
FileGlob: "**/argocd",
|
||||
EvidenceMatcher: fileContentsVersionMatcher(
|
||||
`(?m)common\.version=(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
Package: "argocd",
|
||||
PURL: mustPURL("pkg:golang/github.com/argoproj/argo-cd@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:argoproj:argocd:*:*:*:*:*:*:*"),
|
||||
},
|
||||
{
|
||||
Class: "helm",
|
||||
FileGlob: "**/helm",
|
||||
@@ -64,24 +55,6 @@ var defaultClassifiers = []classifier{
|
||||
PURL: mustPURL("pkg:golang/helm.sh/helm@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:helm:helm:*:*:*:*:*:*:*"),
|
||||
},
|
||||
{
|
||||
Class: "kustomize",
|
||||
FileGlob: "**/kustomize",
|
||||
EvidenceMatcher: fileContentsVersionMatcher(
|
||||
`(?m)version=kustomize/v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
Package: "kustomize",
|
||||
PURL: mustPURL("pkg:golang/sigs.k8s.io/kustomize@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:kustomize:kustomize:*:*:*:*:*:*:*"),
|
||||
},
|
||||
{
|
||||
Class: "kubectl",
|
||||
FileGlob: "**/kubectl",
|
||||
EvidenceMatcher: fileContentsVersionMatcher(
|
||||
`(?m)\x00v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`),
|
||||
Package: "kubectl",
|
||||
PURL: mustPURL("pkg:golang/k8s.io/kubectl@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:kubectl:kubectl:*:*:*:*:*:*:*"),
|
||||
},
|
||||
{
|
||||
Class: "redis-binary",
|
||||
FileGlob: "**/redis-server",
|
||||
|
||||
@@ -7,15 +7,8 @@ all: \
|
||||
classifiers/dynamic/ruby-library-3.2.1 \
|
||||
classifiers/dynamic/ruby-library-2.7.7 \
|
||||
classifiers/dynamic/ruby-library-2.6.10 \
|
||||
classifiers/dynamic/argocd-2.5.11 \
|
||||
classifiers/dynamic/argocd-2.6.4 \
|
||||
classifiers/dynamic/helm-3.11.1 \
|
||||
classifiers/dynamic/helm-3.10.3 \
|
||||
classifiers/dynamic/kubectl-1.24.11 \
|
||||
classifiers/dynamic/kubectl-1.25.7 \
|
||||
classifiers/dynamic/kubectl-1.26.2 \
|
||||
classifiers/dynamic/kustomize-4.5.7 \
|
||||
classifiers/dynamic/kustomize-5.0.0 \
|
||||
classifiers/dynamic/consul-1.15.2
|
||||
|
||||
|
||||
@@ -89,18 +82,6 @@ classifiers/dynamic/ruby-library-2.6.10:
|
||||
/usr/local/lib/libruby.so.2.6 \
|
||||
$@/libruby.so.2.6
|
||||
|
||||
classifiers/dynamic/argocd-2.5.11:
|
||||
$(eval $@_image := "argoproj/argocd:v2.5.11@sha256:d1062935b3256ec69422843ebcb50debb54fd389436961586000c8ce6ee7f249")
|
||||
./get-image-file.sh $($@_image) \
|
||||
/usr/local/bin/argocd \
|
||||
$@/argocd
|
||||
|
||||
classifiers/dynamic/argocd-2.6.4:
|
||||
$(eval $@_image := "argoproj/argocd:v2.6.4@sha256:61fcbba187ff53c00696cb580edf70cada59c45cf399d8477631acf43cf522ee")
|
||||
./get-image-file.sh $($@_image) \
|
||||
/usr/local/bin/argocd \
|
||||
$@/argocd
|
||||
|
||||
classifiers/dynamic/helm-3.11.1:
|
||||
$(eval $@_image := "alpine/helm:3.11.1@sha256:8628e3695fb743a8b9de89626f1b7a221280c2152c0e288c2504e59b68233e8b")
|
||||
./get-image-file.sh $($@_image) \
|
||||
@@ -113,36 +94,6 @@ classifiers/dynamic/helm-3.10.3:
|
||||
/usr/local/bin/helm \
|
||||
$@/helm
|
||||
|
||||
classifiers/dynamic/kubectl-1.24.11:
|
||||
$(eval $@_image := "bitnami/kubectl:1.24.11@sha256:79d60c5ac8a1dc84e2c39f56d8e8cc0053159b5ed88f283bdf8fbda1ee86c8bc")
|
||||
./get-image-file.sh $($@_image) \
|
||||
/opt/bitnami/kubectl/bin/kubectl \
|
||||
$@/kubectl
|
||||
|
||||
classifiers/dynamic/kubectl-1.25.7:
|
||||
$(eval $@_image := "bitnami/kubectl:1.25.7@sha256:d7b00dbfdc6d8890aefe40edfb6c1d4c90cbb6c978794bb51a21744edc34ba7a")
|
||||
./get-image-file.sh $($@_image) \
|
||||
/opt/bitnami/kubectl/bin/kubectl \
|
||||
$@/kubectl
|
||||
|
||||
classifiers/dynamic/kubectl-1.26.2:
|
||||
$(eval $@_image := "line/kubectl-kustomize:1.26.2-5.0.0@sha256:9ee3b4a9a21f0777fc1d8c64208290f818a2e68c5e9e892e931621bda089bf06")
|
||||
./get-image-file.sh $($@_image) \
|
||||
/usr/local/bin/kubectl \
|
||||
$@/kubectl
|
||||
|
||||
classifiers/dynamic/kustomize-4.5.7:
|
||||
$(eval $@_image := "argoproj/argocd:v2.6.4@sha256:61fcbba187ff53c00696cb580edf70cada59c45cf399d8477631acf43cf522ee")
|
||||
./get-image-file.sh $($@_image) \
|
||||
/usr/local/bin/kustomize \
|
||||
$@/kustomize
|
||||
|
||||
classifiers/dynamic/kustomize-5.0.0:
|
||||
$(eval $@_image := "line/kubectl-kustomize:1.26.2-5.0.0@sha256:9ee3b4a9a21f0777fc1d8c64208290f818a2e68c5e9e892e931621bda089bf06")
|
||||
./get-image-file.sh $($@_image) \
|
||||
/usr/local/bin/kustomize \
|
||||
$@/kustomize
|
||||
|
||||
classifiers/dynamic/consul-1.15.2:
|
||||
$(eval $@_image := "hashicorp/consul:1.15.2@sha256:c2169f3bb18dd947ae8eb5f6766896695c71fb439f050a3343e0007d895615b8")
|
||||
./get-image-file.sh $($@_image) \
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/php"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/portage"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/python"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/r"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/rpm"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/ruby"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/rust"
|
||||
@@ -53,6 +54,7 @@ func ImageCatalogers(cfg Config) []pkg.Cataloger {
|
||||
php.NewComposerInstalledCataloger(),
|
||||
portage.NewPortageCataloger(),
|
||||
python.NewPythonPackageCataloger(),
|
||||
r.NewPackageCataloger(),
|
||||
rpm.NewRpmDBCataloger(),
|
||||
ruby.NewGemSpecCataloger(),
|
||||
sbom.NewSBOMCataloger(),
|
||||
@@ -121,6 +123,7 @@ func AllCatalogers(cfg Config) []pkg.Cataloger {
|
||||
portage.NewPortageCataloger(),
|
||||
python.NewPythonIndexCataloger(),
|
||||
python.NewPythonPackageCataloger(),
|
||||
r.NewPackageCataloger(),
|
||||
rpm.NewFileCataloger(),
|
||||
rpm.NewRpmDBCataloger(),
|
||||
ruby.NewGemFileLockCataloger(),
|
||||
|
||||
@@ -10,12 +10,17 @@ import (
|
||||
)
|
||||
|
||||
func TestDpkgCataloger(t *testing.T) {
|
||||
licenseLocation := source.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright")
|
||||
expected := []pkg.Package{
|
||||
{
|
||||
Name: "libpam-runtime",
|
||||
Version: "1.1.8-3.6",
|
||||
FoundBy: "dpkgdb-cataloger",
|
||||
Licenses: []string{"GPL-1", "GPL-2", "LGPL-2.1"},
|
||||
Name: "libpam-runtime",
|
||||
Version: "1.1.8-3.6",
|
||||
FoundBy: "dpkgdb-cataloger",
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("GPL-1", licenseLocation),
|
||||
pkg.NewLicenseFromLocations("GPL-2", licenseLocation),
|
||||
pkg.NewLicenseFromLocations("LGPL-2.1", licenseLocation),
|
||||
),
|
||||
Locations: source.NewLocationSet(
|
||||
source.NewVirtualLocation("/var/lib/dpkg/status", "/var/lib/dpkg/status"),
|
||||
source.NewVirtualLocation("/var/lib/dpkg/info/libpam-runtime.md5sums", "/var/lib/dpkg/info/libpam-runtime.md5sums"),
|
||||
|
||||
@@ -22,9 +22,12 @@ const (
|
||||
)
|
||||
|
||||
func newDpkgPackage(d pkg.DpkgMetadata, dbLocation source.Location, resolver source.FileResolver, release *linux.Release) pkg.Package {
|
||||
// TODO: separate pr to license refactor, but explore extracting dpkg-specific license parsing into a separate function
|
||||
licenses := make([]pkg.License, 0)
|
||||
p := pkg.Package{
|
||||
Name: d.Package,
|
||||
Version: d.Version,
|
||||
Licenses: pkg.NewLicenseSet(licenses...),
|
||||
Locations: source.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
PURL: packageURL(d, release),
|
||||
Type: pkg.DebPkg,
|
||||
@@ -93,8 +96,10 @@ func addLicenses(resolver source.FileResolver, dbLocation source.Location, p *pk
|
||||
if copyrightReader != nil && copyrightLocation != nil {
|
||||
defer internal.CloseAndLogError(copyrightReader, copyrightLocation.VirtualPath)
|
||||
// attach the licenses
|
||||
p.Licenses = parseLicensesFromCopyright(copyrightReader)
|
||||
|
||||
licenseStrs := parseLicensesFromCopyright(copyrightReader)
|
||||
for _, licenseStr := range licenseStrs {
|
||||
p.Licenses.Add(pkg.NewLicenseFromLocations(licenseStr, copyrightLocation.WithoutAnnotations()))
|
||||
}
|
||||
// keep a record of the file where this was discovered
|
||||
p.Locations.Add(*copyrightLocation)
|
||||
}
|
||||
|
||||
@@ -307,6 +307,7 @@ Installed-Size: 10kib
|
||||
Name: "apt",
|
||||
Type: "deb",
|
||||
PURL: "pkg:deb/debian/apt?distro=debian-10",
|
||||
Licenses: pkg.NewLicenseSet(),
|
||||
Locations: source.NewLocationSet(source.NewLocation("place")),
|
||||
MetadataType: "DpkgMetadata",
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/anchore/syft/internal/licenses"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/event"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
@@ -74,7 +75,7 @@ func modCacheResolver(modCacheDir string) source.WritableFileResolver {
|
||||
return r
|
||||
}
|
||||
|
||||
func (c *goLicenses) getLicenses(resolver source.FileResolver, moduleName, moduleVersion string) (licenses []string, err error) {
|
||||
func (c *goLicenses) getLicenses(resolver source.FileResolver, moduleName, moduleVersion string) (licenses []pkg.License, err error) {
|
||||
licenses, err = findLicenses(resolver,
|
||||
fmt.Sprintf(`**/go/pkg/mod/%s@%s/*`, processCaps(moduleName), moduleVersion),
|
||||
)
|
||||
@@ -93,7 +94,7 @@ func (c *goLicenses) getLicenses(resolver source.FileResolver, moduleName, modul
|
||||
return requireCollection(licenses), err
|
||||
}
|
||||
|
||||
func (c *goLicenses) getLicensesFromLocal(moduleName, moduleVersion string) ([]string, error) {
|
||||
func (c *goLicenses) getLicensesFromLocal(moduleName, moduleVersion string) ([]pkg.License, error) {
|
||||
if !c.opts.searchLocalModCacheLicenses {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -103,7 +104,7 @@ func (c *goLicenses) getLicensesFromLocal(moduleName, moduleVersion string) ([]s
|
||||
return findLicenses(c.localModCacheResolver, moduleSearchGlob(moduleName, moduleVersion))
|
||||
}
|
||||
|
||||
func (c *goLicenses) getLicensesFromRemote(moduleName, moduleVersion string) ([]string, error) {
|
||||
func (c *goLicenses) getLicensesFromRemote(moduleName, moduleVersion string) ([]pkg.License, error) {
|
||||
if !c.opts.searchRemoteLicenses {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -148,14 +149,15 @@ func moduleSearchGlob(moduleName, moduleVersion string) string {
|
||||
return fmt.Sprintf("%s/*", moduleDir(moduleName, moduleVersion))
|
||||
}
|
||||
|
||||
func requireCollection(licenses []string) []string {
|
||||
func requireCollection(licenses []pkg.License) []pkg.License {
|
||||
if licenses == nil {
|
||||
return []string{}
|
||||
return make([]pkg.License, 0)
|
||||
}
|
||||
return licenses
|
||||
}
|
||||
|
||||
func findLicenses(resolver source.FileResolver, globMatch string) (out []string, err error) {
|
||||
func findLicenses(resolver source.FileResolver, globMatch string) (out []pkg.License, err error) {
|
||||
out = make([]pkg.License, 0)
|
||||
if resolver == nil {
|
||||
return
|
||||
}
|
||||
@@ -172,7 +174,7 @@ func findLicenses(resolver source.FileResolver, globMatch string) (out []string,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsed, err := licenses.Parse(contents)
|
||||
parsed, err := licenses.Parse(contents, l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -13,24 +13,42 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func Test_LocalLicenseSearch(t *testing.T) {
|
||||
loc1 := source.NewLocation("github.com/someorg/somename@v0.3.2/LICENSE")
|
||||
loc2 := source.NewLocation("github.com/!cap!o!r!g/!cap!project@v4.111.5/LICENSE.txt")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
version string
|
||||
expected string
|
||||
expected pkg.License
|
||||
}{
|
||||
{
|
||||
name: "github.com/someorg/somename",
|
||||
version: "v0.3.2",
|
||||
expected: "Apache-2.0",
|
||||
name: "github.com/someorg/somename",
|
||||
version: "v0.3.2",
|
||||
expected: pkg.License{
|
||||
Value: "Apache-2.0",
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: license.Concluded,
|
||||
Locations: source.NewLocationSet(loc1),
|
||||
URLs: internal.NewStringSet(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "github.com/CapORG/CapProject",
|
||||
version: "v4.111.5",
|
||||
expected: "MIT",
|
||||
name: "github.com/CapORG/CapProject",
|
||||
version: "v4.111.5",
|
||||
expected: pkg.License{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Concluded,
|
||||
Locations: source.NewLocationSet(loc2),
|
||||
URLs: internal.NewStringSet(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -39,10 +57,12 @@ func Test_LocalLicenseSearch(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
l := newGoLicenses(GoCatalogerOpts{
|
||||
searchLocalModCacheLicenses: true,
|
||||
localModCacheDir: path.Join(wd, "test-fixtures", "licenses", "pkg", "mod"),
|
||||
})
|
||||
l := newGoLicenses(
|
||||
GoCatalogerOpts{
|
||||
searchLocalModCacheLicenses: true,
|
||||
localModCacheDir: path.Join(wd, "test-fixtures", "licenses", "pkg", "mod"),
|
||||
},
|
||||
)
|
||||
licenses, err := l.getLicenses(source.EmptyResolver{}, test.name, test.version)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -54,6 +74,9 @@ func Test_LocalLicenseSearch(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_RemoteProxyLicenseSearch(t *testing.T) {
|
||||
loc1 := source.NewLocation("github.com/someorg/somename@v0.3.2/LICENSE")
|
||||
loc2 := source.NewLocation("github.com/!cap!o!r!g/!cap!project@v4.111.5/LICENSE.txt")
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
buf := &bytes.Buffer{}
|
||||
uri := strings.TrimPrefix(strings.TrimSuffix(r.RequestURI, ".zip"), "/")
|
||||
@@ -94,17 +117,29 @@ func Test_RemoteProxyLicenseSearch(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
version string
|
||||
expected string
|
||||
expected pkg.License
|
||||
}{
|
||||
{
|
||||
name: "github.com/someorg/somename",
|
||||
version: "v0.3.2",
|
||||
expected: "Apache-2.0",
|
||||
name: "github.com/someorg/somename",
|
||||
version: "v0.3.2",
|
||||
expected: pkg.License{
|
||||
Value: "Apache-2.0",
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: license.Concluded,
|
||||
Locations: source.NewLocationSet(loc1),
|
||||
URLs: internal.NewStringSet(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "github.com/CapORG/CapProject",
|
||||
version: "v4.111.5",
|
||||
expected: "MIT",
|
||||
name: "github.com/CapORG/CapProject",
|
||||
version: "v4.111.5",
|
||||
expected: pkg.License{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Concluded,
|
||||
Locations: source.NewLocationSet(loc2),
|
||||
URLs: internal.NewStringSet(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -18,13 +18,13 @@ func (c *goBinaryCataloger) newGoBinaryPackage(resolver source.FileResolver, dep
|
||||
|
||||
licenses, err := c.licenses.getLicenses(resolver, dep.Path, dep.Version)
|
||||
if err != nil {
|
||||
log.Tracef("error getting licenses for package: %s %v", dep.Path, err)
|
||||
log.Tracef("error getting licenses for golang package: %s %v", dep.Path, err)
|
||||
}
|
||||
|
||||
p := pkg.Package{
|
||||
Name: dep.Path,
|
||||
Version: dep.Version,
|
||||
Licenses: licenses,
|
||||
Licenses: pkg.NewLicenseSet(licenses...),
|
||||
PURL: packageURL(dep.Path, dep.Version),
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -34,6 +35,11 @@ var (
|
||||
// devel is used to recognize the current default version when a golang main distribution is built
|
||||
// https://github.com/golang/go/issues/29228 this issue has more details on the progress of being able to
|
||||
// inject the correct version into the main module of the build process
|
||||
|
||||
knownBuildFlagPatterns = []*regexp.Regexp{
|
||||
regexp.MustCompile(`(?m)\.([gG]it)?([bB]uild)?[vV]ersion=(\S+/)*(?P<version>v?\d+.\d+.\d+[-\w]*)`),
|
||||
regexp.MustCompile(`(?m)\.([tT]ag)=(\S+/)*(?P<version>v?\d+.\d+.\d+[-\w]*)`),
|
||||
}
|
||||
)
|
||||
|
||||
const devel = "(devel)"
|
||||
@@ -71,27 +77,82 @@ func (c *goBinaryCataloger) makeGoMainPackage(resolver source.FileResolver, mod
|
||||
gbs,
|
||||
location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
)
|
||||
if main.Version == devel {
|
||||
if version, ok := gbs["vcs.revision"]; ok {
|
||||
if timestamp, ok := gbs["vcs.time"]; ok {
|
||||
//NOTE: err is ignored, because if parsing fails
|
||||
// we still use the empty Time{} struct to generate an empty date, like 00010101000000
|
||||
// for consistency with the pseudo-version format: https://go.dev/ref/mod#pseudo-versions
|
||||
ts, _ := time.Parse(time.RFC3339, timestamp)
|
||||
if len(version) >= 12 {
|
||||
version = version[:12]
|
||||
}
|
||||
version = module.PseudoVersion("", "", ts, version)
|
||||
|
||||
if main.Version != devel {
|
||||
return main
|
||||
}
|
||||
|
||||
version, hasVersion := gbs["vcs.revision"]
|
||||
timestamp, hasTimestamp := gbs["vcs.time"]
|
||||
|
||||
if hasVersion {
|
||||
if hasTimestamp {
|
||||
//NOTE: err is ignored, because if parsing fails
|
||||
// we still use the empty Time{} struct to generate an empty date, like 00010101000000
|
||||
// for consistency with the pseudo-version format: https://go.dev/ref/mod#pseudo-versions
|
||||
ts, _ := time.Parse(time.RFC3339, timestamp)
|
||||
if len(version) >= 12 {
|
||||
version = version[:12]
|
||||
}
|
||||
|
||||
var ldflags string
|
||||
if metadata, ok := main.Metadata.(pkg.GolangBinMetadata); ok {
|
||||
ldflags = metadata.BuildSettings["-ldflags"]
|
||||
}
|
||||
|
||||
majorVersion, fullVersion := extractVersionFromLDFlags(ldflags)
|
||||
if fullVersion != "" {
|
||||
// we've found a specific version from the ldflags! use it as the version.
|
||||
// why not combine that with the pseudo version (e.g. v1.2.3-0.20210101000000-abcdef123456)?
|
||||
// short answer: we're assuming that if a specific semver was provided in the ldflags that
|
||||
// there is a matching vcs tag to match that could be referenced. This assumption could
|
||||
// be incorrect in terms of the go.mod contents, but is not incorrect in terms of the logical
|
||||
// version of the package.
|
||||
version = fullVersion
|
||||
} else {
|
||||
version = module.PseudoVersion(majorVersion, fullVersion, ts, version)
|
||||
}
|
||||
main.Version = version
|
||||
main.PURL = packageURL(main.Name, main.Version)
|
||||
main.SetID()
|
||||
}
|
||||
|
||||
main.Version = version
|
||||
main.PURL = packageURL(main.Name, main.Version)
|
||||
|
||||
main.SetID()
|
||||
}
|
||||
|
||||
return main
|
||||
}
|
||||
|
||||
func extractVersionFromLDFlags(ldflags string) (majorVersion string, fullVersion string) {
|
||||
if ldflags == "" {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
for _, pattern := range knownBuildFlagPatterns {
|
||||
groups := internal.MatchNamedCaptureGroups(pattern, ldflags)
|
||||
v, ok := groups["version"]
|
||||
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
fullVersion = v
|
||||
if !strings.HasPrefix(v, "v") {
|
||||
fullVersion = fmt.Sprintf("v%s", v)
|
||||
}
|
||||
components := strings.Split(v, ".")
|
||||
|
||||
if len(components) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
majorVersion = strings.TrimPrefix(components[0], "v")
|
||||
return majorVersion, fullVersion
|
||||
}
|
||||
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// getArchs finds a binary architecture by two ways:
|
||||
// 1) reading build info from binaries compiled by go1.18+
|
||||
// 2) reading file headers from binaries compiled by < go1.18
|
||||
|
||||
@@ -299,7 +299,101 @@ func TestBuildGoPkgInfo(t *testing.T) {
|
||||
expected: []pkg.Package{unmodifiedMain},
|
||||
},
|
||||
{
|
||||
name: "parse main mod and replace devel version",
|
||||
name: "parse main mod and replace devel pseudo version and ldflags exists (but contains no version)",
|
||||
arch: archDetails,
|
||||
mod: &debug.BuildInfo{
|
||||
GoVersion: goCompiledVersion,
|
||||
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
|
||||
Settings: []debug.BuildSetting{
|
||||
{Key: "GOARCH", Value: archDetails},
|
||||
{Key: "GOOS", Value: "darwin"},
|
||||
{Key: "GOAMD64", Value: "v1"},
|
||||
{Key: "vcs.revision", Value: "41bc6bb410352845f22766e27dd48ba93aa825a4"},
|
||||
{Key: "vcs.time", Value: "2022-10-14T19:54:57Z"},
|
||||
{Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X blah=foobar`},
|
||||
},
|
||||
},
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "github.com/anchore/syft",
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
Version: "v0.0.0-20221014195457-41bc6bb41035",
|
||||
PURL: "pkg:golang/github.com/anchore/syft@v0.0.0-20221014195457-41bc6bb41035",
|
||||
Locations: source.NewLocationSet(
|
||||
source.NewLocationFromCoordinates(
|
||||
source.Coordinates{
|
||||
RealPath: "/a-path",
|
||||
FileSystemID: "layer-id",
|
||||
},
|
||||
).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
MetadataType: pkg.GolangBinMetadataType,
|
||||
Metadata: pkg.GolangBinMetadata{
|
||||
GoCompiledVersion: goCompiledVersion,
|
||||
Architecture: archDetails,
|
||||
BuildSettings: map[string]string{
|
||||
"GOARCH": archDetails,
|
||||
"GOOS": "darwin",
|
||||
"GOAMD64": "v1",
|
||||
"vcs.revision": "41bc6bb410352845f22766e27dd48ba93aa825a4",
|
||||
"vcs.time": "2022-10-14T19:54:57Z",
|
||||
"-ldflags": `build -ldflags="-w -s -extldflags '-static' -X blah=foobar`,
|
||||
},
|
||||
MainModule: "github.com/anchore/syft",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "parse main mod and replace devel version with one from ldflags",
|
||||
arch: archDetails,
|
||||
mod: &debug.BuildInfo{
|
||||
GoVersion: goCompiledVersion,
|
||||
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
|
||||
Settings: []debug.BuildSetting{
|
||||
{Key: "GOARCH", Value: archDetails},
|
||||
{Key: "GOOS", Value: "darwin"},
|
||||
{Key: "GOAMD64", Value: "v1"},
|
||||
{Key: "vcs.revision", Value: "41bc6bb410352845f22766e27dd48ba93aa825a4"},
|
||||
{Key: "vcs.time", Value: "2022-10-14T19:54:57Z"},
|
||||
{Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0`},
|
||||
},
|
||||
},
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "github.com/anchore/syft",
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
Version: "v0.79.0",
|
||||
PURL: "pkg:golang/github.com/anchore/syft@v0.79.0",
|
||||
Locations: source.NewLocationSet(
|
||||
source.NewLocationFromCoordinates(
|
||||
source.Coordinates{
|
||||
RealPath: "/a-path",
|
||||
FileSystemID: "layer-id",
|
||||
},
|
||||
).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
MetadataType: pkg.GolangBinMetadataType,
|
||||
Metadata: pkg.GolangBinMetadata{
|
||||
GoCompiledVersion: goCompiledVersion,
|
||||
Architecture: archDetails,
|
||||
BuildSettings: map[string]string{
|
||||
"GOARCH": archDetails,
|
||||
"GOOS": "darwin",
|
||||
"GOAMD64": "v1",
|
||||
"vcs.revision": "41bc6bb410352845f22766e27dd48ba93aa825a4",
|
||||
"vcs.time": "2022-10-14T19:54:57Z",
|
||||
"-ldflags": `build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0`,
|
||||
},
|
||||
MainModule: "github.com/anchore/syft",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "parse main mod and replace devel version with a pseudo version",
|
||||
arch: archDetails,
|
||||
mod: &debug.BuildInfo{
|
||||
GoVersion: goCompiledVersion,
|
||||
@@ -497,9 +591,6 @@ func TestBuildGoPkgInfo(t *testing.T) {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
for i := range test.expected {
|
||||
p := &test.expected[i]
|
||||
if p.Licenses == nil {
|
||||
p.Licenses = []string{}
|
||||
}
|
||||
p.SetID()
|
||||
}
|
||||
location := source.NewLocationFromCoordinates(
|
||||
@@ -515,3 +606,161 @@ func TestBuildGoPkgInfo(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_extractVersionFromLDFlags(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ldflags string
|
||||
wantMajorVersion string
|
||||
wantFullVersion string
|
||||
}{
|
||||
{
|
||||
name: "empty ldflags",
|
||||
ldflags: "",
|
||||
},
|
||||
{
|
||||
name: "syft ldflags",
|
||||
ldflags: ` build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0 -X github.com/anchore/syft/internal/version.gitCommit=b2b332e8b2b66af0905e98b54ebd713a922be1a8 -X github.com/anchore/syft/internal/version.buildDate=2023-04-21T16:20:25Z -X github.com/anchore/syft/internal/version.gitDescription=v0.79.0 "`,
|
||||
wantMajorVersion: "0",
|
||||
wantFullVersion: "v0.79.0",
|
||||
},
|
||||
{
|
||||
name: "kubectl ldflags",
|
||||
ldflags: ` build -asmflags=all=-trimpath=/workspace/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes
|
||||
build -compiler=gc
|
||||
build -gcflags="all=-trimpath=/workspace/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes "
|
||||
build -ldflags="all=-X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.buildDate=2023-04-12T12:16:51Z' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.buildDate=2023-04-12T12:16:51Z' -X 'k8s.io/client-go/pkg/version.buildDate=2023-04-12T12:16:51Z' -X 'k8s.io/component-base/version.buildDate=2023-04-12T12:16:51Z' -X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitCommit=a1a87a0a2bcd605820920c6b0e618a8ab7d117d4' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitCommit=a1a87a0a2bcd605820920c6b0e618a8ab7d117d4' -X 'k8s.io/client-go/pkg/version.gitCommit=a1a87a0a2bcd605820920c6b0e618a8ab7d117d4' -X 'k8s.io/component-base/version.gitCommit=a1a87a0a2bcd605820920c6b0e618a8ab7d117d4' -X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitTreeState=clean' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitTreeState=clean' -X 'k8s.io/client-go/pkg/version.gitTreeState=clean' -X 'k8s.io/component-base/version.gitTreeState=clean' -X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitVersion=v1.25.9' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitVersion=v1.25.9' -X 'k8s.io/client-go/pkg/version.gitVersion=v1.25.9' -X 'k8s.io/component-base/version.gitVersion=v1.25.9' -X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitMajor=1' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitMajor=1' -X 'k8s.io/client-go/pkg/version.gitMajor=1' -X 'k8s.io/component-base/version.gitMajor=1' -X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitMinor=25' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitMinor=25' -X 'k8s.io/client-go/pkg/version.gitMinor=25' -X 'k8s.io/component-base/version.gitMinor=25' -s -w"`,
|
||||
wantMajorVersion: "1",
|
||||
wantFullVersion: "v1.25.9",
|
||||
},
|
||||
{
|
||||
name: "nerdctl ldflags",
|
||||
ldflags: ` build -ldflags="-s -w -X github.com/containerd/nerdctl/pkg/version.Version=v1.3.1 -X github.com/containerd/nerdctl/pkg/version.Revision=b224b280ff3086516763c7335fc0e0997aca617a"`,
|
||||
wantMajorVersion: "1",
|
||||
wantFullVersion: "v1.3.1",
|
||||
},
|
||||
{
|
||||
name: "limactl ldflags",
|
||||
ldflags: ` build -ldflags="-s -w -X github.com/lima-vm/lima/pkg/version.Version=v0.15.1"`,
|
||||
wantMajorVersion: "0",
|
||||
wantFullVersion: "v0.15.1",
|
||||
},
|
||||
{
|
||||
name: "terraform ldflags",
|
||||
ldflags: ` build -ldflags="-w -s -X 'github.com/hashicorp/terraform/version.Version=1.4.6' -X 'github.com/hashicorp/terraform/version.Prerelease='"`,
|
||||
wantMajorVersion: "1",
|
||||
wantFullVersion: "v1.4.6",
|
||||
},
|
||||
{
|
||||
name: "kube-apiserver ldflags",
|
||||
ldflags: ` build -asmflags=all=-trimpath=/workspace/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes
|
||||
build -buildmode=exe
|
||||
build -compiler=gc
|
||||
build -gcflags="all=-trimpath=/workspace/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes "
|
||||
build -ldflags="all=-X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.buildDate=2023-04-14T13:14:42Z' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.buildDate=2023-04-14T13:14:42Z' -X 'k8s.io/client-go/pkg/version.buildDate=2023-04-14T13:14:42Z' -X 'k8s.io/component-base/version.buildDate=2023-04-14T13:14:42Z' -X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitCommit=4c9411232e10168d7b050c49a1b59f6df9d7ea4b' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitCommit=4c9411232e10168d7b050c49a1b59f6df9d7ea4b' -X 'k8s.io/client-go/pkg/version.gitCommit=4c9411232e10168d7b050c49a1b59f6df9d7ea4b' -X 'k8s.io/component-base/version.gitCommit=4c9411232e10168d7b050c49a1b59f6df9d7ea4b' -X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitTreeState=clean' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitTreeState=clean' -X 'k8s.io/client-go/pkg/version.gitTreeState=clean' -X 'k8s.io/component-base/version.gitTreeState=clean' -X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitVersion=v1.27.1' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitVersion=v1.27.1' -X 'k8s.io/client-go/pkg/version.gitVersion=v1.27.1' -X 'k8s.io/component-base/version.gitVersion=v1.27.1' -X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitMajor=1' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitMajor=1' -X 'k8s.io/client-go/pkg/version.gitMajor=1' -X 'k8s.io/component-base/version.gitMajor=1' -X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitMinor=27' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitMinor=27' -X 'k8s.io/client-go/pkg/version.gitMinor=27' -X 'k8s.io/component-base/version.gitMinor=27' -s -w"`,
|
||||
wantMajorVersion: "1",
|
||||
wantFullVersion: "v1.27.1",
|
||||
},
|
||||
{
|
||||
name: "prometheus ldflags",
|
||||
ldflags: ` build -ldflags="-X github.com/prometheus/common/version.Version=2.44.0 -X github.com/prometheus/common/version.Revision=1ac5131f698ebc60f13fe2727f89b115a41f6558 -X github.com/prometheus/common/version.Branch=HEAD -X github.com/prometheus/common/version.BuildUser=root@739e8181c5db -X github.com/prometheus/common/version.BuildDate=20230514-06:18:11 -extldflags '-static'"
|
||||
build -tags=netgo,builtinassets,stringlabels`,
|
||||
wantMajorVersion: "2",
|
||||
wantFullVersion: "v2.44.0",
|
||||
},
|
||||
{
|
||||
name: "influxdb ldflags",
|
||||
ldflags: ` build -ldflags="-s -w -X main.version=v2.7.1 -X main.commit=407fa622e9 -X main.date=2023-04-28T13:24:27Z -linkmode=external -extld=/musl/x86_64/bin/musl-gcc -extldflags '-fno-PIC -static-pie -Wl,-z,stack-size=8388608'"
|
||||
build -tags=assets,sqlite_foreign_keys,sqlite_json,static_build,noasm`,
|
||||
wantMajorVersion: "2",
|
||||
wantFullVersion: "v2.7.1",
|
||||
},
|
||||
{
|
||||
name: "gitea ldflags",
|
||||
ldflags: ` build -ldflags=" -X \"main.MakeVersion=GNU Make 4.1\" -X \"main.Version=1.19.3\" -X \"main.Tags=bindata sqlite sqlite_unlock_notify\" "`,
|
||||
wantMajorVersion: "1",
|
||||
wantFullVersion: "v1.19.3",
|
||||
},
|
||||
{
|
||||
name: "docker sbom cli ldflags",
|
||||
ldflags: ` build -ldflags="-w -s -extldflags '-static' -X github.com/docker/sbom-cli-plugin/internal/version.version=0.6.1-SNAPSHOT-02cf1c8 -X github.com/docker/sbom-cli-plugin/internal/version.gitCommit=02cf1c888ad6662109ac6e3be618392514a56316 -X github.com/docker/sbom-cli-plugin/internal/version.gitDescription=v0.6.1-dirty "`,
|
||||
wantMajorVersion: "0",
|
||||
wantFullVersion: "v0.6.1-SNAPSHOT-02cf1c8",
|
||||
},
|
||||
{
|
||||
name: "docker scout ldflags",
|
||||
ldflags: ` build -ldflags="-w -s -extldflags '-static' -X github.com/docker/scout-cli-plugin/internal.version=0.10.0 "`,
|
||||
wantMajorVersion: "0",
|
||||
wantFullVersion: "v0.10.0",
|
||||
},
|
||||
{
|
||||
name: "influx telegraf ldflags",
|
||||
ldflags: ` build -ldflags="-w -s -X github.com/influxdata/telegraf/internal.Commit=a3a884a1 -X github.com/influxdata/telegraf/internal.Branch=HEAD -X github.com/influxdata/telegraf/internal.Version=1.26.2"`,
|
||||
wantMajorVersion: "1",
|
||||
wantFullVersion: "v1.26.2",
|
||||
},
|
||||
{
|
||||
name: "argocd ldflags",
|
||||
ldflags: ` build -ldflags="-X github.com/argoproj/argo-cd/v2/common.version=2.7.2 -X github.com/argoproj/argo-cd/v2/common.buildDate=2023-05-12T14:06:49Z -X github.com/argoproj/argo-cd/v2/common.gitCommit=cbee7e6011407ed2d1066c482db74e97e0cc6bdb -X github.com/argoproj/argo-cd/v2/common.gitTreeState=clean -X github.com/argoproj/argo-cd/v2/common.kubectlVersion=v0.24.2 -extldflags=\"-static\""`,
|
||||
wantMajorVersion: "2",
|
||||
wantFullVersion: "v2.7.2",
|
||||
},
|
||||
{
|
||||
name: "kustomize ldflags",
|
||||
ldflags: ` build -ldflags="-s -X sigs.k8s.io/kustomize/api/provenance.version=kustomize/v4.5.7 -X sigs.k8s.io/kustomize/api/provenance.gitCommit=56d82a8378dfc8dc3b3b1085e5a6e67b82966bd7 -X sigs.k8s.io/kustomize/api/provenance.buildDate=2022-08-02T16:35:54Z "`,
|
||||
wantMajorVersion: "4",
|
||||
wantFullVersion: "v4.5.7",
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// negative cases
|
||||
{
|
||||
name: "hugo ldflags",
|
||||
ldflags: ` build -ldflags="-s -w -X github.com/gohugoio/hugo/common/hugo.vendorInfo=gohugoio"`,
|
||||
},
|
||||
{
|
||||
name: "ghostunnel ldflags",
|
||||
ldflags: ` build -ldflags="-X main.version=77d9aaa"`,
|
||||
},
|
||||
{
|
||||
name: "opa ldflags",
|
||||
ldflags: `build -ldflags=" -X github.com/open-policy-agent/opa/version.Hostname=9549178459bc"`,
|
||||
},
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// trickier cases
|
||||
{
|
||||
name: "macvlan plugin for cri-o ldflags",
|
||||
ldflags: ` build -ldflags="-extldflags -static -X github.com/containernetworking/plugins/pkg/utils/buildversion.BuildVersion=v1.2.0"`,
|
||||
wantMajorVersion: "1",
|
||||
wantFullVersion: "v1.2.0",
|
||||
},
|
||||
{
|
||||
name: "coder ldflags",
|
||||
ldflags: ` build -ldflags="-s -w -X 'github.com/coder/coder/buildinfo.tag=0.23.4'"`,
|
||||
wantMajorVersion: "0",
|
||||
wantFullVersion: "v0.23.4",
|
||||
},
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// don't know how to handle these... yet
|
||||
//{
|
||||
// // package name: pkgName: "github.com/krakendio/krakend-ce/v2",
|
||||
// name: "krakenD ldflags",
|
||||
// ldflags: ` build -ldflags="-X github.com/luraproject/lura/v2/core.KrakendVersion=2.3.2 -X github.com/luraproject/lura/v2/core.GoVersion=1.20.4 -X github.com/luraproject/lura/v2/core.GlibcVersion=GLIBC-2.31_(debian-11) "`,
|
||||
// wantMajorVersion: "2.3.2",
|
||||
// wantFullVersion: "v2.3.2",
|
||||
//},
|
||||
//{
|
||||
// // package name: pkgName: "github.com/krakendio/krakend-ce/v2",
|
||||
// name: "krakenD ldflags -- answer embedded in the middle",
|
||||
// ldflags: ` build -ldflags=" -X github.com/luraproject/lura/v2/core.GoVersion=1.20.4 -X github.com/luraproject/lura/v2/core.KrakendVersion=2.3.2 -X github.com/luraproject/lura/v2/core.GlibcVersion=GLIBC-2.31_(debian-11) "`,
|
||||
// wantMajorVersion: "2.3.2",
|
||||
// wantFullVersion: "v2.3.2",
|
||||
//},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotMajorVersion, gotFullVersion := extractVersionFromLDFlags(tt.ldflags)
|
||||
assert.Equal(t, tt.wantMajorVersion, gotMajorVersion, "unexpected major version")
|
||||
assert.Equal(t, tt.wantFullVersion, gotFullVersion, "unexpected full version")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func (c *goModCataloger) parseGoModFile(resolver source.FileResolver, _ *generic
|
||||
packages[m.Mod.Path] = pkg.Package{
|
||||
Name: m.Mod.Path,
|
||||
Version: m.Mod.Version,
|
||||
Licenses: licenses,
|
||||
Licenses: pkg.NewLicenseSet(licenses...),
|
||||
Locations: source.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
PURL: packageURL(m.Mod.Path, m.Mod.Version),
|
||||
Language: pkg.Go,
|
||||
@@ -72,7 +72,7 @@ func (c *goModCataloger) parseGoModFile(resolver source.FileResolver, _ *generic
|
||||
packages[m.New.Path] = pkg.Package{
|
||||
Name: m.New.Path,
|
||||
Version: m.New.Version,
|
||||
Licenses: licenses,
|
||||
Licenses: pkg.NewLicenseSet(licenses...),
|
||||
Locations: source.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
PURL: packageURL(m.New.Path, m.New.Version),
|
||||
Language: pkg.Go,
|
||||
|
||||
@@ -88,12 +88,6 @@ func TestParseGoMod(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixture, func(t *testing.T) {
|
||||
for i := range test.expected {
|
||||
p := &test.expected[i]
|
||||
if p.Licenses == nil {
|
||||
p.Licenses = []string{}
|
||||
}
|
||||
}
|
||||
c := goModCataloger{}
|
||||
pkgtest.NewCatalogTester().
|
||||
FromFile(t, test.fixture).
|
||||
@@ -154,12 +148,6 @@ func Test_GoSumHashes(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixture, func(t *testing.T) {
|
||||
for i := range test.expected {
|
||||
p := &test.expected[i]
|
||||
if p.Licenses == nil {
|
||||
p.Licenses = []string{}
|
||||
}
|
||||
}
|
||||
pkgtest.NewCatalogTester().
|
||||
FromDirectory(t, test.fixture).
|
||||
Expects(test.expected, nil).
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
)
|
||||
|
||||
type locationComparer func(x, y source.Location) bool
|
||||
type licenseComparer func(x, y pkg.License) bool
|
||||
|
||||
type CatalogTester struct {
|
||||
expectedPkgs []pkg.Package
|
||||
@@ -36,12 +37,14 @@ type CatalogTester struct {
|
||||
wantErr require.ErrorAssertionFunc
|
||||
compareOptions []cmp.Option
|
||||
locationComparer locationComparer
|
||||
licenseComparer licenseComparer
|
||||
}
|
||||
|
||||
func NewCatalogTester() *CatalogTester {
|
||||
return &CatalogTester{
|
||||
wantErr: require.NoError,
|
||||
locationComparer: DefaultLocationComparer,
|
||||
licenseComparer: DefaultLicenseComparer,
|
||||
ignoreUnfulfilledPathResponses: map[string][]string{
|
||||
"FilesByPath": {
|
||||
// most catalogers search for a linux release, which will not be fulfilled in testing
|
||||
@@ -59,6 +62,25 @@ func DefaultLocationComparer(x, y source.Location) bool {
|
||||
return cmp.Equal(x.Coordinates, y.Coordinates) && cmp.Equal(x.VirtualPath, y.VirtualPath)
|
||||
}
|
||||
|
||||
func DefaultLicenseComparer(x, y pkg.License) bool {
|
||||
return cmp.Equal(x, y, cmp.Comparer(DefaultLocationComparer), cmp.Comparer(
|
||||
func(x, y source.LocationSet) bool {
|
||||
xs := x.ToSlice()
|
||||
ys := y.ToSlice()
|
||||
if len(xs) != len(ys) {
|
||||
return false
|
||||
}
|
||||
for i, xe := range xs {
|
||||
ye := ys[i]
|
||||
if !DefaultLocationComparer(xe, ye) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
func (p *CatalogTester) FromDirectory(t *testing.T, path string) *CatalogTester {
|
||||
t.Helper()
|
||||
|
||||
@@ -139,6 +161,26 @@ func (p *CatalogTester) IgnoreLocationLayer() *CatalogTester {
|
||||
p.locationComparer = func(x, y source.Location) bool {
|
||||
return cmp.Equal(x.Coordinates.RealPath, y.Coordinates.RealPath) && cmp.Equal(x.VirtualPath, y.VirtualPath)
|
||||
}
|
||||
|
||||
// we need to update the license comparer to use the ignored location layer
|
||||
p.licenseComparer = func(x, y pkg.License) bool {
|
||||
return cmp.Equal(x, y, cmp.Comparer(p.locationComparer), cmp.Comparer(
|
||||
func(x, y source.LocationSet) bool {
|
||||
xs := x.ToSlice()
|
||||
ys := y.ToSlice()
|
||||
if len(xs) != len(ys) {
|
||||
return false
|
||||
}
|
||||
for i, xe := range xs {
|
||||
ye := ys[i]
|
||||
if !p.locationComparer(xe, ye) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}))
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
@@ -209,6 +251,7 @@ func (p *CatalogTester) TestCataloger(t *testing.T, cataloger pkg.Cataloger) {
|
||||
}
|
||||
}
|
||||
|
||||
// nolint:funlen
|
||||
func (p *CatalogTester) assertPkgs(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {
|
||||
t.Helper()
|
||||
|
||||
@@ -233,6 +276,30 @@ func (p *CatalogTester) assertPkgs(t *testing.T, pkgs []pkg.Package, relationshi
|
||||
return true
|
||||
},
|
||||
),
|
||||
cmp.Comparer(
|
||||
func(x, y pkg.LicenseSet) bool {
|
||||
xs := x.ToSlice()
|
||||
ys := y.ToSlice()
|
||||
|
||||
if len(xs) != len(ys) {
|
||||
return false
|
||||
}
|
||||
for i, xe := range xs {
|
||||
ye := ys[i]
|
||||
if !p.licenseComparer(xe, ye) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
),
|
||||
cmp.Comparer(
|
||||
p.locationComparer,
|
||||
),
|
||||
cmp.Comparer(
|
||||
p.licenseComparer,
|
||||
),
|
||||
)
|
||||
|
||||
{
|
||||
@@ -247,7 +314,6 @@ func (p *CatalogTester) assertPkgs(t *testing.T, pkgs []pkg.Package, relationshi
|
||||
t.Errorf("unexpected packages from parsing (-expected +actual)\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var r diffReporter
|
||||
var opts []cmp.Option
|
||||
@@ -296,6 +362,30 @@ func AssertPackagesEqual(t *testing.T, a, b pkg.Package) {
|
||||
return true
|
||||
},
|
||||
),
|
||||
cmp.Comparer(
|
||||
func(x, y pkg.LicenseSet) bool {
|
||||
xs := x.ToSlice()
|
||||
ys := y.ToSlice()
|
||||
|
||||
if len(xs) != len(ys) {
|
||||
return false
|
||||
}
|
||||
for i, xe := range xs {
|
||||
ye := ys[i]
|
||||
if !DefaultLicenseComparer(xe, ye) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
),
|
||||
cmp.Comparer(
|
||||
DefaultLocationComparer,
|
||||
),
|
||||
cmp.Comparer(
|
||||
DefaultLicenseComparer,
|
||||
),
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(a, b, opts...); diff != "" {
|
||||
|
||||
@@ -185,11 +185,13 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) {
|
||||
log.Warnf("failed to create digest for file=%q: %+v", j.archivePath, err)
|
||||
}
|
||||
|
||||
// we use j.location because we want to associate the license declaration with where we discovered the contents in the manifest
|
||||
licenses := pkg.NewLicensesFromLocation(j.location, selectLicenses(manifest)...)
|
||||
return &pkg.Package{
|
||||
Name: selectName(manifest, j.fileInfo),
|
||||
Version: selectVersion(manifest, j.fileInfo),
|
||||
Licenses: selectLicense(manifest),
|
||||
Language: pkg.Java,
|
||||
Licenses: pkg.NewLicenseSet(licenses...),
|
||||
Locations: source.NewLocationSet(
|
||||
j.location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
|
||||
@@ -96,10 +96,12 @@ func TestParseJar(t *testing.T) {
|
||||
},
|
||||
expected: map[string]pkg.Package{
|
||||
"example-jenkins-plugin": {
|
||||
Name: "example-jenkins-plugin",
|
||||
Version: "1.0-SNAPSHOT",
|
||||
PURL: "pkg:maven/io.jenkins.plugins/example-jenkins-plugin@1.0-SNAPSHOT",
|
||||
Licenses: []string{"MIT License"},
|
||||
Name: "example-jenkins-plugin",
|
||||
Version: "1.0-SNAPSHOT",
|
||||
PURL: "pkg:maven/io.jenkins.plugins/example-jenkins-plugin@1.0-SNAPSHOT",
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT License", source.NewLocation("test-fixtures/java-builds/packages/example-jenkins-plugin.hpi")),
|
||||
),
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
@@ -150,7 +152,6 @@ func TestParseJar(t *testing.T) {
|
||||
Name: "example-java-app-gradle",
|
||||
Version: "0.1.0",
|
||||
PURL: "pkg:maven/example-java-app-gradle/example-java-app-gradle@0.1.0",
|
||||
Licenses: []string{},
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
@@ -205,7 +206,6 @@ func TestParseJar(t *testing.T) {
|
||||
Name: "example-java-app-maven",
|
||||
Version: "0.1.0",
|
||||
PURL: "pkg:maven/org.anchore/example-java-app-maven@0.1.0",
|
||||
Licenses: []string{},
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
|
||||
@@ -157,7 +157,7 @@ func selectVersion(manifest *pkg.JavaManifest, filenameObj archiveFilename) stri
|
||||
return ""
|
||||
}
|
||||
|
||||
func selectLicense(manifest *pkg.JavaManifest) []string {
|
||||
func selectLicenses(manifest *pkg.JavaManifest) []string {
|
||||
result := []string{}
|
||||
if manifest == nil {
|
||||
return result
|
||||
|
||||
@@ -12,14 +12,16 @@ func Test_JavascriptCataloger(t *testing.T) {
|
||||
locationSet := source.NewLocationSet(source.NewLocation("package-lock.json"))
|
||||
expectedPkgs := []pkg.Package{
|
||||
{
|
||||
Name: "@actions/core",
|
||||
Version: "1.6.0",
|
||||
FoundBy: "javascript-lock-cataloger",
|
||||
PURL: "pkg:npm/%40actions/core@1.6.0",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"MIT"},
|
||||
Name: "@actions/core",
|
||||
Version: "1.6.0",
|
||||
FoundBy: "javascript-lock-cataloger",
|
||||
PURL: "pkg:npm/%40actions/core@1.6.0",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation("package-lock.json")),
|
||||
),
|
||||
MetadataType: pkg.NpmPackageLockJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz", Integrity: "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw=="},
|
||||
},
|
||||
@@ -35,14 +37,16 @@ func Test_JavascriptCataloger(t *testing.T) {
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", Integrity: "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="},
|
||||
},
|
||||
{
|
||||
Name: "cowsay",
|
||||
Version: "1.4.0",
|
||||
FoundBy: "javascript-lock-cataloger",
|
||||
PURL: "pkg:npm/cowsay@1.4.0",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"MIT"},
|
||||
Name: "cowsay",
|
||||
Version: "1.4.0",
|
||||
FoundBy: "javascript-lock-cataloger",
|
||||
PURL: "pkg:npm/cowsay@1.4.0",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation("package-lock.json")),
|
||||
),
|
||||
MetadataType: pkg.NpmPackageLockJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/cowsay/-/cowsay-1.4.0.tgz", Integrity: "sha512-rdg5k5PsHFVJheO/pmE3aDg2rUDDTfPJau6yYkZYlHFktUz+UxbE+IgnUAEyyCyv4noL5ltxXD0gZzmHPCy/9g=="},
|
||||
},
|
||||
|
||||
@@ -12,30 +12,30 @@ import (
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func newPackageJSONPackage(u packageJSON, locations ...source.Location) pkg.Package {
|
||||
licenses, err := u.licensesFromJSON()
|
||||
func newPackageJSONPackage(u packageJSON, indexLocation source.Location) pkg.Package {
|
||||
licenseCandidates, err := u.licensesFromJSON()
|
||||
if err != nil {
|
||||
log.Warnf("unable to extract licenses from javascript package.json: %+v", err)
|
||||
}
|
||||
|
||||
license := pkg.NewLicensesFromLocation(indexLocation, licenseCandidates...)
|
||||
p := pkg.Package{
|
||||
Name: u.Name,
|
||||
Version: u.Version,
|
||||
Licenses: licenses,
|
||||
PURL: packageURL(u.Name, u.Version),
|
||||
Locations: source.NewLocationSet(locations...),
|
||||
Locations: source.NewLocationSet(indexLocation),
|
||||
Language: pkg.JavaScript,
|
||||
Licenses: pkg.NewLicenseSet(license...),
|
||||
Type: pkg.NpmPkg,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: u.Name,
|
||||
Version: u.Version,
|
||||
Description: u.Description,
|
||||
Author: u.Author.AuthorString(),
|
||||
Homepage: u.Homepage,
|
||||
URL: u.Repository.URL,
|
||||
Licenses: licenses,
|
||||
Private: u.Private,
|
||||
Description: u.Description,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -77,12 +77,6 @@ func newPackageLockV1Package(resolver source.FileResolver, location source.Locat
|
||||
}
|
||||
|
||||
func newPackageLockV2Package(resolver source.FileResolver, location source.Location, name string, u lockPackage) pkg.Package {
|
||||
var licenses []string
|
||||
|
||||
if u.License != nil {
|
||||
licenses = u.License
|
||||
}
|
||||
|
||||
return finalizeLockPkg(
|
||||
resolver,
|
||||
location,
|
||||
@@ -90,10 +84,10 @@ func newPackageLockV2Package(resolver source.FileResolver, location source.Locat
|
||||
Name: name,
|
||||
Version: u.Version,
|
||||
Locations: source.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(location, u.License...)...),
|
||||
PURL: packageURL(name, u.Version),
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: licenses,
|
||||
MetadataType: pkg.NpmPackageLockJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: u.Resolved, Integrity: u.Integrity},
|
||||
},
|
||||
@@ -131,7 +125,8 @@ func newYarnLockPackage(resolver source.FileResolver, location source.Location,
|
||||
}
|
||||
|
||||
func finalizeLockPkg(resolver source.FileResolver, location source.Location, p pkg.Package) pkg.Package {
|
||||
p.Licenses = append(p.Licenses, addLicenses(p.Name, resolver, location)...)
|
||||
licenseCandidate := addLicenses(p.Name, resolver, location)
|
||||
p.Licenses.Add(pkg.NewLicensesFromLocation(location, licenseCandidate...)...)
|
||||
p.SetID()
|
||||
return p
|
||||
}
|
||||
@@ -140,13 +135,13 @@ func addLicenses(name string, resolver source.FileResolver, location source.Loca
|
||||
if resolver == nil {
|
||||
return allLicenses
|
||||
}
|
||||
|
||||
dir := path.Dir(location.RealPath)
|
||||
pkgPath := []string{dir, "node_modules"}
|
||||
pkgPath = append(pkgPath, strings.Split(name, "/")...)
|
||||
pkgPath = append(pkgPath, "package.json")
|
||||
pkgFile := path.Join(pkgPath...)
|
||||
locations, err := resolver.FilesByPath(pkgFile)
|
||||
|
||||
if err != nil {
|
||||
log.Debugf("an error occurred attempting to read: %s - %+v", pkgFile, err)
|
||||
return allLicenses
|
||||
|
||||
@@ -140,7 +140,7 @@ func (r *repository) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type license struct {
|
||||
type npmPackageLicense struct {
|
||||
Type string `json:"type"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
@@ -154,7 +154,7 @@ func licenseFromJSON(b []byte) (string, error) {
|
||||
}
|
||||
|
||||
// then try as object (this format is deprecated)
|
||||
var licenseObject license
|
||||
var licenseObject npmPackageLicense
|
||||
err = json.Unmarshal(b, &licenseObject)
|
||||
if err == nil {
|
||||
return licenseObject.Type, nil
|
||||
@@ -178,7 +178,7 @@ func (p packageJSON) licensesFromJSON() ([]string, error) {
|
||||
|
||||
// The "licenses" field is deprecated. It should be inspected as a last resort.
|
||||
if multiLicense != nil && err == nil {
|
||||
mapLicenses := func(licenses []license) []string {
|
||||
mapLicenses := func(licenses []npmPackageLicense) []string {
|
||||
mappedLicenses := make([]string, len(licenses))
|
||||
for i, l := range licenses {
|
||||
mappedLicenses[i] = l.Type
|
||||
@@ -192,8 +192,8 @@ func (p packageJSON) licensesFromJSON() ([]string, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func licensesFromJSON(b []byte) ([]license, error) {
|
||||
var licenseObject []license
|
||||
func licensesFromJSON(b []byte) ([]npmPackageLicense, error) {
|
||||
var licenseObject []npmPackageLicense
|
||||
err := json.Unmarshal(b, &licenseObject)
|
||||
if err == nil {
|
||||
return licenseObject, nil
|
||||
|
||||
@@ -18,12 +18,14 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
{
|
||||
Fixture: "test-fixtures/pkg-json/package.json",
|
||||
ExpectedPkg: pkg.Package{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Language: pkg.JavaScript,
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Language: pkg.JavaScript,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("Artistic-2.0", source.NewLocation("test-fixtures/pkg-json/package.json")),
|
||||
),
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "npm",
|
||||
@@ -31,7 +33,6 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
@@ -39,12 +40,14 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
{
|
||||
Fixture: "test-fixtures/pkg-json/package-license-object.json",
|
||||
ExpectedPkg: pkg.Package{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"ISC"},
|
||||
Language: pkg.JavaScript,
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Language: pkg.JavaScript,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("ISC", source.NewLocation("test-fixtures/pkg-json/package-license-object.json")),
|
||||
),
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "npm",
|
||||
@@ -52,7 +55,6 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"ISC"},
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
@@ -60,11 +62,14 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
{
|
||||
Fixture: "test-fixtures/pkg-json/package-license-objects.json",
|
||||
ExpectedPkg: pkg.Package{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"MIT", "Apache-2.0"},
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation("test-fixtures/pkg-json/package-license-objects.json")),
|
||||
pkg.NewLicenseFromLocations("Apache-2.0", source.NewLocation("test-fixtures/pkg-json/package-license-objects.json")),
|
||||
),
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
@@ -73,7 +78,6 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"MIT", "Apache-2.0"},
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
@@ -85,7 +89,6 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: nil,
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
@@ -94,7 +97,6 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: nil,
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
@@ -106,7 +108,6 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{},
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
@@ -115,7 +116,6 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{},
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
@@ -123,11 +123,13 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
{
|
||||
Fixture: "test-fixtures/pkg-json/package-nested-author.json",
|
||||
ExpectedPkg: pkg.Package{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("Artistic-2.0", source.NewLocation("test-fixtures/pkg-json/package-nested-author.json")),
|
||||
),
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
@@ -136,7 +138,6 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
@@ -144,11 +145,13 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
{
|
||||
Fixture: "test-fixtures/pkg-json/package-repo-string.json",
|
||||
ExpectedPkg: pkg.Package{
|
||||
Name: "function-bind",
|
||||
Version: "1.1.1",
|
||||
PURL: "pkg:npm/function-bind@1.1.1",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"MIT"},
|
||||
Name: "function-bind",
|
||||
Version: "1.1.1",
|
||||
PURL: "pkg:npm/function-bind@1.1.1",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation("test-fixtures/pkg-json/package-repo-string.json")),
|
||||
),
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
@@ -157,7 +160,6 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Author: "Raynos <raynos2@gmail.com>",
|
||||
Homepage: "https://github.com/Raynos/function-bind",
|
||||
URL: "git://github.com/Raynos/function-bind.git",
|
||||
Licenses: []string{"MIT"},
|
||||
Description: "Implementation of Function.prototype.bind",
|
||||
},
|
||||
},
|
||||
@@ -165,11 +167,13 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
{
|
||||
Fixture: "test-fixtures/pkg-json/package-private.json",
|
||||
ExpectedPkg: pkg.Package{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("Artistic-2.0", source.NewLocation("test-fixtures/pkg-json/package-private.json")),
|
||||
),
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
@@ -178,7 +182,6 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Private: true,
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
|
||||
@@ -25,39 +25,6 @@ type packageLock struct {
|
||||
Packages map[string]lockPackage
|
||||
}
|
||||
|
||||
// packageLockLicense
|
||||
type packageLockLicense []string
|
||||
|
||||
func (licenses *packageLockLicense) UnmarshalJSON(data []byte) (err error) {
|
||||
// The license field could be either a string or an array.
|
||||
|
||||
// 1. An array
|
||||
var arr []string
|
||||
if err := json.Unmarshal(data, &arr); err == nil {
|
||||
*licenses = arr
|
||||
return nil
|
||||
}
|
||||
|
||||
// 2. A string
|
||||
var str string
|
||||
if err = json.Unmarshal(data, &str); err == nil {
|
||||
*licenses = make([]string, 1)
|
||||
(*licenses)[0] = str
|
||||
return nil
|
||||
}
|
||||
|
||||
// debug the content we did not expect
|
||||
if len(data) > 0 {
|
||||
log.WithFields("license", string(data)).Debug("Unable to parse the following `license` value in package-lock.json")
|
||||
}
|
||||
|
||||
// 3. Unexpected
|
||||
// In case we are unable to parse the license field,
|
||||
// i.e if we have not covered the full specification,
|
||||
// we do not want to throw an error, instead assign nil.
|
||||
return nil
|
||||
}
|
||||
|
||||
// lockDependency represents a single package dependency listed in the package.lock json file
|
||||
type lockDependency struct {
|
||||
Version string `json:"version"`
|
||||
@@ -73,6 +40,9 @@ type lockPackage struct {
|
||||
License packageLockLicense `json:"license"`
|
||||
}
|
||||
|
||||
// packageLockLicense
|
||||
type packageLockLicense []string
|
||||
|
||||
// parsePackageLock parses a package-lock.json and returns the discovered JavaScript packages.
|
||||
func parsePackageLock(resolver source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
// in the case we find package-lock.json files in the node_modules directories, skip those
|
||||
@@ -125,6 +95,36 @@ func parsePackageLock(resolver source.FileResolver, _ *generic.Environment, read
|
||||
return pkgs, nil, nil
|
||||
}
|
||||
|
||||
func (licenses *packageLockLicense) UnmarshalJSON(data []byte) (err error) {
|
||||
// The license field could be either a string or an array.
|
||||
|
||||
// 1. An array
|
||||
var arr []string
|
||||
if err := json.Unmarshal(data, &arr); err == nil {
|
||||
*licenses = arr
|
||||
return nil
|
||||
}
|
||||
|
||||
// 2. A string
|
||||
var str string
|
||||
if err = json.Unmarshal(data, &str); err == nil {
|
||||
*licenses = make([]string, 1)
|
||||
(*licenses)[0] = str
|
||||
return nil
|
||||
}
|
||||
|
||||
// debug the content we did not expect
|
||||
if len(data) > 0 {
|
||||
log.WithFields("license", string(data)).Debug("Unable to parse the following `license` value in package-lock.json")
|
||||
}
|
||||
|
||||
// 3. Unexpected
|
||||
// In case we are unable to parse the license field,
|
||||
// i.e if we have not covered the full specification,
|
||||
// we do not want to throw an error, instead assign nil.
|
||||
return nil
|
||||
}
|
||||
|
||||
func getNameFromPath(path string) string {
|
||||
parts := strings.Split(path, "node_modules/")
|
||||
return parts[len(parts)-1]
|
||||
|
||||
@@ -134,42 +134,50 @@ func TestParsePackageLockV2(t *testing.T) {
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{},
|
||||
},
|
||||
{
|
||||
Name: "@types/prop-types",
|
||||
Version: "15.7.5",
|
||||
PURL: "pkg:npm/%40types/prop-types@15.7.5",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"MIT"},
|
||||
Name: "@types/prop-types",
|
||||
Version: "15.7.5",
|
||||
PURL: "pkg:npm/%40types/prop-types@15.7.5",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)),
|
||||
),
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", Integrity: "sha1-XxnSuFqY6VWANvajysyIGUIPBc8="},
|
||||
},
|
||||
{
|
||||
Name: "@types/react",
|
||||
Version: "18.0.17",
|
||||
PURL: "pkg:npm/%40types/react@18.0.17",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"MIT"},
|
||||
Name: "@types/react",
|
||||
Version: "18.0.17",
|
||||
PURL: "pkg:npm/%40types/react@18.0.17",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)),
|
||||
),
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/@types/react/-/react-18.0.17.tgz", Integrity: "sha1-RYPZwyLWfv5LOak10iPtzHBQzPQ="},
|
||||
},
|
||||
{
|
||||
Name: "@types/scheduler",
|
||||
Version: "0.16.2",
|
||||
PURL: "pkg:npm/%40types/scheduler@0.16.2",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"MIT"},
|
||||
Name: "@types/scheduler",
|
||||
Version: "0.16.2",
|
||||
PURL: "pkg:npm/%40types/scheduler@0.16.2",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)),
|
||||
),
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", Integrity: "sha1-GmL4lSVyPd4kuhsBsJK/XfitTTk="},
|
||||
},
|
||||
{
|
||||
Name: "csstype",
|
||||
Version: "3.1.0",
|
||||
PURL: "pkg:npm/csstype@3.1.0",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"MIT"},
|
||||
Name: "csstype",
|
||||
Version: "3.1.0",
|
||||
PURL: "pkg:npm/csstype@3.1.0",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)),
|
||||
),
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", Integrity: "sha1-TdysNxjXh8+d8NG30VAzklyPKfI="},
|
||||
},
|
||||
@@ -268,33 +276,35 @@ func TestParsePackageLockAlias(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
v2Pkg := pkg.Package{
|
||||
Name: "alias-check",
|
||||
Version: "1.0.0",
|
||||
PURL: "pkg:npm/alias-check@1.0.0",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"ISC"},
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{},
|
||||
}
|
||||
|
||||
packageLockV1 := "test-fixtures/pkg-lock/alias-package-lock-1.json"
|
||||
packageLockV2 := "test-fixtures/pkg-lock/alias-package-lock-2.json"
|
||||
packageLocks := []string{packageLockV1, packageLockV2}
|
||||
|
||||
for _, packageLock := range packageLocks {
|
||||
v2Pkg := pkg.Package{
|
||||
Name: "alias-check",
|
||||
Version: "1.0.0",
|
||||
PURL: "pkg:npm/alias-check@1.0.0",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("ISC", source.NewLocation(packageLockV2)),
|
||||
),
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{},
|
||||
}
|
||||
|
||||
for _, pl := range packageLocks {
|
||||
expected := make([]pkg.Package, len(commonPkgs))
|
||||
copy(expected, commonPkgs)
|
||||
|
||||
if packageLock == packageLockV2 {
|
||||
if pl == packageLockV2 {
|
||||
expected = append(expected, v2Pkg)
|
||||
}
|
||||
|
||||
for i := range expected {
|
||||
expected[i].Locations.Add(source.NewLocation(packageLock))
|
||||
expected[i].Locations.Add(source.NewLocation(pl))
|
||||
}
|
||||
pkgtest.TestFileParser(t, packageLock, parsePackageLock, expected, expectedRelationships)
|
||||
pkgtest.TestFileParser(t, pl, parsePackageLock, expected, expectedRelationships)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,31 +313,39 @@ func TestParsePackageLockLicenseWithArray(t *testing.T) {
|
||||
var expectedRelationships []artifact.Relationship
|
||||
expectedPkgs := []pkg.Package{
|
||||
{
|
||||
Name: "tmp",
|
||||
Version: "1.0.0",
|
||||
Licenses: []string{"ISC"},
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Name: "tmp",
|
||||
Version: "1.0.0",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("ISC", source.NewLocation(fixture)),
|
||||
),
|
||||
PURL: "pkg:npm/tmp@1.0.0",
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{},
|
||||
},
|
||||
{
|
||||
Name: "pause-stream",
|
||||
Version: "0.0.11",
|
||||
Licenses: []string{"MIT", "Apache2"},
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Name: "pause-stream",
|
||||
Version: "0.0.11",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)),
|
||||
pkg.NewLicenseFromLocations("Apache2", source.NewLocation(fixture)),
|
||||
),
|
||||
PURL: "pkg:npm/pause-stream@0.0.11",
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{},
|
||||
},
|
||||
{
|
||||
Name: "through",
|
||||
Version: "2.3.8",
|
||||
Licenses: []string{"MIT"},
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Name: "through",
|
||||
Version: "2.3.8",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)),
|
||||
),
|
||||
PURL: "pkg:npm/through@2.3.8",
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{},
|
||||
|
||||
@@ -3,10 +3,13 @@ package javascript
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
@@ -17,8 +20,9 @@ import (
|
||||
var _ generic.Parser = parsePnpmLock
|
||||
|
||||
type pnpmLockYaml struct {
|
||||
Dependencies map[string]string `json:"dependencies"`
|
||||
Packages map[string]interface{} `json:"packages"`
|
||||
Version string `json:"lockfileVersion" yaml:"lockfileVersion"`
|
||||
Dependencies map[string]interface{} `json:"dependencies" yaml:"dependencies"`
|
||||
Packages map[string]interface{} `json:"packages" yaml:"packages"`
|
||||
}
|
||||
|
||||
func parsePnpmLock(resolver source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
@@ -34,19 +38,55 @@ func parsePnpmLock(resolver source.FileResolver, _ *generic.Environment, reader
|
||||
return nil, nil, fmt.Errorf("failed to parse pnpm-lock.yaml file: %w", err)
|
||||
}
|
||||
|
||||
for name, version := range lockFile.Dependencies {
|
||||
lockVersion, _ := strconv.ParseFloat(lockFile.Version, 64)
|
||||
|
||||
for name, info := range lockFile.Dependencies {
|
||||
version := ""
|
||||
|
||||
switch info := info.(type) {
|
||||
case string:
|
||||
version = info
|
||||
case map[string]interface{}:
|
||||
v, ok := info["version"]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
ver, ok := v.(string)
|
||||
if ok {
|
||||
version = parseVersion(ver)
|
||||
}
|
||||
default:
|
||||
log.Tracef("unsupported pnpm dependency type: %+v", info)
|
||||
continue
|
||||
}
|
||||
|
||||
if hasPkg(pkgs, name, version) {
|
||||
continue
|
||||
}
|
||||
|
||||
pkgs = append(pkgs, newPnpmPackage(resolver, reader.Location, name, version))
|
||||
}
|
||||
|
||||
packageNameRegex := regexp.MustCompile(`^/?([^(]*)(?:\(.*\))*$`)
|
||||
splitChar := "/"
|
||||
if lockVersion >= 6.0 {
|
||||
splitChar = "@"
|
||||
}
|
||||
|
||||
// parse packages from packages section of pnpm-lock.yaml
|
||||
for nameVersion := range lockFile.Packages {
|
||||
nameVersionSplit := strings.Split(strings.TrimPrefix(nameVersion, "/"), "/")
|
||||
nameVersion = packageNameRegex.ReplaceAllString(nameVersion, "$1")
|
||||
nameVersionSplit := strings.Split(strings.TrimPrefix(nameVersion, "/"), splitChar)
|
||||
|
||||
// last element in split array is version
|
||||
version := nameVersionSplit[len(nameVersionSplit)-1]
|
||||
|
||||
// construct name from all array items other than last item (version)
|
||||
name := strings.Join(nameVersionSplit[:len(nameVersionSplit)-1], "/")
|
||||
name := strings.Join(nameVersionSplit[:len(nameVersionSplit)-1], splitChar)
|
||||
|
||||
if hasPkg(pkgs, name, version) {
|
||||
continue
|
||||
}
|
||||
|
||||
pkgs = append(pkgs, newPnpmPackage(resolver, reader.Location, name, version))
|
||||
}
|
||||
@@ -55,3 +95,16 @@ func parsePnpmLock(resolver source.FileResolver, _ *generic.Environment, reader
|
||||
|
||||
return pkgs, nil, nil
|
||||
}
|
||||
|
||||
func hasPkg(pkgs []pkg.Package, name, version string) bool {
|
||||
for _, p := range pkgs {
|
||||
if p.Name == name && p.Version == version {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func parseVersion(version string) string {
|
||||
return strings.SplitN(version, "(", 2)[0]
|
||||
}
|
||||
|
||||
@@ -52,3 +52,95 @@ func TestParsePnpmLock(t *testing.T) {
|
||||
|
||||
pkgtest.TestFileParser(t, fixture, parsePnpmLock, expectedPkgs, expectedRelationships)
|
||||
}
|
||||
|
||||
func TestParsePnpmV6Lock(t *testing.T) {
|
||||
var expectedRelationships []artifact.Relationship
|
||||
fixture := "test-fixtures/pnpm-v6/pnpm-lock.yaml"
|
||||
|
||||
locationSet := source.NewLocationSet(source.NewLocation(fixture))
|
||||
|
||||
expectedPkgs := []pkg.Package{
|
||||
{
|
||||
Name: "@testing-library/jest-dom",
|
||||
Version: "5.16.5",
|
||||
PURL: "pkg:npm/%40testing-library/jest-dom@5.16.5",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
},
|
||||
{
|
||||
Name: "@testing-library/react",
|
||||
Version: "13.4.0",
|
||||
PURL: "pkg:npm/%40testing-library/react@13.4.0",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
},
|
||||
{
|
||||
Name: "@testing-library/user-event",
|
||||
Version: "13.5.0",
|
||||
PURL: "pkg:npm/%40testing-library/user-event@13.5.0",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
},
|
||||
{
|
||||
Name: "react",
|
||||
Version: "18.2.0",
|
||||
PURL: "pkg:npm/react@18.2.0",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
},
|
||||
{
|
||||
Name: "react-dom",
|
||||
Version: "18.2.0",
|
||||
PURL: "pkg:npm/react-dom@18.2.0",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
},
|
||||
{
|
||||
Name: "web-vitals",
|
||||
Version: "2.1.4",
|
||||
PURL: "pkg:npm/web-vitals@2.1.4",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
},
|
||||
{
|
||||
Name: "@babel/core",
|
||||
Version: "7.21.4",
|
||||
PURL: "pkg:npm/%40babel/core@7.21.4",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
},
|
||||
{
|
||||
Name: "@types/eslint",
|
||||
Version: "8.37.0",
|
||||
PURL: "pkg:npm/%40types/eslint@8.37.0",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
},
|
||||
{
|
||||
Name: "read-cache",
|
||||
Version: "1.0.0",
|
||||
PURL: "pkg:npm/read-cache@1.0.0",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
},
|
||||
{
|
||||
Name: "schema-utils",
|
||||
Version: "3.1.2",
|
||||
PURL: "pkg:npm/schema-utils@3.1.2",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
},
|
||||
}
|
||||
|
||||
pkgtest.TestFileParser(t, fixture, parsePnpmLock, expectedPkgs, expectedRelationships)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
lockfileVersion: '6.0'
|
||||
|
||||
dependencies:
|
||||
'@testing-library/jest-dom':
|
||||
specifier: ^5.16.5
|
||||
version: 5.16.5
|
||||
'@testing-library/react':
|
||||
specifier: ^13.4.0
|
||||
version: 13.4.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@testing-library/user-event':
|
||||
specifier: ^13.5.0
|
||||
version: 13.5.0(@testing-library/dom@9.2.0)
|
||||
react:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0
|
||||
react-dom:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0(react@18.2.0)
|
||||
web-vitals:
|
||||
specifier: ^2.1.4
|
||||
version: 2.1.4
|
||||
|
||||
packages:
|
||||
/@babel/core@7.21.4:
|
||||
resolution: {integrity: sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.2.1
|
||||
'@babel/code-frame': 7.21.4
|
||||
'@babel/generator': 7.21.4
|
||||
'@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.4)
|
||||
'@babel/helper-module-transforms': 7.21.2
|
||||
'@babel/helpers': 7.21.0
|
||||
'@babel/parser': 7.21.4
|
||||
'@babel/template': 7.20.7
|
||||
'@babel/traverse': 7.21.4
|
||||
'@babel/types': 7.21.4
|
||||
convert-source-map: 1.9.0
|
||||
debug: 4.3.4
|
||||
gensync: 1.0.0-beta.2
|
||||
json5: 2.2.3
|
||||
semver: 6.3.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@testing-library/jest-dom@5.16.5:
|
||||
resolution: {integrity: sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==}
|
||||
engines: {node: '>=8', npm: '>=6', yarn: '>=1'}
|
||||
dependencies:
|
||||
'@adobe/css-tools': 4.2.0
|
||||
'@babel/runtime': 7.21.0
|
||||
'@types/testing-library__jest-dom': 5.14.5
|
||||
aria-query: 5.1.3
|
||||
chalk: 3.0.0
|
||||
css.escape: 1.5.1
|
||||
dom-accessibility-api: 0.5.16
|
||||
lodash: 4.17.21
|
||||
redent: 3.0.0
|
||||
dev: false
|
||||
|
||||
/@testing-library/react@13.4.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==}
|
||||
engines: {node: '>=12'}
|
||||
peerDependencies:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.21.0
|
||||
'@testing-library/dom': 8.20.0
|
||||
'@types/react-dom': 18.2.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@testing-library/user-event@13.5.0(@testing-library/dom@9.2.0):
|
||||
resolution: {integrity: sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==}
|
||||
engines: {node: '>=10', npm: '>=6'}
|
||||
peerDependencies:
|
||||
'@testing-library/dom': '>=7.21.4'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.21.0
|
||||
'@testing-library/dom': 9.2.0
|
||||
dev: false
|
||||
|
||||
/@types/eslint@8.37.0:
|
||||
resolution: {integrity: sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==}
|
||||
dependencies:
|
||||
'@types/estree': 1.0.1
|
||||
'@types/json-schema': 7.0.11
|
||||
dev: false
|
||||
|
||||
/react-dom@18.2.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
react: 18.2.0
|
||||
scheduler: 0.23.0
|
||||
dev: false
|
||||
|
||||
/react@18.2.0:
|
||||
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
dev: false
|
||||
|
||||
/read-cache@1.0.0:
|
||||
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
|
||||
dependencies:
|
||||
pify: 2.3.0
|
||||
dev: false
|
||||
|
||||
/schema-utils@3.1.2:
|
||||
resolution: {integrity: sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
dependencies:
|
||||
'@types/json-schema': 7.0.11
|
||||
ajv: 6.12.6
|
||||
ajv-keywords: 3.5.2(ajv@6.12.6)
|
||||
dev: false
|
||||
|
||||
/web-vitals@2.1.4:
|
||||
resolution: {integrity: sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==}
|
||||
dev: false
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user